## Billing

URL: https://appwrite.io/docs/advanced/billing

Learn how to manage billing for your organization, find the plan that best suits your needs, explore optional add-ons, and understand Appwrite's service level agreements and billing policies.

### Manage billing

Configure your organization's plan, payment methods, and spending controls.

- [Manage billing](/docs/advanced/billing/payments): Manage your plan, billing periods, payment methods, budget caps, and invoices.

### Plans

Learn which plan best suits your organization.

- [Free](/docs/advanced/billing/free): Learn about Appwrite Free plan. Free plan for hobby projects and learners.

- [Pro](/docs/advanced/billing/pro): Learn about Appwrite Pro, for growing organizations that need to scale.

- [Enterprise](/docs/advanced/billing/enterprise): Learn about Appwrite Enterprise, for large organizations with advanced needs.

- [Open source](/docs/advanced/billing/oss): Appwrite provides special plans for open source projects.

### Add ons

Learn about additional features and functionalities that Appwrite offers.

- [Compute](/docs/advanced/billing/compute): CPU and memory for Functions and Sites, including build and runtime specs.

- [Phone OTP](/docs/advanced/billing/phone-otp): Learn how Appwrite handles SMS-based OTP authentication.

- [Image Transformations](/docs/advanced/billing/image-transformations): Learn how to transform images dynamically with Appwrite.

- [Database Reads and Writes](/docs/advanced/billing/database-reads-and-writes): Learn how Appwrite handles database reads and writes.

- [Text Embeddings](/docs/advanced/billing/embeddings): Learn how Appwrite meters and bills text embedding tokens per model.

### Service level agreements

Understand the service level agreements available on Appwrite's paid plans.

- [Support SLA](/docs/advanced/billing/support-sla): Learn about Appwrite's support response time commitments.

- [Uptime SLA](/docs/advanced/billing/uptime-sla): Learn about Appwrite's uptime commitments.

### Policies

Understand the policies that govern usage and billing on Appwrite.

- [Fair use](/docs/advanced/billing/fair-use-policy): Understand Appwrite's usage limits, prohibited activities, and enforcement actions.

- [Abuse](/docs/advanced/billing/abuse): Guidelines on abusive behavior, prohibited activities, and reporting mechanisms.

- [Refund](/docs/advanced/billing/refund-policy): Learn about Appwrite's refund policy, eligibility criteria, and request process.

---

## Abuse policy

URL: https://appwrite.io/docs/advanced/billing/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.

### Prohibited activities

The following actions are prohibited on the Appwrite platform and may lead to immediate suspension or termination:

- **Illegal and harmful content:** Sharing any content that is illegal, infringing (e.g., copyright infringement), harmful, threatening, defamatory, obscene, harassing, or otherwise objectionable. This includes distributing malware, viruses, or any malicious code.
- **Unauthorized access and disruption:** Accessing or attempting to access any system, data, or account without authorization. This includes:
    - Hacking
    - Penetration testing without approval
    - Denial-of-Service (DoS) attacks
    - Disrupting Appwrite Cloud's integrity or performance (e.g., excessive resource usage, unauthorized load testing)
- **Deceptive practices:** Engaging in any fraudulent or deceptive activity, such as:
    - Phishing
    - Misleading others
    - Circumventing payment obligations
- **Unsolicited communications:** Sending spam, unauthorized advertising, or any form of improper solicitation.
- **Misuse of resources:**
    - Using Appwrite Cloud for cryptocurrency mining without authorization
    - Violating any applicable laws or regulations
    - Using the Education plan for non-educational or commercial purposes

### 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.

---

## Compute

URL: https://appwrite.io/docs/advanced/billing/compute

On Appwrite Cloud, paid plans let you choose how much **CPU** and **memory** apply to **build** work and to **runtime** work. [Functions](/docs/products/functions) and [Sites](/docs/products/sites) each expose two settings: a **build specification** (install, compile, bundle, package) and a **runtime specification** (executions for functions; serving traffic and SSR for sites). You can pick different tiers for each phase so heavy builds do not force you to oversize steady execution, and vice versa.

These options help you tune performance and cost: for example, a large install or compiler step can use a higher build spec while a smaller spec covers routine invocations or traffic.

### Specifications

Appwrite Cloud offers the following specification tiers. The same tiers are available for **build** and **runtime** independently on each function or site:

| 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 |

**Note**

On Appwrite Cloud, **Pro** plan organizations can change build and runtime specifications from the default 512MB and 0.5 CPU. The Free plan uses the default. For custom compute options, contact our [sales team](https://appwrite.io/contact-us/enterprise).

### Build timeouts

On **Appwrite Cloud**, each function and site **deployment build** (install, compile, bundle, and package) must finish within a **maximum build duration** that depends on your organization plan:

| Plan | Maximum build duration |
| --- | --- |
| Free | 15 minutes |
| Pro / Scale | 45 minutes |
| Enterprise | Custom |

These limits apply to the **build** phase only. [Function execution timeout](/docs/products/functions/functions#timeout) and [site request timeout](/docs/products/sites/develop#timeouts) are separate settings. Compare plans on the [pricing page](/pricing).

On **self-hosted** instances, the global ceiling for configurable build timeouts is [`_APP_COMPUTE_BUILD_TIMEOUT`](/docs/advanced/self-hosting/configuration/environment-variables) (default 900 seconds). Individual function and site settings cannot exceed that server-wide maximum.

Configure specifications in the Appwrite Console under each function or site **Settings** - **Resource limits**. See also the [Functions](/docs/products/functions/functions#resource-limits) and [Sites](/docs/products/sites/develop#resource-limits) configuration guides.

### GB-Hours

GB-hours quantify compute use by combining memory (in GB) and duration (in hours). Both **build** activity and **execution** (function runs or site serving, depending on the product) draw from your plan's GB-hour pool according to the specifications in effect for that phase.

How it works:

- Memory allocation: The GB value comes from the spec in use for that phase (build or runtime).
- Duration: Time that phase is active (for example, build duration or execution time).
- Calculation: Memory (GB) multiplied by hours gives GB-hours for that usage.

**Example:**

If a function runs with 4 GB of memory for 2 hours of execution time:

**4GB * 2 hours = 8 GB-hours**

That counts toward your execution GB-hours for the billing period.

#### Pricing

- The Free plan includes up to 100 GB-hours of execution and build time per month.

- The Pro plan includes up to 1,000 GB-hours of execution and build time per month. Additional usage is billed at $0.09 per GB-hour.

Once the monthly GB-hours limit is reached, additional usage is billed automatically. Set budget alerts and a budget cap to avoid unexpected charges.

---

## Database Reads and Writes

URL: https://appwrite.io/docs/advanced/billing/database-reads-and-writes

Appwrite provides powerful database capabilities through [TablesDB](/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.

---

## Text Embeddings

URL: https://appwrite.io/docs/advanced/billing/embeddings

Appwrite generates [text embeddings](/docs/products/databases/vectorsdb/embeddings) with built-in models, so you can turn text into vectors without running a separate embedding service. Embedding usage is metered in tokens, and each model is priced and counted separately.

#### How embedding usage is counted

Every call to `createTextEmbeddings` sends one or more strings to a model. Appwrite counts the tokens in those strings and records them against the model that produced the embeddings.

How it works:
1. Generate embeddings through the Appwrite SDK or API.
2. Appwrite tracks the token count per model, not per request.
3. Token counts are aggregated per project and rolled up to your organization.
4. Allowances are refreshed monthly on your billing cycle.

Each model keeps its own counter. Embedding 2M tokens with `nomic-embed-text` and 2M tokens with `all-minilm` uses 2M against each model's allowance, not 4M against a shared pool.

#### Pricing

Rates are per model, since models differ in cost to run.

| Model | Dimension | Rate |
| --- | --- | --- |
| `nomic-embed-text` | 768 | $0.10 per 1M tokens |
| `all-minilm` | 384 | $0.05 per 1M tokens |

##### Free plan
- **Included**: 1,000,000 tokens per model per month.
- **Overage**: Not available. `createTextEmbeddings` returns a `402` error once the cap is reached. Only embedding creation is blocked, and the rest of your project keeps working.

##### Pro and Scale plans
- **Included**: 10,000,000 tokens per model per month on Pro and 100,000,000 tokens per model per month on Scale.
- **Overage**: Billed at the model's rate, rounded up per started million above the allowance.

##### Enterprise plan
- **Included**: Custom allowances.
- **Overage**: Custom rates.

#### How overage is calculated

The included allowance is a ceiling, not a floor. Usage inside it is covered by your plan price and adds nothing to your invoice, so 5M tokens against a 10M allowance bills $0 for embeddings.

Above the allowance, Appwrite bills each started million at the model's rate:

```
overage = ceil((tokens - allowance) / 1,000,000) x rate
```

For example, a Pro organization that embeds 12M tokens with `nomic-embed-text` in one cycle is billed `ceil((12M - 10M) / 1M) x $0.10 = $0.20`. Each model is calculated independently against its own allowance and rate.

Overage on paid plans counts toward your [budget cap](/docs/advanced/billing/payments), and embedding creation is never hard blocked on a paid plan.

For detailed information about the different pricing options and features, please visit the [pricing page](/pricing).

#### Best practices

To keep embedding costs predictable:
1. **Pick the smallest model that works**: `all-minilm` costs half as much per token as `nomic-embed-text` and stores 384 values per vector instead of 768.
2. **Embed once, store the vector**: reuse the stored `embeddings` field instead of regenerating it on every search.
3. **Batch your calls**: pass several strings to one `createTextEmbeddings` call to cut request overhead.
4. **Monitor usage**: track embedding tokens per model in the Appwrite Console.
5. **Set a budget cap**: bound overage on Pro and Scale before it surprises you.

---

## Enterprise

URL: https://appwrite.io/docs/advanced/billing/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.

---

## Fair use policy

URL: https://appwrite.io/docs/advanced/billing/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

- **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

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).

### Prohibited activities

The following actions are prohibited on the Appwrite platform and may lead to immediate suspension or termination:

- **Illegal and harmful content:** Sharing any content that is illegal, infringing (e.g., copyright infringement), harmful, threatening, defamatory, obscene, harassing, or otherwise objectionable. This includes distributing malware, viruses, or any malicious code.
- **Unauthorized access and disruption:** Accessing or attempting to access any system, data, or account without authorization. This includes:
    - Hacking
    - Penetration testing without approval
    - Denial-of-Service (DoS) attacks
    - Disrupting Appwrite Cloud's integrity or performance (e.g., excessive resource usage, unauthorized load testing)
- **Deceptive practices:** Engaging in any fraudulent or deceptive activity, such as:
    - Phishing
    - Misleading others
    - Circumventing payment obligations
- **Unsolicited communications:** Sending spam, unauthorized advertising, or any form of improper solicitation.
- **Misuse of resources:**
    - Using Appwrite Cloud for cryptocurrency mining without authorization
    - Violating any applicable laws or regulations
    - Using the Education plan for non-educational or commercial purposes

### 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

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

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

Appwrite reserves the right to update this policy to reflect evolving platform capabilities or changing usage patterns at any time.

### Contact us

For questions or further clarification, please [contact our support team](/contact-us).

---

## Free

URL: https://appwrite.io/docs/advanced/billing/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

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

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.

#### Check resource usage

You can check your organization's resource usage for the current billing cycle by navigating to your organization, under the **Usage** tab.

#### Reaching resource limits

Reaching your organization's resource limits will have the following effects until the current billing period ends.

- Component
- Consequence

---

- **Bandwidth**
- More bandwidth will be purchased automatically until your organization reaches a budget cap. If the organization uses a Free plan or a budget cap is reached, API access will be denied until your organization's plan is upgraded or your budget cap is increased.

---

- Importing projects via migrations disabled, but you can still export your projects.

---

- Platform creation disabled.

---

- **Users**
- Creating new accounts and team invitations disabled.

---

- **Compute**
- Function executions are disabled.

---

- **Realtime**
- Realtime subscriptions disabled.

---

- **Storage**
- File uploads are disabled. Persists across billing periods until the amount of storage used is below the plan limit.

#### Switching to Free plan and reaching limits

When an orgnization switches from Pro or Scale plan to Free plan, the organization's projects will be able to take advantage of the existing limits until the end of the current billing period.
After the billing period ends, the Free plan limits and consequences will apply.

If an organisation has multiple members after the billing period ends, all admins besides the original creator of the organization will be removed.
The following consequences should also apply at the project level if the Free plan per-project resource limits have been exceeded.

- Component
- Action

---

- **Platforms**
- If more than 3 platforms have been created, disable them in order of date created (oldest ones first).

---

- **Webhooks**
- If more than 2 webhooks have been created, disable them in order of date created (oldest ones first).

---

- **Teams**
- If more than 100 teams have been created, disable them in order of date created (oldest ones first).

---

- **Databases**
- If more than 1 database has been created, disable them in order of date created (oldest ones first).

---

- **Buckets**
- If more than 3 buckets have been created, disable them in order of date created (oldest ones first).

---

- **Functions**
- If more than 5 functions have been created, disable them in order of date created (oldest ones first).

---

## Image Transformations

URL: https://appwrite.io/docs/advanced/billing/image-transformations

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).

---

## Open source

URL: https://appwrite.io/docs/advanced/billing/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

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&nbsp;[Open Source Initiative definition](https://opensource.org/osd/)
- Have obtained an approved&nbsp;[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

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.

---

## Manage billing

URL: https://appwrite.io/docs/advanced/billing/payments

Appwrite allows you to configure billing per organization.
You can access your organizations billing information under the **Billing** tab of your organization.

### 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 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
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
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
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
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
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
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
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

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).

---

## Phone OTP

URL: https://appwrite.io/docs/advanced/billing/phone-otp

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 testing

You can use the [Mock phone numbers](/docs/products/auth/security#mock-phone-numbers) feature to test your integrations without incurring any costs.

### SMS messages

You'll be charged per SMS sent.

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

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.38 |
| +376 | Andorra | $ 0.14 |
| +244 | Angola | $ 0.15 |
| +54 | Argentina | $ 0.14 |
| +374 | Armenia | $ 0.29 |
| +297 | Aruba | $ 0.35 |
| +61 | Australia | $ 0.05 |
| +43 | Austria | $ 0.04 |
| +994 | Azerbaijan | $ 0.54 |
| +973 | Bahrain | $ 0.05 |
| +880 | Bangladesh | $ 0.62 |
| +375 | Belarus | $ 0.32 |
| +32 | Belgium | $ 0.14 |
| +501 | Belize | $ 0.43 |
| +229 | Benin | $ 0.39 |
| +975 | Bhutan | $ 0.47 |
| +591 | Bolivia | $ 0.31 |
| +387 | Bosnia and Herzegovina | $ 0.08 |
| +267 | Botswana | $ 0.13 |
| +55 | Brazil | $ 0.05 |
| +673 | Brunei | $ 0.09 |
| +359 | Bulgaria | $ 0.23 |
| +226 | Burkina Faso | $ 0.34 |
| +257 | Burundi | $ 0.55 |
| +855 | Cambodia | $ 0.57 |
| +237 | Cameroon | $ 0.38 |
| +238 | Cape Verde Islands | $ 0.28 |
| +56 | Chile | $ 0.04 |
| +86 | China | $ 0.03 |
| +57 | Colombia | $ 0.05 |
| +269 | Comoros and Mayotte | $ 0.54 |
| +242 | Congo | $ 0.39 |
| +682 | Cook Islands | $ 0.17 |
| +506 | Costa Rica | $ 0.22 |
| +385 | Croatia | $ 0.19 |
| +53 | Cuba | $ 0.11 |
| +357 | Cyprus | $ 0.02 |
| +420 | Czech Republic | $ 0.09 |
| +45 | Denmark | $ 0.08 |
| +253 | Djibouti | $ 0.19 |
| +593 | Ecuador | $ 0.32 |
| +20 | Egypt | $ 0.54 |
| +503 | El Salvador | $ 0.11 |
| +240 | Equatorial Guinea | $ 0.28 |
| +291 | Eritrea | $ 0.17 |
| +372 | Estonia | $ 0.07 |
| +251 | Ethiopia | $ 0.51 |
| +500 | Falkland Islands | $ 0.13 |
| +298 | Faroe Islands | $ 0.09 |
| +679 | Fiji | $ 0.29 |
| +358 | Finland | $ 0.12 |
| +33 | France | $ 0.10 |
| +594 | French Guiana | $ 0.19 |
| +689 | French Polynesia | $ 0.14 |
| +241 | Gabon | $ 0.42 |
| +220 | Gambia | $ 0.13 |
| +995 | Georgia | $ 0.20 |
| +49 | Germany | $ 0.14 |
| +233 | Ghana | $ 0.45 |
| +350 | Gibraltar | $ 0.12 |
| +30 | Greece | $ 0.07 |
| +299 | Greenland | $ 0.04 |
| +590 | Guadeloupe | $ 0.21 |
| +1671 | Guam | $ 0.04 |
| +502 | Guatemala | $ 0.31 |
| +224 | Guinea | $ 0.37 |
| +245 | Guinea-Bissau | $ 0.37 |
| +592 | Guyana | $ 0.31 |
| +509 | Haiti | $ 0.47 |
| +504 | Honduras | $ 0.33 |
| +852 | Hong Kong | $ 0.08 |
| +36 | Hungary | $ 0.10 |
| +354 | Iceland | $ 0.10 |
| +91 | India | $ 0.003 |
| +62 | Indonesia | $ 0.58 |
| +98 | Iran | $ 0.39 |
| +964 | Iraq | $ 0.56 |
| +353 | Ireland | $ 0.11 |
| +972 | Israel | $ 0.01 |
| +39 | Italy | $ 0.07 |
| +81 | Japan | $ 0.09 |
| +962 | Jordan | $ 0.57 |
| +254 | Kenya | $ 0.38 |
| +686 | Kiribati | $ 0.08 |
| +850 | North Korea | $ 0.03 |
| +82 | South Korea | $ 0.03 |
| +965 | Kuwait | $ 0.30 |
| +996 | Kyrgyzstan | $ 0.49 |
| +856 | Laos | $ 0.19 |
| +371 | Latvia | $ 0.09 |
| +961 | Lebanon | $ 0.42 |
| +266 | Lesotho | $ 0.16 |
| +231 | Liberia | $ 0.38 |
| +218 | Libya | $ 0.60 |
| +423 | Liechtenstein | $ 0.05 |
| +370 | Lithuania | $ 0.07 |
| +352 | Luxembourg | $ 0.13 |
| +853 | Macao | $ 0.04 |
| +389 | Macedonia | $ 0.07 |
| +261 | Madagascar | $ 0.62 |
| +265 | Malawi | $ 0.40 |
| +60 | Malaysia | $ 0.40 |
| +960 | Maldives | $ 0.44 |
| +223 | Mali | $ 0.36 |
| +356 | Malta | $ 0.08 |
| +692 | Marshall Islands | $ 0.03 |
| +596 | Martinique | $ 0.21 |
| +222 | Mauritania | $ 0.30 |
| +52 | Mexico | $ 0.35 |
| +691 | Micronesia | $ 0.03 |
| +373 | Moldova | $ 0.11 |
| +377 | Monaco | $ 0.13 |
| +976 | Mongolia | $ 0.46 |
| +212 | Morocco | $ 0.36 |
| +258 | Mozambique | $ 0.29 |
| +95 | Myanmar | $ 0.59 |
| +264 | Namibia | $ 0.07 |
| +674 | Nauru | $ 0.29 |
| +977 | Nepal | $ 0.47 |
| +31 | Netherlands | $ 0.15 |
| +687 | New Caledonia | $ 0.12 |
| +64 | New Zealand | $ 0.11 |
| +505 | Nicaragua | $ 0.21 |
| +227 | Niger | $ 0.42 |
| +234 | Nigeria | $ 0.61 |
| +683 | Niue | $ 0.05 |
| +672 | Norfolk Islands | $ 0.07 |
| +1 | North America | $ 0.02 |
| +1670 | Northern Mariana Islands | $ 0.11 |
| +47 | Norway | $ 0.11 |
| +968 | Oman | $ 0.25 |
| +680 | Palau | $ 0.12 |
| +92 | Pakistan | $ 0.58 |
| +507 | Panama | $ 0.22 |
| +675 | Papua New Guinea | $ 0.26 |
| +595 | Paraguay | $ 0.12 |
| +51 | Peru | $ 0.04 |
| +63 | Philippines | $ 0.34 |
| +48 | Poland | $ 0.04 |
| +351 | Portugal | $ 0.04 |
| +974 | Qatar | $ 0.34 |
| +262 | Reunion | $ 0.09 |
| +40 | Romania | $ 0.08 |
| +7 | Russia and Kazakhstan | $ 0.52 |
| +250 | Rwanda | $ 0.49 |
| +378 | San Marino | $ 0.06 |
| +239 | Sao Tome and Principe | $ 0.11 |
| +966 | Saudi Arabia | $ 0.33 |
| +221 | Senegal | $ 0.46 |
| +381 | Serbia | $ 0.46 |
| +248 | Seychelles | $ 0.43 |
| +232 | Sierra Leone | $ 0.35 |
| +65 | Singapore | $ 0.09 |
| +421 | Slovak Republic | $ 0.09 |
| +386 | Slovenia | $ 0.18 |
| +677 | Solomon Islands | $ 0.12 |
| +252 | Somalia | $ 0.24 |
| +27 | South Africa | $ 0.16 |
| +34 | Spain | $ 0.07 |
| +94 | Sri Lanka | $ 0.60 |
| +290 | St. Helena | $ 0.06 |
| +249 | Sudan | $ 0.48 |
| +597 | Suriname | $ 0.27 |
| +268 | Swaziland | $ 0.13 |
| +46 | Sweden | $ 0.09 |
| +41 | Switzerland | $ 0.06 |
| +963 | Syria | $ 0.60 |
| +886 | Taiwan | $ 0.08 |
| +992 | Tajikistan | $ 0.57 |
| +255 | Tanzania | $ 0.49 |
| +66 | Thailand | $ 0.03 |
| +228 | Togo | $ 0.53 |
| +676 | Tonga | $ 0.25 |
| +216 | Tunisia | $ 0.54 |
| +90 | Turkey | $ 0.01 |
| +993 | Turkmenistan | $ 0.39 |
| +688 | Tuvalu | $ 0.13 |
| +256 | Uganda | $ 0.41 |
| +380 | Ukraine | $ 0.25 |
| +971 | United Arab Emirates | $ 0.16 |
| +44 | United Kingdom | $ 0.08 |
| +598 | Uruguay | $ 0.11 |
| +998 | Uzbekistan | $ 0.64 |
| +678 | Vanuatu | $ 0.28 |
| +58 | Venezuela | $ 0.44 |
| +84 | Vietnam | $ 0.28 |
| +967 | Yemen | $ 0.34 |
| +260 | Zambia | $ 0.46 |
| +263 | Zimbabwe | $ 0.32 |

---

## Pro

URL: https://appwrite.io/docs/advanced/billing/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

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

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

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

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.

#### Check resource usage

You can check your organization's resource usage for the current billing cycle by navigating to your organization, under the **Usage** tab.

#### Reaching resource limits

Reaching your organization's resource limits will have the following effects until the current billing period ends.

- Component
- Consequence

---

- **Bandwidth**
- More bandwidth will be purchased automatically until your organization reaches a budget cap. If the organization uses a Free plan or a budget cap is reached, API access will be denied until your organization's plan is upgraded or your budget cap is increased.

---

- Importing projects via migrations disabled, but you can still export your projects.

---

- Platform creation disabled.

---

- **Users**
- Creating new accounts and team invitations disabled.

---

- **Compute**
- Function executions are disabled.

---

- **Realtime**
- Realtime subscriptions disabled.

---

- **Storage**
- File uploads are disabled. Persists across billing periods until the amount of storage used is below the plan limit.

#### Switching to Free plan and reaching limits

When an orgnization switches from Pro or Scale plan to Free plan, the organization's projects will be able to take advantage of the existing limits until the end of the current billing period.
After the billing period ends, the Free plan limits and consequences will apply.

If an organisation has multiple members after the billing period ends, all admins besides the original creator of the organization will be removed.
The following consequences should also apply at the project level if the Free plan per-project resource limits have been exceeded.

- Component
- Action

---

- **Platforms**
- If more than 3 platforms have been created, disable them in order of date created (oldest ones first).

---

- **Webhooks**
- If more than 2 webhooks have been created, disable them in order of date created (oldest ones first).

---

- **Teams**
- If more than 100 teams have been created, disable them in order of date created (oldest ones first).

---

- **Databases**
- If more than 1 database has been created, disable them in order of date created (oldest ones first).

---

- **Buckets**
- If more than 3 buckets have been created, disable them in order of date created (oldest ones first).

---

- **Functions**
- If more than 5 functions have been created, disable them in order of date created (oldest ones first).

---

## Refund policy

URL: https://appwrite.io/docs/advanced/billing/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

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

Refunds may be considered in the following circumstances:

#### Service delivery failure

Critical and unresolved service outages affecting paid plans, or confirmed infrastructure issues that significantly impact service availability and performance.

#### Billing errors

Customers who are incorrectly charged due to:

- Duplicate payments
- Plan mismatches
- Proven invoicing errors

#### 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 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

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/billing/fair-use-policy) policy

### 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

Whenever possible, we will first consider non-monetary solutions such as:

- Service credits
- Extended trial periods
- Plan adjustments

### Modifications

We reserve the right to modify this policy at any time. Changes become effective upon posting to our website. Your continued use of our services after changes indicates your acceptance of the updated policy.

### Contact us

For questions or concerns about this policy, please contact us at our [contact page](https://appwrite.io/contact-us).

---

## Support SLA

URL: https://appwrite.io/docs/advanced/billing/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.

### Modifications

We reserve the right to modify this policy at any time. Changes become effective upon posting to our website. Your continued use of our services after changes indicates your acceptance of the updated policy.

### Contact us

For questions or concerns about this policy, please contact us at our [contact page](https://appwrite.io/contact-us).

---

## Uptime SLA

URL: https://appwrite.io/docs/advanced/billing/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

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% |

Uptime is measured only for the specific Appwrite services your organization actively uses. An outage affecting services that your organization does not use will not be counted as downtime under this SLA.

### 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

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 |

Credits are calculated based on the monthly fee for your base subscription plan plus any non-usage-based add-ons (e.g., BAA, SOC-2). Usage-based charges are excluded from credit calculations.

### 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

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

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.

### Modifications

We reserve the right to modify this policy at any time. Changes become effective upon posting to our website. Your continued use of our services after changes indicates your acceptance of the updated policy.

### Contact us

For questions or concerns about this policy, please contact us at our [contact page](https://appwrite.io/contact-us).

---

## Migrations

URL: 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

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 | Rows | Files | Functions | Sites |
|--------|-------|-----------|-----------|-------|-----------|-------|
| [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 | enabled |
| [Self hosted](/docs/advanced/migrations/self-hosted) | enabled | enabled | enabled | enabled | enabled | enabled |

### 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

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

URL: 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.

#### 1. 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.

#### 2. 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.

#### 3. 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, functions, and sites.

1. Click **Start Migration** to start the migration process. You do not need to keep the Appwrite Console open through the process.

**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.

---

## Migrate from Firebase

URL: 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.

**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.

#### 1. 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. |

#### 2. 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.

#### 3. 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.

#### 4. 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/security/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).

### 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

URL: 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.

**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.

#### 1. 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. |

#### 2. 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.

#### 3. 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/security/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).

### 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

URL: 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.

#### 1. 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.

#### 2. 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.

#### 3. 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, functions, and sites.

3. Click **Start migration** to start the migration process. You do not need to keep the Appwrite Console open through the process.

---

## Migrate from Supabase

URL: 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.

**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.

#### 1. Obtain credentials

Find all of the following credentials from your Supabase project.

1. Go to your Supabase project dashboard and click the **Connect** button in the top right corner.

1. A modal will appear with several connection options. Select **Transaction pooler** (recommended for migrations).

1. Copy the connection details from the modal to retrieve the **Host**, **Port** and **Username** fields below.

| Field | Description |
| ---------------| -------------------------------------------------------------------------------------------------------------- |
| **Host** | The host of your Supabase Database, found in the **Transaction pooler** connection string from the **Connect** modal. |
| **Port** | The port of your Supabase Database, found in the **Transaction pooler** connection string. |
| **Username** | The username of your Supabase Database, found in the **Transaction pooler** connection string from the **Connect** modal. |
| **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 in **Database Settings**. |
| **Endpoint** | This is the endpoint of your Supabase instance under **Project Settings > API > Project URL**. This is used to migrate your files. |
| **API Key** | This is the key of your Supabase instance under **Project Settings > API keys**. This is used to migrate your files. Make sure to use the hidden **service_role** key. |

#### 2. 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.

#### 3. 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/security/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).

### 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.

---

## Security

URL: 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/security/permissions), and [HTTPS/TLS](/docs/advanced/security/tls) to protect you and your users' data.

### 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.

- [GDPR](/docs/advanced/security/gdpr): Appwrite is GDPR compliant. Learn about our measures, privacy policy, and find our data processing agreement.

- [PCI](/docs/advanced/security/pci): Appwrite uses Stripe to handle payment and payment information securely. Learn about Appwrite's PCI compliance.

- [SOC 2](/docs/advanced/security/soc2): Appwrite is SOC2 Type I compliant. Learn about Appwrite's measures to meet SOC 2 standards.

- [HIPAA](/docs/advanced/security/hipaa): Appwrite is HIPAA compliant. Learn about Appwrite's measures to protect personal health information.

- [CCPA](/docs/advanced/security/ccpa): Appwrite is CCPA compliant. Learn about our measures to protect consumer privacy under the California Consumer Privacy Act.

### 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.

- [Authentication](/docs/products/auth/security): Secure authentication methods to protect your users and promote better passwords.

- [Encryption](/docs/advanced/security/encryption): Appwrite encrypts sensitive data and files in Appwrite Databases and Storage.

- [Multi-factor authentication](/docs/advanced/security/mfa): Add a second layer of protection by requiring users to verify their identity with multiple factors.

- [HTTPS](/docs/advanced/security/https): Appwrite Cloud enforces HTTPS on all endpoints to prevent on-path attacks like packet sniffing.

- [TLS](/docs/advanced/security/tls): Appwrite assigns TLS certificates on all Appwrite and user provided domains connected to Appwrite.

- [Backups](/docs/advanced/security/backups): Appwrite Cloud uses regular backups to prevent data loss and improve resiliency.

- [Penetration tests](/docs/advanced/security/penetration-tests): Appwrite employs regular third-party penetration tests to find vulnerabilities.

- [Audit logs](/docs/advanced/security/audit-logs): Appwrite provides detailed audit logs for each product to track and discover suspicious activity.

- [Abuse protection](/docs/advanced/security/abuse-protection): Appwrite protects against common abuse methods like DoS and brute-force attacks.

### Access control

Appwrite is secure by default and provides tools for you to manage
access control and prevent abuse.

- [Permissions](/docs/advanced/security/permissions): Control which users can access which resources.

- [Roles](/docs/advanced/security/roles): Learn about Console organization member roles and the access each one grants.

- [Rate limits](/docs/advanced/security/rate-limits): Appwrite has rate limits on some endpoints to prevent abuse.

- [API keys](/docs/partners/project/api-keys): Create and manage API keys used by Server SDKs.

- [Dev keys](/docs/advanced/security/dev-keys): Create and manage dev keys used by Client SDKs in dev environments.

### Configuration

Pass constants and secrets to your Appwrite resources.

- [Environment variables](/docs/partners/project/environment-variables): Use project, function, and site environment variables to pass constants and secrets to your Functions and Sites.

### 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

URL: 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
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).

[Learn more about rate limits](/docs/advanced/security/rate-limits)

### Email policies
Email policies let you block sign-ups and email updates from free, aliased, or disposable email providers. This helps keep throwaway accounts, signup spam, and bot registrations out of your project before they reach your authentication and rate-limit layers. Policies run at sign-up time and on email updates, and do not affect existing sessions.

[Learn more about email policies](/docs/products/auth/email-policies)

### Cross-origin resource sharing (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**.

![Add a platform](/images/docs/quick-starts/add-platform.avif)

[Learn more about debugging CORS errors](/blog/post/cors-error)

### 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

URL: 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

You can access audit logs for different products under the **Activity** tab where applicable.
Logs are available for tables, rows, and individual users.

![Project settings screen](/images/docs/advanced/security/activity.avif)

### Format

Audit log entries under the **Activity** tab has the following structure.
Each entry describes an event.

- Column
- Description
---
- User
- Name of the user that performed the event.
---
- Event
- The name of the [event](/docs/apis/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.

### 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

URL: 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.

### Persistence

Appwrite handles the persistence of the session in a consistent way across SDKs. After authenticating with an SDK, the SDK will persist the session so that the user will not need to log in again the next time they open the app. The mechanism for persistence depends on the SDK.

**Best Practice**

Only keep user sessions active as long as needed and maintain exactly **one** instance of the Client SDK in your app to avoid conflicting session data.

| | Framework | Storage method |
| :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------: | :--------------------------------------------------------------------------------------------------: |
| ![Javascript logo](/images/platforms/light/javascript.svg) | Web | Uses a secure session cookie and falls back to local storage when a session cookie is not available. |
| ![Javascript logo](/images/platforms/light/flutter.svg) | Flutter | Uses a session cookie stored in Application Documents through the **path_provider** package. |
| ![Javascript logo](/images/platforms/light/apple.svg) | Apple | Uses a session cookie stored in **UserDefaults**. |
| ![Javascript logo](/images/platforms/light/android.svg) | Android | Uses a session cookie stored in **SharedPreferences**. |

### Session limits

In Appwrite versions 1.2 and above, you can limit the number of active sessions created per user to prevent the accumulation of unused but active sessions. New sessions created by the same user past the session limit delete the oldest session.

You can change the session limit in the **Security** tab of the Auth Service in your Appwrite Console. The default session limit is 10 with a maximum configurable limit of 100.

### Permissions

Security is very important to protect users' data and privacy.
Appwrite uses a [permissions model](/docs/advanced/security/permissions) coupled with user sessions to ensure users need correct permissions to access resources.
With all Appwrite services, including databases and storage, access is granted at the table, bucket, row, or file level.
These permissions are enforced for client SDKs and server SDKs when using JWT, but are ignored when using a server SDK with an API key.

### Password strength

Password strength lets you set the minimum requirements a password must meet when a user creates an account or changes their password. Enforcing these rules makes passwords harder to guess and brute-force.

You can configure two kinds of requirements:

- **Minimum length**: the smallest number of characters a password is allowed to have.
- **Character requirements**: require any combination of an uppercase letter, a lowercase letter, a number, and a special character. Each requirement is an independent toggle, so you can enforce as few or as many as your app needs.

Passwords that don't meet the configured requirements are rejected when a user signs up and whenever they change their password. To configure password strength, navigate to **Auth** > **Security** > **Password strength**, set the minimum length and character requirements, then click **Update**.

### Password history

Password history prevents users from reusing recent passwords. This protects user accounts from security risks by enforcing a new password every time it's changed.

Password history can be enabled in the Auth service's **Security** tab on the Appwrite Console. You can choose how many previous passwords to remember, up to a maximum of 20, and block users from reusing them.

### Password dictionary

Password dictionary protects users from using bad passwords. It compares the user's password to the [10,000 most common passwords](https://github.com/danielmiessler/SecLists/blob/master/Passwords/Common-Credentials/10k-most-common.txt) and throws an error if there's a match. Together with [rate limits](/docs/advanced/security/rate-limits), password dictionary will significantly reduce the chance of a malicious actor guessing user passwords.

Password dictionary can be enabled in the Auth service's **Security** tab on the Appwrite Console.

### Password hashing

Appwrite protects passwords by using the [Argon2](https://github.com/P-H-C/phc-winner-argon2) password-hashing algorithm.

Argon 2 is a resilient and secure password hashing algorithm that is also the winner of the [Password Hashing Competition](https://www.password-hashing.net/).

Appwrite combines Argon 2 with the use of techniques such as salting, adjustable work factors, and memory hardness to securely handle passwords.

If an user is imported into Appwrite with hash differnt than Argon2, the password will be re-hashed on first successful user's sign in. This ensures all passwords are stored as securely as possible.

### Personal data

Encourage passwords that are hard to guess by disallowing users to pick passwords that contain personal data.
Personal data includes the user's name, email, and phone number.

Disallowing personal data can be enabled in the Auth service's **Security** tab on the Appwrite Console.

### Email policies

Email policies let you restrict which email addresses can sign up for your project. You can independently block free email providers, aliased addresses, and disposable email services to keep throwaway accounts, signup spam, and bot registrations out of your user base. Policies run at sign-up and on email updates, and existing users can still sign in even if their address would not pass the current policy.

Email policies can be enabled in the Auth service's **Security** tab on the Appwrite Console, or programmatically through the Project service. Learn more in the [Email policies](/docs/products/auth/email-policies) docs.

### Session alerts

Enable email alerts for your users so that whenever a new session is created for their account, they will be alerted with details about the sign-in. This helps users quickly spot unauthorized access and take action to secure their account.

#### When alerts are not sent

Session alerts are intentionally skipped in a few situations to avoid redundant or confusing emails:

- **First session after sign-up**: the very first sign-in a user makes after creating their account does not trigger an alert. A brand-new account doesn't yet hold anything worthy of protection, so alerting at this stage adds no real security value. It also prevents a double-email situation in flows where your project may already be sending a welcome or verification email.
- **[Magic URL](/docs/products/auth/magic-url), [Email OTP](/docs/products/auth/email-otp), and [OAuth2](/docs/products/auth/oauth2) sign-ins**: these authentication methods already verify the user's access to the sign-in channel (their inbox or identity provider), so no additional alert is needed.
- **No email address on file**: users who have not set an email address on their account will not receive alerts.

To toggle session alerts, navigate to **Auth** > **Security** > **Session alerts**.

### 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

### Mock phone numbers

Creating and using mock phone numbers allows users to test SMS authentication without needing an actual phone number. This can be useful for testing edge cases where a user doesn't have a phone number but needs to sign in to your application using SMS.

To create a mock phone number, navigate to **Auth** > **Security** > Mock Phone Numbers. After defining a mock phone number, you need to define a specific OTP code that will be used for SMS sign-in instead of the SMS secret code sent to a real phone number.

---

## Backups

URL: 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

URL: 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.

---

## Dev keys

URL: https://appwrite.io/docs/advanced/security/dev-keys

**Deprecation notice**

The creation of new dev keys is paused, and dev keys will be deprecated on September 1, 2026. Appwrite's login rate limit has changed so that a successful login now resets it, which means you no longer need a dev key to test authentication flows during development. We recommend planning your migration away from dev keys ahead of time. [Learn more in the changelog](/changelog/entry/2026-07-22).

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.

**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.

### Dev keys vs API keys

Dev keys and [API keys](/docs/partners/project/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

To create a new dev key, navigate to **Overview** > **Integrations** > **Dev keys** and click **Create Dev key**.

![Create dev key](/images/docs/dev-keys/light.avif)

You can then implement the dev key while initializing the Appwrite client in your app.

```client-web
import { Client } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<YOUR_PROJECT_ID>')
    .setDevKey('<YOUR_DEV_KEY>'); // Your dev key
```

```client-flutter
import 'package:appwrite/appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<YOUR_PROJECT_ID>')
    .setDevKey('<YOUR_DEV_KEY>'); // Your dev key
```

```client-react-native
import { Client } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<YOUR_PROJECT_ID>')
    .setDevKey('<YOUR_DEV_KEY>'); // Your dev key
```

```client-apple
import Appwrite

let client = Client()
    .setEndpoint("https://cloud.appwrite.io/v1")
    .setProject("<YOUR_PROJECT_ID>")
    .setDevKey("<YOUR_DEV_KEY>") // Your dev key
```

```client-android-kotlin
import io.appwrite.Client

val client = Client(context)
    .setEndpoint("https://cloud.appwrite.io/v1")
    .setProject("<YOUR_PROJECT_ID>")
    .setDevKey("<YOUR_DEV_KEY>") // Your dev key
```

```client-android-java
import io.appwrite.Client;

Client client = new Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<YOUR_PROJECT_ID>")
    .setDevKey("<YOUR_DEV_KEY>"); // Your dev key
```

If you need to replace your dev key, create a new key, update your app credentials and, once ready, delete your old key.

---

## Encryption

URL: 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

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
Database columns support encryption for text columns (varchar, text, mediumtext, and longtext). This feature is available on Pro plans and higher. When creating a text column in the UI, encryption can be enabled using a toggle option.
Columns are encrypted with AES-128 in Galois/Counter Mode (GCM).

**Querying encrypted columns**

Note that encrypted columns cannot be queried.

---

## GDPR

URL: 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

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**.

![Project settings screen](/images/docs/advanced/security/dpa.avif)

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

URL: 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.

#### Appwrite as a Business Associate

Appwrite serves as a business associate to customers that meet the definition of a [covered entity](https://www.hhs.gov/hipaa/for-professionals/covered-entities/index.html) under HIPAA, such as health plans and healthcare providers. A business associate performs certain functions or services that involve the use or disclosure of PHI on behalf of a covered entity. Covered entities may be required to enter into a Business Associate Agreement (BAA) with business associates to meet their HIPAA requirements.

For Enterprise customers subject to HIPAA and processing PHI within their applications, Appwrite will sign a BAA. To request Appwrite's BAA, please [contact us](/contact-us).

For Pro customers, the BAA is a click-through agreement that is not signed. An organization owner can enable it directly from the Appwrite Console: open your organization's **Settings** tab, find the **BAA** section, select **Enable BAA**, then review the agreement and select **Accept & Enable**. The BAA is a paid add-on billed to your organization and prorated for your current billing cycle. You can review the agreement on the [Business Associate Agreement](/legal/baa) page.

#### 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

URL: 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://<REGION>.cloud.appwrite.io/v1/<ENDPOINT>
```

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
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
You can add a [custom domain](/docs/products/network/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
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

URL: 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.

**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 guide](/docs/products/auth/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**.

![Multi-factor authentication settings](/images/docs/advanced/security/mfa.avif)

Toggle **Multi-factor authentication** to enable MFA for your account, then click **Add authentication factor**.

![Multi-factor authentication modal](/images/docs/advanced/security/mfa-modal.avif)

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

URL: 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.

**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.

---

## Penetration tests

URL: 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.

---

## Permissions

URL: https://appwrite.io/docs/advanced/security/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

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

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 integrations can be used for increased flexibility. When using a Server SDK in combination with the proper [API key scopes](/docs/partners/project/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

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

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

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

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, ID, TablesDB, Permission, Role } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

const tablesDB = new TablesDB(client);

let promise = tablesDB.createRow({
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    rowId: ID.unique(),
    data: {'actorName': 'Chris Evans', 'height': 183},
    permissions: [
        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

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, ID, TablesDB, Permission, Role } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

const tablesDB = new TablesDB(client);

let promise = tablesDB.createRow({
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    rowId: ID.unique(),
    data: {'actorName': 'Chris Evans', 'height': 183},
    permissions: [
        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

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

**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

##### Create a row for a user
When creating rows in your application, set row-level permissions to restrict access to only the creator:

```client-web
import { Client, ID, TablesDB, Permission, Role } from "appwrite";

const client = new Client()
    .setEndpoint('https://cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

const tablesDB = new TablesDB(client);

let promise = tablesDB.createRow({
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    rowId: ID.unique(),
    data: { 'title': 'My Private Row' },
    permissions: [
        Permission.read(Role.user('<USER_ID>')),    // Only this user can read
        Permission.update(Role.user('<USER_ID>')),  // Only this user can update
        Permission.delete(Role.user('<USER_ID>'))   // 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('<PROJECT_ID>');

    final tablesDB = TablesDB(client);

    try {
        final row = await tablesDB.createRow(
            databaseId: '<DATABASE_ID>',
            tableId: '<TABLE_ID>',
            data: { 'title': 'My Private Row' },
            permissions: [
                Permission.read(Role.user('<USER_ID>')),    // Only this user can read
                Permission.update(Role.user('<USER_ID>')),  // Only this user can update
                Permission.delete(Role.user('<USER_ID>'))   // 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("<PROJECT_ID>");

    let tablesDB = TablesDB(client);

    do {
        let row = try await tablesDB.createRow(
            databaseId: "<DATABASE_ID>",
            tableId: "<TABLE_ID>",
            data: ["title": "My Private Row"],
            permissions: [
                Permission.read(Role.user("<USER_ID>")),    // Only this user can read
                Permission.update(Role.user("<USER_ID>")),  // Only this user can update
                Permission.delete(Role.user("<USER_ID>"))   // 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("<PROJECT_ID>");

    val tablesDB = TablesDB(client);

    try {
        val row = tablesDB.createRow(
            databaseId = "<DATABASE_ID>",
            tableId = "<TABLE_ID>",
            data = mapOf("title" to "My Private Row"),
            permissions = listOf(
                Permission.read(Role.user("<USER_ID>")),    // Only this user can read
                Permission.update(Role.user("<USER_ID>")),  // Only this user can update
                Permission.delete(Role.user("<USER_ID>"))   // Only this user can delete
            )
        );
    } catch (e: AppwriteException) {
        Log.e("Appwrite", e.message);
    }
}
```

**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

---

## Rate-limits

URL: https://appwrite.io/docs/advanced/security/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

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

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/security/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

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
}
```

---

## Roles

URL: https://appwrite.io/docs/advanced/security/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.

**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).

#### 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
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
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
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 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 will soon be available on the Appwrite Console. Custom roles will be a Scale and Enterprise plans feature.

---

## SOC 2

URL: 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

URL: https://appwrite.io/docs/advanced/security/tls

Appwrite generates TLS certificates to ensure your API traffic is appropriately encrypted. The certificate authority used depends on your deployment type:

- **Self-hosted deployments** use [Let's Encrypt](https://letsencrypt.org/), an open source and not-for-profit certificate authority provided by the Internet Security Research Group (ISRG) that secures more than 363 million websites.
- **Appwrite Cloud** uses [Certainly](https://docs.fastly.com/products/certainly), Fastly's certificate authority, for Sites and Functions.

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/products/network/custom-domains) that you configure for your Appwrite projects.
- [Domains for Appwrite Functions](/docs/products/functions/domains), generated or user provided.
- [Domains for Appwrite Sites](/docs/products/sites/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.

### CAA records

If your domain has restrictive [CAA records](/docs/products/network/caa-records) in DNS, you must authorize the certificate authority Appwrite uses before a certificate can be issued. On Appwrite Cloud, add `certainly.com` to your CAA policy. Domains without any CAA records do not require this step.

[Learn more about CAA records >](/docs/products/network/caa-records)

---

## Self-hosting

URL: 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.

**Looking for the source?**

Appwrite is open source. Peek at the Docker files, services, and the rest of the stack in the [appwrite/appwrite](https://github.com/appwrite/appwrite) repository.

**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)

### 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

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)

**Enterprise self-hosting**

For compliance features and dedicated support, explore [enterprise self-hosting solutions](/contact-us/enterprise).

### 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

Choose the deployment method that best fits your needs:

#### One-click deployments

**Recommended:** Use these pre-configured marketplace apps for instant setup:

| &nbsp; | Provider | Installation Link |
| --- | --- | --- |
| | DigitalOcean | [Click to install](https://marketplace.digitalocean.com/apps/appwrite) |
| | Akamai Compute | [Click to install](https://www.linode.com/marketplace/apps/appwrite/appwrite/) |
| | AWS Marketplace | [Click to install](https://aws.amazon.com/marketplace/pp/prodview-2hiaeo2px4md6) |

#### Cloud platforms

For custom deployments on major cloud providers:

- [Amazon Web Services](/docs/advanced/self-hosting/platforms/aws): One-click AWS Marketplace deployment with custom configuration options.

- [DigitalOcean](/docs/advanced/self-hosting/platforms/digitalocean): Marketplace installation with simple Droplet configuration.

- [Google Cloud](/docs/advanced/self-hosting/platforms/google-cloud): Deploy using Cloud Run, Compute Engine, or other Google Cloud services.

- [Microsoft Azure](/docs/advanced/self-hosting/platforms/azure): Deploy using Container Apps, Virtual Machines, or other Azure services.

#### Platform-as-a-Service (PaaS)

Deploy Appwrite on modern PaaS platforms for simplified management:

- [Coolify](/docs/advanced/self-hosting/platforms/coolify): Open-source platform for easy self-hosting with one-click deployments.
- [Dokploy](/docs/advanced/self-hosting/platforms/dokploy): Open-source deployment platform with one-click Appwrite template deployment.

### Configuration

After deployment, configure Appwrite to enable additional features:

- [Email delivery](/docs/advanced/self-hosting/configuration/email): Set up SMTP providers for user verification, password recovery, and notifications.

- [SMS Delivery](/docs/advanced/self-hosting/configuration/sms): Configure SMS providers for phone authentication and two-factor authentication.

- [Storage Backends](/docs/advanced/self-hosting/configuration/storage): Connect external storage providers like AWS S3, Backblaze, or Wasabi.

- [Functions Runtime](/docs/advanced/self-hosting/configuration/functions): Enable serverless functions with custom runtimes and execution environments.

### Production readiness

Ensure your Appwrite deployment is production-ready:

#### Security considerations

- **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

- **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

- **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

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 [Dokploy](/docs/advanced/self-hosting/platforms/dokploy) for simplified management

---

## Databases

URL: https://appwrite.io/docs/advanced/self-hosting/configuration/databases

Appwrite supports PostgreSQL, MariaDB, and MongoDB as database backends. You select the database during [installation](/docs/advanced/self-hosting/installation) via the setup wizard and **cannot change it after installation**.

Regardless of which database you choose, the Appwrite API remains the same. Only the underlying storage engine differs.

**Default database**

PostgreSQL is the default database for new installations.

### CLI installation

If you prefer to skip the setup wizard, you can set the database directly using the `--database` flag:

**macOS and Linux**

```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:2.2.0 \
    --database=postgresql
```

**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:2.2.0 ^
    --database=postgresql
```

**Windows (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:2.2.0 `
    --database=postgresql
```

Accepted values are `postgresql`, `mariadb`, and `mongodb`.

### Supported databases

#### PostgreSQL

PostgreSQL is a relational SQL database and the default option for new Appwrite installations.

- **Docker image:** `appwrite/postgres:0.1.0`
- **Container name:** `appwrite-postgresql`
- **Default port:** `5432`

#### MariaDB

MariaDB is a relational SQL database. It was the default database in Appwrite versions prior to 1.9.0.

- **Docker image:** `mariadb:10.11`
- **Container name:** `appwrite-mariadb`
- **Default port:** `3306`

#### MongoDB

MongoDB is a document-based database and was the default in Appwrite 1.9. The installer configures it as a replica set (`rs0`) and places two helper files that the container needs next to `docker-compose.yml`: `mongo-init.js` and `mongo-entrypoint.sh`.

- **Docker image:** `mongo:8.2.5`
- **Container name:** `appwrite-mongodb`
- **Default port:** `27017`

### Environment variables

All database backends use the same environment variables. The values differ depending on which database you selected during installation.

| Variable | Description | PostgreSQL default | MariaDB default | MongoDB default |
|---|---|---|---|---|
| `_APP_DB_ADAPTER` | Database adapter type | `postgresql` | `mariadb` | `mongodb` |
| `_APP_DB_HOST` | Database server hostname | `postgresql` | `mariadb` | `mongodb` |
| `_APP_DB_PORT` | Database server port | `5432` | `3306` | `27017` |
| `_APP_DB_SCHEMA` | Database name | `appwrite` | `appwrite` | `appwrite` |
| `_APP_DB_USER` | Database user | `user` | `user` | `user` |
| `_APP_DB_PASS` | Database user password | `password` | `password` | `password` |
| `_APP_DB_ROOT_PASS` | Database root password | `rootsecretpassword` | `rootsecretpassword` | `rootsecretpassword` |

The installer writes only the selected database's service into your `docker-compose.yml`, so the other database containers never run.

**Change default passwords**

Always change the default database passwords before deploying to production. Update both the environment variables and the database credentials.

### Backups

For database backup procedures, see the [Backups](/docs/advanced/self-hosting/production/backups) guide.

---

## Email delivery

URL: 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.

**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.

### 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".

**Applying changes**

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.

```sh
docker compose up -d
```

You can verify if the changes have been successfully applied by running this command:

```sh
docker compose exec appwrite vars
```

### Debugging

If you are unable to send emails, there are several common issues to check. Follow these troubleshooting steps:

#### Check email worker logs

The first place to look for errors is the emails worker logs. The container name depends on your [worker topology](/docs/advanced/self-hosting/configuration/topologies).

**Combined (default)**

```sh
docker compose logs -f appwrite-worker
```

**Separate**

```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

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

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

Check if environment variables are properly set in the container:

**Combined (default)**

```sh
docker compose exec appwrite-worker vars
```

**Separate**

```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

URL: 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
| 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_CONFIG` | This variable allows you to report server errors to Sentry. This value is empty by default, set a DSN value `sentry://PROJECT_ID:SENTRY_API_KEY@SENTRY_HOST/` to enable it. **version >= 2.1.0** Sentry is the only supported provider; other DSN schemes are rejected at startup and disable reporting. The same DSN is used by the API, workers, CLI tasks, and the realtime server. |
| `_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. |
| `_APP_WORKER_MAX_COROUTINES` | Maximum number of jobs a queue worker processes concurrently. In the combined [worker topology](/docs/advanced/self-hosting/configuration/topologies), this sets the size of the coroutine pool shared by all queues. In the separate topology, it overrides the concurrency limit of the worker it is set on. The databases worker always processes one job at a time regardless of this setting. |

### 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
Appwrite supports MariaDB and MongoDB as database backends. The database is selected during [installation](/docs/advanced/self-hosting/installation). See [Databases](/docs/advanced/self-hosting/configuration/databases) for more details.

| Name | Description |
|------------------------|--------------------------------------------------------------------------------------------------|
| `_APP_DB_ADAPTER` | Database adapter type. Default value is: `mongodb`. Possible values: `mongodb`, `mariadb`. |
| `_APP_DB_HOST` | Database server host name address. Default value is: `mongodb` (or `mariadb` for MariaDB). |
| `_APP_DB_PORT` | Database server TCP port. Default value is: `27017` (or `3306` for MariaDB). |
| `_APP_DB_SCHEMA` | Database name. Default value is: `appwrite`. |
| `_APP_DB_USER` | Database server user name. Default value is: `user`. |
| `_APP_DB_PASS` | Database server user password. Default value is: `password`. |
| `_APP_DB_ROOT_PASS` | Database server root password. Default value is: `rootsecretpassword`. |

### 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

| Name | Description |
|---------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `_APP_SMS_PROVIDER` | **version >= 0.15.0** Provider used for delivering SMS for Phone authentication. Use the following format: `sms://<USER>:<SECRET>@<PROVIDER>`. Ensure `<USER>` and `<SECRET>` 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, or an alphanumeric sender ID where the provider supports it, used for sending out messages. A phone number must start with a leading '+' and have a maximum of 15 digits without spaces (+123456789). If using Twilio, this may also be a Messaging Service SID, starting with MG. |

### 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_AUTOGRAVITY_HOST` | **version >= 2.1.0** The host of the AutoGravity service (for example `http://appwrite-autogravity:8080`) used to detect the focal point when a file preview requests `gravity=auto`. Leave empty to disable automatic gravity; the other gravity values keep working. |
| `_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)
| **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_COMPUTE_BUILD_SIZE_LIMIT` | **version >= 1.7.0** The maximum size of a built function or site deployment in bytes. The default value is 2,000,000,000 (2GB), and the maximum value is 4,294,967,295 (4.2GB). Replaces `_APP_FUNCTIONS_BUILD_SIZE_LIMIT`, which is deprecated. |
| `_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)

| **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
| **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_SCHEDULES` | Schedules deletion interval (in seconds). |
| `_APP_MAINTENANCE_RETENTION_USAGE_TTL` | **version >= 2.2.0** Retention in days for raw and daily usage events stored in ClickHouse. Gauges have no TTL. The default value is `180`. Set to `0` to disable event TTLs. |

### 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
| **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
| **Name** | **Description** |
|----------------------------------------|-------------------------------------------------------------------------|
| `_APP_ASSISTANT_OPENAI_API_KEY` | **version >= 1.4.0** - OpenAI API key. You can find it in your OpenAI application settings. |

---

## Functions

URL: 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

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 3.11, .NET 6.0, and Java 18 runtimes.

```bash
_APP_FUNCTIONS_RUNTIMES=dart-3.11,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.

**Applying changes**

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.

```sh
docker compose up -d
```

You can verify if the changes have been successfully applied by running this command:

```sh
docker compose exec appwrite vars
```

### SSL certificates for function domains

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.
The container name depends on your [worker topology](/docs/advanced/self-hosting/configuration/topologies).

**Combined (default)**

```bash
docker compose logs appwrite-worker
```

**Separate**

```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
```

**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.

For other DNS providers, refer to [Traefik's DNS providers documentation](https://doc.traefik.io/traefik/https/acme/#providers).

---

## Sites

URL: 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

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.41 runtimes.

```bash
_APP_SITES_RUNTIMES=static-1,node-22,flutter-3.41
```

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.

**Applying changes**

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.

```sh
docker compose up -d
```

You can verify if the changes have been successfully applied by running this command:

```sh
docker compose exec appwrite vars
```

### Add an apex domain for sites

Appwrite allows you to add an apex domain to your instance's configuration that you can publicly expose your Site on.

**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.

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.

**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.

### SSL certificates for sites domains

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.
The container name depends on your [worker topology](/docs/advanced/self-hosting/configuration/topologies).

**Combined (default)**

```bash
docker compose logs appwrite-worker
```

**Separate**

```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
```

**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.

For other DNS providers, refer to [Traefik's DNS providers documentation](https://doc.traefik.io/traefik/https/acme/#providers).

---

## SMS delivery

URL: 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.

**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.

### 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.

| &nbsp; | SMS provider | Create account | Get credentials |
| --- | --- | --- | --- |
| | Twilio | [Website](https://www.twilio.com) | [Documentation](https://www.twilio.com/docs/iam/access-tokens#step-2-api-key) |
| | Textmagic | [Website](https://www.textmagic.com) | [Documentation](https://www.textmagic.com/docs/api/start/#How-to-obtain-the-API-credentials) |
| | Telesign | [Website](https://www.telesign.com) | [Documentation](https://support.telesign.com/s/article/Find-Customer-ID-and-API-Key) |
| | MSG91 | [Website](https://msg91.com) | [Documentation](https://msg91.com/help/where-can-i-find-my-authentication-key) |
| | Vonage | [Website](https://www.vonage.ca/) | [Documentation](https://developer.vonage.com/en/account/secret-management) |

### 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.

**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.

| Provider | \_APP\_SMS\_PROVIDER | \_APP\_SMS\_FROM |
| -------- | -------------------------------------------------- | ----------------------- |
| Twilio | `sms://<ACCOUNT SID>:<AUTH TOKEN>@twilio` | `<TWILIO PHONE NUMBER>` |
| Textmagic| `sms://<USERNAME>:<API KEY>@text-magic` | `<PHONE NUMBER OR SENDER ID>` |
| TeleSign | `sms://<CUSTOMER ID>:<API KEY>@telesign` | `<TELESIGN PHONE NUMBER>` |
| MSG91 | `sms://<SENDER ID>:<AUTH KEY>@msg91` | `<FLOW ID>` |
| Vonage | `sms://<API KEY>:<API SECRET>@vonage` | `<PHONE NUMBER OR BRAND NAME>` |

**Applying changes**

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.

```sh
docker compose up -d
```

You can verify if the changes have been successfully applied by running this command:

```sh
docker compose exec appwrite vars
```

---

## Storage

URL: 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
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.

[Learn more about storage environment variables](/docs/advanced/self-hosting/environment-variables)

### 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).

**Applying changes**

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.

```sh
docker compose up -d
```

You can verify if the changes have been successfully applied by running this command:

```sh
docker compose exec appwrite vars
```

---

## TLS Certificates

URL: 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

If you're still struggling with your certificates, check the certificates worker log. The container name depends on your [worker topology](/docs/advanced/self-hosting/configuration/topologies).

**Combined (default)**

```bash
docker compose logs appwrite-worker
```

**Separate**

```bash
docker compose logs appwrite-worker-certificates
```

### 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

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

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.

**Applying changes**

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.

```sh
docker compose up -d
```

You can verify if the changes have been successfully applied by running this command:

```sh
docker compose exec appwrite vars
```

---

## Worker topologies

URL: https://appwrite.io/docs/advanced/self-hosting/configuration/topologies

Appwrite uses queue workers and schedulers to process background work like sending emails, running builds, issuing TLS certificates, and executing functions. A topology defines how these run: either a single container that consumes every queue, or one container per queue.

Appwrite supports two topologies:

- **Combined** runs all workers in one container and all schedulers in another. This is the default and the recommended choice for most installations.
- **Separate** runs one container per queue and one container per scheduler, so you can scale and monitor each queue independently.

Both topologies process the same queues with the same behavior. The choice only affects how many containers your instance runs and how you scale them.

### Combined topology

The combined topology runs two containers alongside the rest of the Appwrite stack:

| Container | Role |
| --------- | ---- |
| `appwrite-worker` | Consumes every queue in a single process. Each queue gets its own pool of coroutines, so a busy queue doesn't block the others. |
| `appwrite-task-scheduler` | Runs the function, execution, and message schedulers in one process. |

This is what starts when you install Appwrite or bring up the stack with the default configuration:

```bash
docker compose up -d
```

The combined topology uses fewer containers and less memory, and gives you one place to read worker logs:

```bash
docker compose logs -f appwrite-worker
```

### Separate topology

The separate topology replaces `appwrite-worker` with one container per queue, and `appwrite-task-scheduler` with one container per scheduler:

| Workers | Schedulers |
| ------- | ---------- |
| `appwrite-worker-webhooks` | `appwrite-task-scheduler-functions` |
| `appwrite-worker-deletes` | `appwrite-task-scheduler-executions` |
| `appwrite-worker-databases` | `appwrite-task-scheduler-messages` |
| `appwrite-worker-builds` | |
| `appwrite-worker-jobs` | |
| `appwrite-worker-screenshots` | |
| `appwrite-worker-certificates` | |
| `appwrite-worker-executions` | |
| `appwrite-worker-functions` | |
| `appwrite-worker-mails` | |
| `appwrite-worker-notifications` | |
| `appwrite-worker-messaging` | |
| `appwrite-worker-migrations` | |
| `appwrite-worker-stats-usage` | |
| `appwrite-worker-stats-resources` | |
| `appwrite-task-stats-resources` | |

In Appwrite's `docker-compose.yml`, these services sit behind the `separate` Compose profile. To run the separate topology from a manual installation, include the override file and enable the profile:

```bash
docker compose -f docker-compose.yml -f docker-compose.separate.yml --profile separate up -d
```

The `docker-compose.separate.yml` override hides the combined containers so the two topologies don't run at the same time.

**Run one topology at a time**

Never run the combined and separate containers together. Both consume the same queues, so each job would race between two consumers.

The maintenance and interval tasks run as their own containers in both topologies.

### Choosing a topology

![Workers and schedulers selection in the setup wizard](/images/docs/advanced/self-hosting/installation/wizard-setup-advanced.avif)

Pick the combined topology unless you need to scale or isolate individual queues. The separate topology helps when:

- One queue, such as builds or executions, needs more replicas or dedicated resources than the rest.
- You want per-queue logs, metrics, and resource limits.
- You want to restart a misbehaving queue without touching the others.

You select the topology during installation:

- **Setup wizard**: in [Step 1](/docs/advanced/self-hosting/installation#wizard-setup), expand **Advanced settings** and choose an option under **Workers and schedulers**.
- **Install command**: pass `--topology=combined` or `--topology=separate` to the installer.

The installer writes a `docker-compose.yml` that only contains the services for the topology you selected. When you [upgrade](/docs/advanced/self-hosting/production/updates), Appwrite detects the topology of your existing installation from your Compose file and keeps it.

### Switching topologies

To switch an existing installation, run the [upgrade command](/docs/advanced/self-hosting/production/updates) with the `--topology` parameter from the directory that contains your `appwrite` folder:

```bash
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:<APPWRITE_VERSION> \
    --topology=separate
```

This rewrites your `docker-compose.yml` with the other topology's services and restarts the stack. Your data is not affected. The upgrade command also moves your installation to the version of the image you run. To switch topology without upgrading, use the version your installation is already on.

### Worker concurrency

Each queue has its own concurrency limit. The databases, mails, notifications, messaging, and migrations queues process one job at a time. The rest process up to 8 jobs concurrently.

The [`_APP_WORKER_MAX_COROUTINES`](/docs/advanced/self-hosting/configuration/environment-variables) environment variable adjusts these limits. In the combined worker it sets the size of the coroutine pool shared by all queues. In a separate topology worker it overrides that queue's own limit. The databases worker always processes one job at a time regardless of this setting.

---

## Version control

URL: https://appwrite.io/docs/advanced/self-hosting/configuration/version-control

Appwrite supports automatic deployments through Git integration. In order for Appwrite to access your repos, you must [create a GitHub app](https://docs.github.com/en/apps/creating-github-apps/about-creating-github-apps/about-creating-github-apps) to enable this integration. The GitHub app requires the following configurations.

For 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/).

#### GitHub App name

The GitHub App name will be displayed when connecting Appwrite to GitHub. In addition, this name will be transformed to a slug ("My GitHub App" will become "my-github-app") and appear in the URL when accessing your app in GitHub. This slug should be set as the `_APP_VCS_GITHUB_APP_NAME` environment variable in Appwrite.

#### Homepage URL

The homepage URL will appear when looking at the public page of your app. It can be any URL you'd like.

#### Callback URL

GitHub will use **callback URLs** to redirect users back to Appwrite. Set these callback URLs under **Identifying and authorizing users** in the same order as listed below.

| URLs |
| ----------------------------------------------------------------------------- |
| `https://<HOSTNAME_OR_IP>/v1/vcs/github/callback` |
| `https://<HOSTNAME_OR_IP>/v1/account/sessions/oauth2/callback/github/console` |

Also, check the **Request user authentication (OAuth) during installation** box.

#### Post installation

Check the **Redirect on update** box under the **Post installation** section.

#### Webhook

GitHub will notify Appwrite about events like new commits using webhooks. Under **Webhook**, you need to check the **Active** checkbox. You also need to set the **Webhook URL** as `https://<HOSTNAME_OR_IP>/v1/vcs/github/events`.

If you're running Appwrite on `localhost`, GitHub can't send requests to Appwrite through webhooks and automatic deployments won't work. You'll need to host Appwrite on a server or use a proxy like [ngrok](https://ngrok.com/) to make Appwrite accessible to GitHub.

#### Repository permissions

Configure these permissions under the **Repository permission** dropdown.

| 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 |

#### Account permissions

Configure these permissions under the **Account Permission** dropdown.

| Permission | Access |
| ------------- | --------- |
| Email address | Read-only |

#### Subscribe to events

Select these under the **Subscribe to events** dropdown.

| Events |
| ------------ |
| Pull request |
| Push |

#### Where can this GitHub App be installed?

Check the **Any account** box under **Where can this GitHub App be installed?** section. This is important to allow you to install the GitHub app on multiple Appwrite projects.

#### Environment variables

After creating your app, you'll have to configure 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](/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_DOMAIN_SITES` | This will be used for system generated [Site Domains](/docs/products/sites/domains). When a site domain is generated, it will be `[UNIQUE_ID].[_APP_DOMAIN_SITES]`. If `_APP_DOMAIN_SITES` is set to `example.com` for example, the generated domain for sites 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 the display name you'll see on GitHub and it will be visible in your GitHub app's URL. |
| `_APP_VCS_GITHUB_PRIVATE_KEY` | RSA private key from GitHub wrapped with double quotes and newlines replaced with `\n`. You can generate private keys from GitHub application settings. |
| `_APP_VCS_GITHUB_APP_ID` | GitHub application ID. You can find it in your GitHub application details. |
| `_APP_VCS_GITHUB_CLIENT_ID` | GitHub client ID. You can find it in your GitHub application details. |
| `_APP_VCS_GITHUB_CLIENT_SECRET` | GitHub client secret. You can generate secrets in your GitHub application settings. |
| `_APP_VCS_GITHUB_WEBHOOK_SECRET` | GitHub webhook secret. You can configure it in your GitHub application settings under webhook section. |

For example, see below.

```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
```

[Learn more about environment variables](/docs/advanced/self-hosting/environment-variables)

#### Update existing GitHub apps

There are additional steps if you're updating permissions in existing GitHub apps.
Every time you update your GitHub app's permissions, GitHub will prompt you to review the changes.
You will receive an email and you'll find a prompt under **Settings** > **Integrations** > **Applications** > **Installed GitHub Apps** > find your GitHub app > **Configure**.
You need to accept the new permissions so they're applied to your app.

#### 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
```

**Applying changes**

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.

```sh
docker compose up -d
```

You can verify if the changes have been successfully applied by running this command:

```sh
docker compose exec appwrite vars
```

---

## Installation

URL: 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

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

The easiest way to install Appwrite is using our Docker installer. The installer launches a web-based setup wizard that guides you through the entire process.

Before running the installation command, ensure you have [Docker CLI](https://www.docker.com/products/docker-desktop) installed on your host machine.

**Firewall configuration**

The installation wizard runs on port **20080**. If you are installing on a remote server, ensure that port 20080 is open in your firewall or security group settings before proceeding. You can close this port after installation is complete.

#### Installation commands

**macOS and Linux**

Run the following command in your terminal:

```bash
docker run -it --rm \
    --publish 20080:20080 \
    --volume /var/run/docker.sock:/var/run/docker.sock \
    --volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \
    --entrypoint="install" \
    appwrite/appwrite:2.2.0
```

**Windows (CMD)**

```cmd
docker run -it --rm ^
    --publish 20080:20080 ^
    --volume //var/run/docker.sock:/var/run/docker.sock ^
    --volume "%cd%"/appwrite:/usr/src/code/appwrite:rw ^
    --entrypoint="install" ^
    appwrite/appwrite:2.2.0
```

**Windows (PowerShell)**

```powershell
docker run -it --rm `
    --publish 20080:20080 `
    --volume /var/run/docker.sock:/var/run/docker.sock `
    --volume ${pwd}/appwrite:/usr/src/code/appwrite:rw `
    --entrypoint="install" `
    appwrite/appwrite:2.2.0
```

Once the command is running, open your browser and navigate to `http://localhost:20080` to access the setup wizard.

### Setup wizard

The setup wizard walks you through four steps to configure your Appwrite instance.

#### Step 1: Setup your app

Configure your Appwrite instance's basic settings.

![Setup your app](/images/docs/advanced/self-hosting/installation/wizard-setup-app.avif)

- **Hostname** - The domain or IP address where your Appwrite instance will be accessible.
- **Use HTTPS** - Turn this on when your Appwrite domain uses HTTPS, including behind a reverse proxy.
- **Database** - Choose [PostgreSQL, MariaDB, or MongoDB](/docs/advanced/self-hosting/configuration/databases) as your database backend. PostgreSQL is selected by default.
- **Advanced settings** - Optionally configure the [worker topology](/docs/advanced/self-hosting/configuration/topologies) under **Workers and schedulers**, HTTP and HTTPS ports, SSL certificate email, and an OpenAI API key for the [Appwrite Assistant](/docs/tooling/assistant).

#### Step 2: Secure your app

A secret API key is automatically generated for your instance. This key is used to encrypt sensitive data.

![Secure your app](/images/docs/advanced/self-hosting/installation/wizard-secure.avif)

**Save your key**

You won't be able to see this key again after proceeding. Copy it somewhere safe before continuing.

You can use the **Copy** button to copy the key or **Regenerate** to create a new one.

#### Step 3: Create your account

Set up the email and password for your Appwrite account. You'll use these credentials to sign in to the Appwrite Console after installation.

![Create your account](/images/docs/advanced/self-hosting/installation/wizard-account.avif)

#### Step 4: Review your setup

Review all your configuration settings. If anything looks wrong, use the **Back** button to make changes. When you're ready, click **Install** to begin the installation.

![Review your setup](/images/docs/advanced/self-hosting/installation/wizard-review.avif)

### Manual installation

For advanced users who prefer manual setup, you can install Appwrite using Docker Compose directly.

#### Generate configuration files

#### Install Appwrite

1. Create a directory named `appwrite` and place the generated files inside. Every installation needs `docker-compose.yml` and `.env`; MongoDB installations also need `mongo-init.js` and `mongo-entrypoint.sh`
2. Edit the `.env` file to customize your installation. At minimum, update `_APP_OPENSSL_KEY_V1` and `_APP_EXECUTOR_SECRET` with unique secret values
3. Start the Appwrite stack:

```bash
docker compose up -d --remove-orphans
```

**Worker topologies**

By default, Appwrite runs all queue workers in a single container. To run one container per queue instead, see [Worker topologies](/docs/advanced/self-hosting/configuration/topologies).

### Post-installation

After installation completes:

1. **Access the Console** - Navigate to your machine's hostname or IP address in your browser
2. **Create your first project** - Set up your development environment

**Startup time**

On non-Linux hosts, the server might take a few minutes to start after installation completes. This is normal behavior.

#### Organizations

A self-hosted instance allows one organization to be created. The Console creates it, along with an initial project, when the first user signs in. The limit applies to the instance, not to each account.

Invite other users to the organization to share its existing projects. Creating an account alone does not grant organization or project access, and signing up does not create a separate project for each user. If you allow additional accounts to register with `_APP_CONSOLE_WHITELIST_ROOT=disabled`, they still need to accept an organization invitation.

Existing organizations from an upgrade are preserved; they are not merged or deleted. Accounts belonging to several organizations can still switch between them and transfer projects. The Console hides the organization switcher when an account belongs to only one organization.

Organizations use console teams, but this restriction does not apply to teams inside application projects. You can continue creating those teams through the API and SDKs.

#### SDK version compatibility

The tables below map each released self-hosted Appwrite version to the SDK versions built against it. Use this to pin your SDK to a version known to work with your server.

The Appwrite CLI is not listed. One CLI build works against a range of server versions.

##### Client SDKs

| Appwrite | Web | Flutter | React Native | Apple | Android |
| --- | --- | --- | --- | --- | --- |
| 1.7.0 | 18.0.0 | 16.0.0 | 0.9.0 | 10.0.0 | 8.0.0 |
| 1.7.1 to 1.7.3 | 18.0.0 | 16.0.0 | 0.9.0 | 10.0.0 | 8.0.0 |
| 1.7.4 | 18.1.1 | 17.0.0 | 0.10.0 | 10.0.0 | 8.0.0 |
| 1.7.5 | 18.1.1 | 17.0.0 | 0.10.0 | 10.0.0 | 8.0.0 |
| 1.8.0 | 21.4.0 | 20.3.0 | 0.18.0 | 13.3.0 | 11.3.0 |
| 1.8.1 | 23.0.0 | 22.0.0 | 0.25.0 | 15.0.0 | 13.0.0 |
| 1.9.0 | 24.1.1 | 23.0.0 | 0.27.1 | 16.0.0 | 14.1.0 |
| 1.9.5 | 26.1.0 | 25.2.0 | 0.33.0 | 18.2.0 | 25.2.0 |
| 1.9.6 | 26.2.0 | 25.3.0 | 0.34.0 | 18.3.0 | 26.0.0 |
| 2.0.0 | 27.0.0 | 26.2.0 | 0.35.0 | 19.0.0 | 27.2.0 |
| 2.1.0 | 27.0.0 | 26.2.0 | 0.35.0 | 19.0.0 | 27.2.0 |
| 2.2.0 | 27.0.0 | 26.2.0 | 0.35.0 | 19.0.0 | 27.2.0 |

##### Server SDKs

| Appwrite | Node.js | Python | PHP | Dart | Ruby | .NET | Go | Swift | Kotlin | CLI |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| 1.7.0 | 17.0.0 | 11.0.0 | 15.0.0 | 16.0.0 | 16.0.0 | 0.13.0 | v0.7.0 | 10.0.0 | 9.0.0 | 6.2.3 |
| 1.7.1 to 1.7.3 | 17.0.0 | 11.0.0 | 15.0.0 | 16.0.0 | 16.0.0 | 0.13.0 | v0.7.0 | 10.0.0 | 9.0.0 | 6.2.3 |
| 1.7.4 | 17.1.0 | 11.0.0 | 15.0.0 | 16.1.0 | 16.0.0 | 0.13.0 | v0.7.0 | 10.0.0 | 9.0.0 | 8.0.0 |
| 1.7.5 | 17.1.0 | 11.0.0 | 15.0.0 | 16.1.0 | 16.0.0 | 0.13.0 | v0.7.0 | 10.0.0 | 9.0.0 | 8.0.0 |
| 1.8.0 | 20.2.1 | 13.4.1 | 17.5.0 | 19.3.0 | 19.3.0 | 0.22.0 | v0.13.1 | 13.2.2 | 12.3.0 | 12.0.1 |
| 1.8.1 | 22.1.3 | 16.0.0 | 20.2.1 | 21.3.0 | 21.1.0 | 1.0.0 | v1.0.0 | 15.2.0 | 14.1.0 | 15.0.0 |
| 1.9.0 | 23.1.0 | 17.0.0 | 21.0.0 | 22.0.0 | 22.0.0 | 2.0.0 | v2.0.0 | 16.0.0 | 15.0.0 | 17.4.0 |
| 1.9.5 | 26.2.0 | 21.0.0 | 26.1.0 | 25.1.0 | 25.1.0 | 5.1.0 | v5.1.0 | 19.1.0 | 18.1.0 | 22.3.0 |
| 1.9.6 | 27.0.0 | 22.1.0 | 27.0.0 | 26.0.0 | 26.0.0 | 6.0.0 | v6.0.0 | 20.0.0 | 19.0.0 | 23.0.0 |
| 2.0.0 | 29.0.0 | 24.0.0 | 30.0.0 | 29.0.0 | 28.0.0 | 8.0.0 | v7.2.0 | 22.0.0 | 22.0.0 | 27.3.0 |
| 2.1.0 | 29.0.0 | 24.0.0 | 30.0.0 | 29.0.0 | 28.0.0 | 8.0.0 | v7.3.0 | 22.0.0 | 22.0.0 | 27.3.0 |
| 2.2.0 | 29.0.0 | 24.0.0 | 30.0.0 | 29.0.0 | 28.0.0 | 8.0.0 | v7.3.0 | 22.0.0 | 22.0.0 | 27.3.0 |

### Managing your installation

#### Stop Appwrite

To stop your Appwrite containers:

```bash
docker compose stop
```

#### Restart Appwrite

To restart your Appwrite containers:

```bash
docker compose start
```

#### Uninstall Appwrite

To completely remove Appwrite and all its data:

```bash
docker compose down -v
```

**Data loss warning**

The uninstall command will permanently delete all your Appwrite data. Make sure to backup any important information before running this command.

### 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

[Configure databases](/docs/advanced/self-hosting/configuration/databases) - Learn more about MongoDB and MariaDB configuration

[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

---

## MCP server

URL: https://appwrite.io/docs/advanced/self-hosting/mcp

The [hosted Appwrite MCP server](/docs/tooling/ai/mcp-servers) authenticates against Appwrite Cloud. If you are running a self-hosted Appwrite instance, use the local MCP server instead. It runs on your machine over the stdio transport and connects to your instance with an API key, allowing LLMs and code-generation tools to 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 local 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

#### Appwrite API key

Before launching the MCP server, create an **API key** with the necessary scopes enabled in your Appwrite project.

![Appwrite API key](/images/docs/mcp/appwrite/appwrite-api-secret.avif)

Ensure you save the **API key** along with the **project ID** 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:

**Linux and MacOS**

```bash
curl -LsSf https://astral.sh/uv/install.sh | sh
```

**Windows**

```powershell
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
``` 

You can verify the installation by running the following command in your terminal:

```bash
uv
```

### Installation

Add the local MCP server to your AI tool's MCP configuration:

```json
{
  "mcpServers": {
    "appwrite": {
      "command": "uvx",
      "args": [
        "mcp-server-appwrite"
      ],
      "env": {
        "APPWRITE_PROJECT_ID": "your-project-id",
        "APPWRITE_API_KEY": "your-api-key",
        "APPWRITE_ENDPOINT": "https://your-appwrite-domain/v1"
      }
    }
  }
}
```

Replace `your-project-id` and `your-api-key` with the values from your project, and point `APPWRITE_ENDPOINT` to your instance's API endpoint.

#### 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 |
| --- | --- |
| `--tablesdb` | 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 |

**Enable only the MCP tools you need**

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.

### 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
```

#### 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
```

#### 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
```

---

## AWS deployment

URL: https://appwrite.io/docs/advanced/self-hosting/platforms/aws

Deploy Appwrite on AWS using the pre-configured Marketplace app.

### One-click installation

#### 1. 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**

#### 2. 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**

#### 3. 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

### 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

For production deployments, optimization, and advanced AWS configuration, see the [production deployment guide](/docs/advanced/self-hosting/production).

### 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

URL: https://appwrite.io/docs/advanced/self-hosting/platforms/azure

Deploy Appwrite on Microsoft Azure using Virtual Machines.

### Virtual Machines deployment

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

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

For production deployments, optimization, and advanced configuration, see the [production deployment guide](/docs/advanced/self-hosting/production).

### 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

URL: 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

Before starting, ensure your server meets the [minimum requirements](/docs/advanced/self-hosting#system-requirements) for hosting Appwrite with Coolify.

### 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

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=tls
_APP_SMTP_USERNAME=username
```

**GitHub Integration**
To connect a GitHub App so Appwrite can build and deploy functions and sites from your GitHub repositories, 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

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

URL: https://appwrite.io/docs/advanced/self-hosting/platforms/digitalocean

Deploy Appwrite on DigitalOcean using the pre-configured Marketplace app.

### One-click installation

#### 1. 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**

#### 2. 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

### 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

For production deployments, optimization, and advanced configuration, see the [production deployment guide](/docs/advanced/self-hosting/production).

### 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

---

## Dokploy

URL: https://appwrite.io/docs/advanced/self-hosting/platforms/dokploy

Dokploy is an open-source, self-hosted deployment platform that simplifies application management through an intuitive dashboard and one-click template deployments. Appwrite is available as a template in Dokploy's template catalog, letting you deploy the complete Appwrite stack, including the database, workers, and function executor, in a few clicks. This guide walks you through setting up Appwrite on your Dokploy instance and provides necessary configuration and troubleshooting tips.

### Prerequisites

Before starting, ensure your server meets the [minimum requirements](/docs/advanced/self-hosting#system-requirements) for hosting Appwrite with Dokploy.

### Installation

Install Dokploy on your server using the command below:

```bash
curl -sSL https://dokploy.com/install.sh | sh
```

Once the installation is complete, open the Dokploy dashboard in your web browser at `http://your-server-ip:3000`.

1. Sign up for a new account.
2. Click the **Create Project** button to start a new project.
3. Inside the project, click **Create Service** and select **Template**.
4. Search for and select **Appwrite** from the template catalog.
5. Click **Create**, then open the new service and click **Deploy**.

Once the deployment finishes, the **Containers** tab lists every Appwrite service running on your server.

![Appwrite services running in Dokploy](/images/docs/advanced/self-hosting/dokploy/deployed-service.avif)

The template generates secure random secrets and pre-fills the recommended settings, but you can customize them in the **Environment** tab. After deployment completes, access your Appwrite console by opening the main domain from the **Domains** tab and navigating to `/console`.

### Domains and HTTPS

The template configures three domains for your Appwrite instance: the main API/console domain, a `sites.` subdomain, and a `functions.` subdomain. To use your own domain:

1. Point DNS `A` records for your domain and the `sites.` and `functions.` subdomains to your server.
2. Add wildcard `A` records for `*.sites.your-domain` and `*.functions.your-domain`, which Appwrite uses for site previews and function execution URLs.
3. Update the hosts in the service's **Domains** tab and the `_APP_DOMAIN`, `_APP_CONSOLE_DOMAIN`, `_APP_DOMAIN_SITES`, and `_APP_DOMAIN_FUNCTIONS` environment variables to match.
4. For the main domain entries (API, console, and realtime), enable **HTTPS** with the **Let's Encrypt** certificate provider, then redeploy. These are single hosts, so Dokploy issues certificates for them automatically.
5. The two wildcard domains (`*.sites.your-domain` and `*.functions.your-domain`) need a wildcard certificate you provide — see below.

**Wildcard certificates**
Dokploy's built-in Let's Encrypt uses the HTTP-01 challenge, which cannot issue wildcard certificates, so the `*.sites` and `*.functions` domains (used for site previews and function execution URLs) need a certificate you supply:

1. Issue a wildcard certificate for `*.sites.your-domain` and `*.functions.your-domain` with a **DNS-01** client such as [lego](https://go-acme.github.io/lego/), acme.sh, or certbot, using your DNS provider's API token. DNS-01 is required — HTTP-01 cannot validate wildcards.
2. Add the certificate under **Dokploy → Settings → Certificates**.
3. In the **Domains** tab, set each wildcard domain's certificate provider to **None** (keep HTTPS enabled). This is the step most people miss: if it stays on **Let's Encrypt**, Traefik keeps retrying an impossible wildcard issuance and never serves the certificate you uploaded.

Traefik then serves your uploaded certificate for matching subdomains via SNI.

**Simpler alternative: per-domain certificates**
If you only need HTTPS on a handful of specific site or function domains — for example a production site on `app.sites.your-domain` or a custom domain — you can skip the wildcard certificate entirely. HTTP-01 can issue a certificate for a specific hostname (just not a wildcard), so:

1. In the service's **Domains** tab, add the exact domain (not a wildcard) with **Port 80**, **HTTPS** enabled, and the **Let's Encrypt** certificate provider.
2. Redeploy. Dokploy issues a standard certificate for that host, the same way it does for your main domain.

The domain must resolve to your server (the wildcard `A` record already handles this) and be publicly reachable on port 80 for the challenge. This is easier than managing a wildcard certificate, but you repeat it for each domain and it does not cover Appwrite's auto-generated preview subdomains — use the wildcard certificate above if you need every subdomain covered automatically.

**Using Cloudflare**

Cloudflare's free Universal SSL only covers your apex and first-level wildcard (`*.your-domain`), not second-level wildcards like `*.sites.your-domain`. If these subdomains are proxied (orange cloud), a matching edge certificate requires Cloudflare Advanced Certificate Manager. To avoid that cost, set the `*.sites` and `*.functions` records to **DNS only** (grey cloud) and rely on the Let's Encrypt wildcard certificate above. A Cloudflare **Origin** certificate will not work here — it is only trusted behind Cloudflare's proxy, never by browsers directly.

### Configuration

Dokploy stores the template's environment variables in the service's **Environment** tab. You can modify these variables and redeploy the service to apply the changes. 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=tls
_APP_SMTP_USERNAME=username
```

**GitHub Integration**
To connect a GitHub App so Appwrite can build and deploy functions and sites from your GitHub repositories, 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

1. **Sites or function domains return 404**
   Requests to `<id>.sites.your-domain` require wildcard DNS records (`*.sites.your-domain` and `*.functions.your-domain`) pointing to your server. Verify the records exist and that the `_APP_DOMAIN_SITES` and `_APP_DOMAIN_FUNCTIONS` environment variables match your configured domains.

2. **Browser shows a certificate warning on site subdomains**
   Generated subdomains need a wildcard certificate, which Dokploy cannot issue automatically. Follow the steps in [Domains and HTTPS](#domains-https) to add one under **Dokploy → Settings → Certificates**.

3. **Only deploy one Appwrite instance per server**
   The Appwrite template runs its function executor and runtime containers on the shared Docker network, so multiple Appwrite deployments on the same Dokploy server can conflict. Use one Appwrite service per server.

---

## Google Cloud deployment

URL: https://appwrite.io/docs/advanced/self-hosting/platforms/google-cloud

Deploy Appwrite on Google Cloud Platform using Compute Engine virtual machines.

### Compute Engine deployment

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

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

For production deployments, optimization, and advanced configuration, see the [production deployment guide](/docs/advanced/self-hosting/production).

### 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

URL: 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

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

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

[Start with scaling configuration](/docs/advanced/self-hosting/production/scaling)

---

## Backups

URL: https://appwrite.io/docs/advanced/self-hosting/production/backups

**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.

Self-hosted Appwrite requires manual backup procedures to protect your data.

### What to back up

Your Appwrite installation has several components that need backing up:

1. **Database** - User data, rows, 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

Appwrite supports MariaDB and MongoDB as database backends. Use the backup method that matches your installation.

#### MariaDB backups

Use `mysqldump` for MariaDB 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
```

For large databases, consider `mariabackup` for physical backups.

#### MongoDB backups

Use `mongodump` for MongoDB installations:

```bash
### Create database backup
docker compose exec mongodb sh -c 'exec mongodump --username=root --password="$MONGO_INITDB_ROOT_PASSWORD" --authenticationDatabase=admin --archive' > ./dump.archive

### Restore (fresh installation only)
docker compose exec -T mongodb sh -c 'exec mongorestore --username=root --password="$MONGO_INITDB_ROOT_PASSWORD" --authenticationDatabase=admin --archive' < dump.archive
```

**Fresh installation only**

Only restore to fresh Appwrite installations to avoid data corruption.

### Storage volume backups

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 (MariaDB installations)
- `appwrite-mongodb` - Database files (MongoDB installations)

#### 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
```

**External storage**

For S3/GCS/Azure storage, use your provider's native backup tools.

### Environment variables

Back up your `.env` file containing configuration and secrets:

```bash
cp .env .env.backup.$(date +"%Y%m%d")
```

**Critical variable**

The `_APP_OPENSSL_KEY_V1` encrypts your data. Copy this exact value when restoring, or encrypted data becomes inaccessible.

Store `.env` backups securely due to sensitive data.

### 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

#### 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

URL: 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

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

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

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.

[Learn more about Appwrite status codes](/docs/apis/response-codes)

### 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
```

**Applying changes**

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.

```sh
docker compose up -d
```

You can verify if the changes have been successfully applied by running this command:

```sh
docker compose exec appwrite vars
```

---

## Email delivery

URL: 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

URL: 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

URL: 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

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

[Learn more about how rate limits work](/docs/advanced/security/rate-limits)

### Abuse log retention

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

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
```

[Learn more about environment variables](/docs/advanced/self-hosting/configuration/environment-variables)

---

## Scaling

URL: 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.

Which worker containers your instance runs depends on its [worker topology](/docs/advanced/self-hosting/configuration/topologies). The default combined topology runs all queues in a single `appwrite-worker` container that you can replicate. The separate topology runs one container per queue, which lets you scale each queue independently.

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 database (MariaDB or MongoDB, depending on your installation) 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

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=<number_of_workers>
```

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

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: '<number_of_files>'
      max-size: '<size_limit>'
```

#### Redis memory management
You may want to set memory limits for Redis to prevent out-of-memory issues:

### Monitoring considerations

- **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

URL: 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

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.

**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.

### 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.

[Learn more about environment variables](/docs/advanced/self-hosting/environment-variables)

### 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.

**Applying changes**

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.

```sh
docker compose up -d
```

You can verify if the changes have been successfully applied by running this command:

```sh
docker compose exec appwrite vars
```

---

## Updates and migrations

URL: 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

### Upgrading to 2.2.0

Appwrite 2.2.0 upgrades from `2.1.0`. Run the upgrade command with the `2.2.0` tag, then run the migration. If your instance is on 2.0.0, upgrade to `2.1.0` first.

```sh
docker run -it --rm \
    --publish 20080:20080 \
    --volume /var/run/docker.sock:/var/run/docker.sock \
    --volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \
    --entrypoint="upgrade" \
    appwrite/appwrite:2.2.0
```

Then run the migration from the `appwrite` directory.

```sh
cd appwrite/
docker compose exec appwrite migrate
```

Function and site executions are stored in ClickHouse only from 2.2.0. The upgrade stops writing executions to the project database, and `_APP_EXECUTIONS_DUAL_WRITE` is removed. Remove it from your `.env` if you set it. `_APP_MAINTENANCE_RETENTION_USAGE_HOURLY` is removed as well, because hourly usage metrics are stored in ClickHouse and are governed by `_APP_MAINTENANCE_RETENTION_USAGE_TTL`. See [environment variables](/docs/advanced/self-hosting/configuration/environment-variables).

### Upgrading to 2.1.0

Appwrite 2.1.0 upgrades from `2.0.0`. Run the upgrade command with the `2.1.0` tag, then run the migration. If your instance is still on 1.9.x, upgrade to `1.9.6`, then `2.0.0`, then `2.1.0`.

```sh
docker run -it --rm \
    --publish 20080:20080 \
    --volume /var/run/docker.sock:/var/run/docker.sock \
    --volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \
    --entrypoint="upgrade" \
    appwrite/appwrite:2.1.0
```

Then run the migration from the `appwrite` directory.

```sh
cd appwrite/
docker compose exec appwrite migrate
```

The upgrade adds the `appwrite-autogravity` container and leaves `_APP_AUTOGRAVITY_HOST` empty in your `.env`. Set it to `http://appwrite-autogravity:8080` to turn on [AutoGravity](/docs/products/storage/images) for file previews.

Error reporting is Sentry-only from 2.1.0. `_APP_LOGGING_PROVIDER` is removed, and `_APP_LOGGING_CONFIG` accepts only a `sentry://` DSN. If your `.env` holds a Raygun, AppSignal, or LogOwl DSN, Appwrite rejects it at startup and turns error reporting off, so replace it with a Sentry DSN or leave the variable empty. See [environment variables](/docs/advanced/self-hosting/configuration/environment-variables).

### Upgrading to 2.0.0

Appwrite 2.0.0 requires a migration. Upgrade to `1.9.6` first, then to `2.0.0`.

```sh
docker run -it --rm \
    --publish 20080:20080 \
    --volume /var/run/docker.sock:/var/run/docker.sock \
    --volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \
    --entrypoint="upgrade" \
    appwrite/appwrite:2.0.0
```

Then run the migration from the `appwrite` directory.

```sh
cd appwrite/
docker compose exec appwrite migrate
```

Your existing database stays as it is. The database you installed with, whether MariaDB, MongoDB, or PostgreSQL, is the database you keep. PostgreSQL is the default for new installations only, and upgrading does not move your data onto it.

The upgrade also keeps the [worker topology](/docs/advanced/self-hosting/configuration/topologies) of your existing installation. Appwrite reads the topology from your Compose file and writes the new one to match. To change the topology at the same time, pass `--topology=combined` or `--topology=separate` to the upgrade command.

### Installing the 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.

**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.

**Choose an image tag**

Replace `<APPWRITE_VERSION>` below with the specific Appwrite image tag you intend to run (for example, `2.2.0`). Avoid using `latest` in production.

#### Unix

```sh
docker run -it --rm \
    --publish 20080:20080 \
    --volume /var/run/docker.sock:/var/run/docker.sock \
    --volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \
    --entrypoint="upgrade" \
    appwrite/appwrite:<APPWRITE_VERSION>
```

#### CMD

```cmd
docker run -it --rm ^
    --publish 20080:20080 ^
    --volume //var/run/docker.sock:/var/run/docker.sock ^
    --volume "%cd%"/appwrite:/usr/src/code/appwrite:rw ^
    --entrypoint="upgrade" ^
    appwrite/appwrite:<APPWRITE_VERSION>
```

#### PowerShell

```powershell
docker run -it --rm `
    --publish 20080:20080 `
    --volume /var/run/docker.sock:/var/run/docker.sock `
    --volume ${pwd}/appwrite:/usr/src/code/appwrite:rw `
    --entrypoint="upgrade" `
    appwrite/appwrite:<APPWRITE_VERSION>
```

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

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!

---

## APIs

URL: https://appwrite.io/docs/apis

Every Appwrite service is available through a consistent set of APIs. You can call them directly over [REST](/docs/apis/rest) or [GraphQL](/docs/apis/graphql), subscribe to changes in [Realtime](/docs/apis/realtime), or react to changes using [events](/docs/apis/events) and [webhooks](/docs/apis/webhooks).

Most applications don't call these APIs by hand. Instead, use one of the official [SDKs](/docs/sdks), which wrap every endpoint for your language and platform, and browse the [API reference](/docs/references) for the full list of services and methods. The pages below describe the underlying protocols and conventions for when you need to integrate directly.

### Protocols

Choose the protocol that fits your use case. REST and GraphQL cover the same endpoints, while Realtime delivers updates as they happen.

- [REST](/docs/apis/rest): Access every Appwrite endpoint over HTTP without an SDK, including authentication, file uploads, queries, and permissions.
- [GraphQL](/docs/apis/graphql): Query and mutate your data through a single GraphQL endpoint.
- [Realtime](/docs/apis/realtime): Subscribe to channels and receive updates over WebSockets the moment your data changes.

### Event-driven workflows

React to project changes with events emitted by Appwrite and webhooks delivered to your own endpoints.

- [Events](/docs/apis/events): The full list of events Appwrite emits so you can react to changes across your project.
- [Webhooks](/docs/apis/webhooks): Trigger external workflows by delivering events to your own HTTP endpoints.

### API responses

Understand the responses Appwrite returns and handle errors so your application can recover gracefully.

- [Response codes](/docs/apis/response-codes): HTTP status codes, error types, and patterns for handling errors gracefully in your application.

---

## Events

URL: https://appwrite.io/docs/apis/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/apis/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, Sites, and Authentication services below.

**Authentication**

- Name
- Description

---

- `teams.*`
- This event triggers on any teams event.
  Returns [Team Object](/docs/references/cloud/models/team)

---

- `teams.*.create`
- This event triggers when a team is created.
  Returns [Team Object](/docs/references/cloud/models/team)

---

- `teams.*.delete`
- This event triggers when a team is deleted.
  Returns [Team Object](/docs/references/cloud/models/team)

---

- `teams.*.memberships.*`
- This event triggers on any team memberships event.
  Returns [Membership Object](/docs/references/cloud/models/membership)

---

- `teams.*.memberships.*.create`
- This event triggers when a membership is created.
  Returns [Membership Object](/docs/references/cloud/models/membership)

---

- `teams.*.memberships.*.delete`
- This event triggers when a membership is deleted.
  Returns [Membership Object](/docs/references/cloud/models/membership)

---

- `teams.*.memberships.*.update`
- This event triggers when a membership is updated.
  Returns [Membership Object](/docs/references/cloud/models/membership)

---

- `teams.*.memberships.*.update.status`
- This event triggers when a team memberships status is updated.
  Returns [Membership Object](/docs/references/cloud/models/membership)

---

- `teams.*.update`
- This event triggers when a team is updated.
  Returns [Team Object](/docs/references/cloud/models/team)

---

- `teams.*.update.prefs`
- This event triggers when a team's preferences are updated.
  Returns [Team Object](/docs/references/cloud/models/team)

---

- `users.*`
- This event triggers on any user's event.
  Returns [User Object](/docs/references/cloud/models/user)

---

- `users.*.create`
- This event triggers when a user is created.
  Returns [User Object](/docs/references/cloud/models/user)

---

- `users.*.delete`
- This event triggers when a user is deleted.
  Returns [User Object](/docs/references/cloud/models/user)

---

- `users.*.recovery.*`
- This event triggers on any user's recovery token event.
  Returns [Token Object](/docs/references/cloud/models/token)

---

- `users.*.recovery.*.create`
- This event triggers when a recovery token for a user is created.
  Returns [Token Object](/docs/references/cloud/models/token)

---

- `users.*.recovery.*.update`
- This event triggers when a recovery token for a user is validated.
  Returns [Token Object](/docs/references/cloud/models/token)

---

- `users.*.sessions.*`
- This event triggers on any user's sessions event.
  Returns [Session Object](/docs/references/cloud/models/session)

---

- `users.*.sessions.*.create`
- This event triggers when a session for a user is created.
  Returns [Session Object](/docs/references/cloud/models/session)

---

- `users.*.sessions.*.delete`
- This event triggers when a session for a user is deleted.
  Returns [Session Object](/docs/references/cloud/models/session)

---

- `users.*.update`
- This event triggers when a user is updated.
  Returns [User Object](/docs/references/cloud/models/user)

---

- `users.*.update.email`
- This event triggers when a user's email address is updated.
  Returns [User Object](/docs/references/cloud/models/user)

---

- `users.*.update.name`
- This event triggers when a user's name is updated.
  Returns [User Object](/docs/references/cloud/models/user)

---

- `users.*.update.password`
- This event triggers when a user's password is updated.
  Returns [User Object](/docs/references/cloud/models/user)

---

- `users.*.update.prefs`
- This event triggers when a user's preferences is updated.
  Returns [User Object](/docs/references/cloud/models/user)

---

- `users.*.update.status`
- This event triggers when a user's status is updated.
  Returns [User Object](/docs/references/cloud/models/user)

---

- `users.*.verification.*`
- This event triggers on any user's verification token event.
  Returns [Token Object](/docs/references/cloud/models/token)

---

- `users.*.verification.*.create`
- This event triggers when a verification token for a user is created.
  Returns [Token Object](/docs/references/cloud/models/token)

---

- `users.*.verification.*.update`
- This event triggers when a verification token for a user is validated.
  Returns [Token Object](/docs/references/cloud/models/token)

**Databases**

- Name
- Description

---

- `tablesdb.*`
- This event triggers on any database event.
  Returns [Database Object](/docs/references/cloud/models/database)

---

- `tablesdb.*.tables.*`
- This event triggers on any table event.
  Returns [Table Object](/docs/references/cloud/models/table)

---

- `tablesdb.*.tables.*.columns.*`
- This event triggers on any columns event.
  Returns [Column Object](/docs/references/cloud/models/columnList)

---

- `tablesdb.*.tables.*.columns.*.create`
- This event triggers when a column is created.
  Returns [Column Object](/docs/references/cloud/models/columnList)

---

- `tablesdb.*.tables.*.columns.*.update`
- This event triggers when a column is updated.
  Returns [Column Object](/docs/references/cloud/models/columnList)

---

- `tablesdb.*.tables.*.columns.*.delete`
- This event triggers when a column is deleted.
  Returns [Column Object](/docs/references/cloud/models/columnList)

---

- `tablesdb.*.tables.*.create`
- This event triggers when a table is created.
  Returns [Table Object](/docs/references/cloud/models/table)

---

- `tablesdb.*.tables.*.delete`
- This event triggers when a table is deleted.
  Returns [Table Object](/docs/references/cloud/models/table)

---

- `tablesdb.*.tables.*.rows.*`
- This event triggers on any rows event.
  Returns [Row Object](/docs/references/cloud/models/row)

---

- `tablesdb.*.tables.*.rows.*.create`
- This event triggers when a row is created.
  Returns [Row Object](/docs/references/cloud/models/row)

---

- `tablesdb.*.tables.*.rows.*.delete`
- This event triggers when a row is deleted.
  Returns [Row Object](/docs/references/cloud/models/row)

---

- `tablesdb.*.tables.*.rows.*.update`
- This event triggers when a row is updated.
  Returns [Row Object](/docs/references/cloud/models/row)

---

- `tablesdb.*.tables.*.rows.*.upsert`
- This event triggers when a row is upserted.
  Returns [Row Object](/docs/references/cloud/models/row)

---

- `tablesdb.*.tables.*.indexes.*`
- This event triggers on any indexes event.
  Returns [Index Object](/docs/references/cloud/models/columnIndex)

---

- `tablesdb.*.tables.*.indexes.*.create`
- This event triggers when an index is created.
  Returns [Index Object](/docs/references/cloud/models/columnIndex)

---

- `tablesdb.*.tables.*.indexes.*.update`
- This event triggers when an index is updated.
  Returns [Index Object](/docs/references/cloud/models/columnIndex)

---

- `tablesdb.*.tables.*.indexes.*.delete`
- This event triggers when an index is deleted.
  Returns [Index Object](/docs/references/cloud/models/columnIndex)

---

- `tablesdb.*.tables.*.update`
- This event triggers when a table is updated.
  Returns [Table Object](/docs/references/cloud/models/table)

---

- `tablesdb.*.create`
- This event triggers when a database is created.
  Returns [Database Object](/docs/references/cloud/models/database)

---

- `tablesdb.*.delete`
- This event triggers when a database is deleted.
  Returns [Database Object](/docs/references/cloud/models/database)

---

- `tablesdb.*.update`
- This event triggers when a database is updated.
  Returns [Database Object](/docs/references/cloud/models/database)

**Note**

Collections, documents, and attributes have been deprecated in favour of tables, rows, and columns. The `databases.*` event prefix has been replaced by `tablesdb.*`.

**Storage**

- Name
- Description

---

- `buckets.*`
- This event triggers on any buckets event.
  Returns [Bucket Object](/docs/references/cloud/models/bucket)

---

- `buckets.*.create`
- This event triggers when a bucket is created.
  Returns [Bucket Object](/docs/references/cloud/models/bucket)

---

- `buckets.*.delete`
- This event triggers when a bucket is deleted.
  Returns [Bucket Object](/docs/references/cloud/models/bucket)

---

- `buckets.*.files.*`
- This event triggers on any files event.
  Returns [File Object](/docs/references/cloud/models/file)

---

- `buckets.*.files.*.create`
- Since the Appwrite SDK chunks files in 5MB increments, this event will trigger for each 5MB chunk. A file is fully uploaded when `chunksTotal` equals `chunksUploaded`.
  Returns [File Object](/docs/references/cloud/models/file)

---

- `buckets.*.files.*.delete`
- This event triggers when a file is deleted.
  Returns [File Object](/docs/references/cloud/models/file)

---

- `buckets.*.files.*.update`
- This event triggers when a file is updated.
  Returns [File Object](/docs/references/cloud/models/file)

---

- `buckets.*.update`
- This event triggers when a bucket is updated.
  Returns [Bucket Object](/docs/references/cloud/models/bucket)

**Functions**

- Name
- Description

---

- `functions.*`
- This event triggers on any functions event.
  Returns [Function Object](/docs/references/cloud/models/function)

---

- `functions.*.create`
- This event triggers when a function is created.
  Returns [Function Object](/docs/references/cloud/models/function)

---

- `functions.*.delete`
- This event triggers when a function is deleted.
  Returns [Function Object](/docs/references/cloud/models/function)

---

- `functions.*.deployments.*`
- This event triggers on any deployments event.
  Returns [Deployment Object](/docs/references/cloud/models/deployment)

---

- `functions.*.deployments.*.create`
- This event triggers when a deployment is created.
  Returns [Deployment Object](/docs/references/cloud/models/deployment)

---

- `functions.*.deployments.*.delete`
- This event triggers when a deployment is deleted.
  Returns [Deployment Object](/docs/references/cloud/models/deployment)

---

- `functions.*.deployments.*.update`
- This event triggers when a deployment is updated.
  Returns [Deployment Object](/docs/references/cloud/models/deployment)

---

- `functions.*.executions.*`
- This event triggers on any executions event.
  Returns [Execution Object](/docs/references/cloud/models/execution)

---

- `functions.*.executions.*.create`
- This event triggers when an execution is created.
  Returns [Execution Object](/docs/references/cloud/models/execution)

---

- `functions.*.executions.*.delete`
- This event triggers when an execution is deleted.
  Returns [Execution Object](/docs/references/cloud/models/execution)

---

- `functions.*.executions.*.update`
- This event triggers when an execution is updated.
  Returns [Execution Object](/docs/references/cloud/models/execution)

---

- `functions.*.update`
- This event triggers when a function is updated.
  Returns [Function Object](/docs/references/cloud/models/function)

**Sites**

- Name
- Description

---

- `sites.*`
- This event triggers on any sites event.
  Returns [Site Object](/docs/references/cloud/models/site)

---

- `sites.*.create`
- This event triggers when a site is created.
  Returns [Site Object](/docs/references/cloud/models/site)

---

- `sites.*.delete`
- This event triggers when a site is deleted.
  Returns [Site Object](/docs/references/cloud/models/site)

---

- `sites.*.deployments.*`
- This event triggers on any deployments event.
  Returns [Deployment Object](/docs/references/cloud/models/deployment)

---

- `sites.*.deployments.*.create`
- This event triggers when a deployment is created.
  Returns [Deployment Object](/docs/references/cloud/models/deployment)

---

- `sites.*.deployments.*.delete`
- This event triggers when a deployment is deleted.
  Returns [Deployment Object](/docs/references/cloud/models/deployment)

---

- `sites.*.deployments.*.update`
- This event triggers when a deployment is updated.
  Returns [Deployment Object](/docs/references/cloud/models/deployment)

---

- `sites.*.update`
- This event triggers when a site is updated.
  Returns [Site Object](/docs/references/cloud/models/site)

**Messaging**

- Name
- Description

---

- `providers.*`
- This event triggers on any providers event.
  Returns [Provider Object](/docs/references/cloud/models/provider)

---

- `providers.*.create`
- This event triggers when a provider is created.
  Returns [Provider Object](/docs/references/cloud/models/provider)

---

- `providers.*.delete`
- This event triggers when a provider is deleted.
  Returns [Provider Object](/docs/references/cloud/models/provider)

---

- `providers.*.update`
- This event triggers when a provider is updated.
  Returns [Provider Object](/docs/references/cloud/models/provider)

---

- `topics.*`
- This event triggers on any topic event.
  Returns [Topic Object](/docs/references/cloud/models/topic)

---

- `topics.*.create`
- This event triggers when a topic is created.
  Returns [Topic Object](/docs/references/cloud/models/topic)

---

- `topics.*.delete`
- This event triggers when a topic is deleted.
  Returns [Topic Object](/docs/references/cloud/models/topic)

---

- `topics.*.update`
- This event triggers when a topic is updated.
  Returns [Topic Object](/docs/references/cloud/models/topic)

---

- `topics.*.subscribers.*.create`
- This event triggers when a subscriber to a topic is created.
  Returns [Topic Object](/docs/references/cloud/models/topic)

---

- `topics.*.subscribers.*.delete`
- This event triggers when a subscriber to a topic is deleted.
  Returns [Topic Object](/docs/references/cloud/models/topic)

---

- `messages.*`
- This event triggers on any message event.
  Returns [Message Object](/docs/references/cloud/models/message)

---

- `messages.*.create`
- This event triggers when a message is created.
  Returns [Message Object](/docs/references/cloud/models/message)

---

- `messages.*.delete`
- This event triggers when a message is deleted.
  Returns [Message Object](/docs/references/cloud/models/message)

---

- `messages.*.update`
- This event triggers when a message is updated.
  Returns [Message Object](/docs/references/cloud/models/message)

### 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": [
      "tablesdb.default.tables.movies.rows.lion-king.create",
      "tablesdb.*.tables.*.rows.*.create",
      "tablesdb.default.tables.*.rows.lion-king.create",
      "tablesdb.*.tables.*.rows.lion-king.create",
      "tablesdb.*.tables.movies.rows.lion-king.create",
      "tablesdb.default.tables.movies.rows.*.create",
      "tablesdb.*.tables.movies.rows.*.create",
      "tablesdb.default.tables.*.rows.*.create",
      "tablesdb.default.tables.movies.rows.lion-king",
      "tablesdb.*.tables.*.rows.*",
      "tablesdb.default.tables.*.rows.lion-king",
      "tablesdb.*.tables.*.rows.lion-king",
      "tablesdb.*.tables.movies.rows.lion-king",
      "tablesdb.default.tables.movies.rows.*",
      "tablesdb.*.tables.movies.rows.*",
      "tablesdb.default.tables.*.rows.*",
      "tablesdb.default.tables.movies",
      "tablesdb.*.tables.*",
      "tablesdb.default.tables.*",
      "tablesdb.*.tables.movies",
      "tablesdb.default",
      "tablesdb.*"
  ]
}
```

---

## GraphQL

URL: 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

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

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

You can execute a GraphQL query via a GET request, passing a `query` and optionally `operationName` and `variables` as query parameters.

#### POST requests

There are multiple ways to make a GraphQL POST request, differentiated by content type.

**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": {}
    }
]
```

**GraphQL**

The `application/graphql` content type can be used to send a query as the raw POST body.

```graphql
query GetAccount {
    accountGet {
        _id
        email
    }
}
```

#### 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

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

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

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

There are two main features that make GraphQL appealing when compared to the REST API: **selection sets** and **query batching**.

#### 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

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

Appwrite SDKs also support GraphQL in addition to the REST services.

```client-web
import { Client, Graphql } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your Appwrite Endpoint
    .setProject('<PROJECT_ID>');                // 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://<REGION>.cloud.appwrite.io/v1') // Your Appwrite Endpoint
    .setProject('<PROJECT_ID>');               // 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://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<PROJECT_ID>")                // 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://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<PROJECT_ID>")                // 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()
}
```

---

## Realtime

URL: 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. The Appwrite Realtime allows you to listen to any Appwrite events in realtime using the `Realtime` service.

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.

```client-web
import { Client, Realtime, Channel } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

const realtime = new Realtime(client);

// Subscribe to files channel
const subscription = await realtime.subscribe(Channel.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://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

final realtime = Realtime(client);

// Subscribe to files channel
final subscription = realtime.subscribe([Channel.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://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

let realtime = Realtime(client)

// Subscribe to files channel
let subscription = realtime.subscribe(channels: [Channel.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.Channel
import io.appwrite.Client
import io.appwrite.services.Realtime

val client = Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

val realtime = Realtime(client)

// Subscribe to files channel
val subscription = realtime.subscribe(Channel.files()) {
    if(it.events.contains("buckets.*.files.*.create")) {
        // Log when a new file is uploaded
        print(it.payload.toString());
    }
}
```

```client-android-java
import io.appwrite.Client;
import io.appwrite.models.RealtimeResponseEvent;
import io.appwrite.services.Realtime;
import kotlin.Unit;

Client client = new Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>");

Realtime realtime = new Realtime(client);

// Subscribe to files channel
realtime.subscribe(new String[] {"files"}, (RealtimeResponseEvent<Object> response) -> {
    if (response.getEvents().contains("buckets.*.files.*.create")) {
        // Log when a new file is uploaded
        System.out.println(response.getPayload());
    }
    return Unit.INSTANCE;
});
```

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/apis/events).

**Permissions**

All subscriptions are secured by the [permissions system](/docs/advanced/security/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.

### Limitations

While the Realtime API offers robust capabilities, there are currently some limitations to keep in mind.

#### Subscription changes

Client SDKs use a **single WebSocket** per `Realtime` client for all subscriptions. Adding a subscription with `subscribe()`, dropping one with `subscription.unsubscribe()`, or replacing channels and queries via `subscription.update(...)` applies on the existing socket where supported — no full reconnect required. The connection is torn down when you call `realtime.disconnect()`, or when the legacy `subscription.close()` runs on the last remaining subscription.

Manage subscription handles alongside your application state so you unsubscribe or disconnect when listeners are no longer needed. See [Subscribe](/docs/apis/realtime/subscribe) for platform-specific APIs.

#### Server SDKs

We currently are not offering access to realtime with Server SDKs and an API key.

---

## Authentication

URL: https://appwrite.io/docs/apis/realtime/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).

**Permissions**

All subscriptions are secured by the [permissions system](/docs/advanced/security/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.

### Session lifecycle

When working with Realtime subscriptions and authentication, keep the following in mind:

1. **Create session first** - Always authenticate the user before creating subscriptions that require access to protected resources.
2. **Re-subscribe on session change** - If a user logs out and a new user logs in, call `realtime.disconnect()` and then create new subscriptions for the new session.
3. **Handle session expiry** - If a session expires, subscriptions tied to that session will stop receiving updates. Listen for session-related errors and re-authenticate when needed.

---

## Channels

URL: https://appwrite.io/docs/apis/realtime/channels

Channels define which Appwrite resources you want to subscribe to. When subscribing to a channel, you will receive callbacks for events related to that channel's resources. The Appwrite SDKs provide a `Channel` helper class to build type-safe channel subscriptions using a fluent API.

### Channel helpers

Instead of manually writing channel strings, you can use the `Channel` helper class to build type-safe channel subscriptions. The helper provides a fluent API that makes it easier to construct channel strings and reduces errors.

```client-web
import { Client, Realtime, Channel } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

const realtime = new Realtime(client);

// Subscribe to account channel
const subscription = await realtime.subscribe(Channel.account(), response => {
    console.log(response);
});

// Subscribe to a specific row
const rowSubscription = await realtime.subscribe(
    Channel.tablesdb('<DATABASE_ID>').table('<TABLE_ID>').row('<ROW_ID>'),
    response => {
        console.log(response);
    }
);
```

```client-flutter
import 'package:appwrite/appwrite.dart';

final client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

final realtime = Realtime(client);

// Subscribe to account channel
final subscription = realtime.subscribe([Channel.account()]);

// Subscribe to a specific row
final docSubscription = realtime.subscribe([
    Channel.tablesdb('<DATABASE_ID>').table('<TABLE_ID>').row('<ROW_ID>')
]);
```

```client-apple
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

let realtime = Realtime(client)

// Subscribe to account channel
let subscription = realtime.subscribe(channels: [Channel.account()]) { response in
    print(String(describing: response))
}

// Subscribe to a specific row
let docSubscription = realtime.subscribe(
    channels: [Channel.tablesdb("<DATABASE_ID>").table("<TABLE_ID>").row("<ROW_ID>")]
) { response in
    print(String(describing: response))
}
```

```client-android-kotlin
import io.appwrite.Channel
import io.appwrite.Client
import io.appwrite.services.Realtime

val client = Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

val realtime = Realtime(client)

// Subscribe to account channel
val subscription = realtime.subscribe(Channel.account()) {
    print(it.toString())
}

// Subscribe to a specific row
val docSubscription = realtime.subscribe(
    Channel.tablesdb("<DATABASE_ID>").table("<TABLE_ID>").row("<ROW_ID>")
) {
    print(it.toString())
}
```

```client-android-java
import io.appwrite.Channel;
import io.appwrite.ChannelKt;
import io.appwrite.Client;
import io.appwrite.models.RealtimeResponseEvent;
import io.appwrite.models.RealtimeSubscription;
import io.appwrite.services.Realtime;
import kotlin.Unit;

Client client = new Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>");

Realtime realtime = new Realtime(client);

// Subscribe to account channel
String accountChannel = Channel.Companion.account();
RealtimeSubscription subscription = realtime.subscribe(
    new String[] {accountChannel},
    (RealtimeResponseEvent<Object> response) -> {
        System.out.println(response);
        return Unit.INSTANCE;
    }
);

// Subscribe to a specific row
Channel<?> rowChannel = ChannelKt.row(
    ChannelKt.table(Channel.Companion.tablesdb("<DATABASE_ID>"), "<TABLE_ID>"),
    "<ROW_ID>"
);

RealtimeSubscription rowSubscription = realtime.subscribe(
    new Channel[] {rowChannel},
    (RealtimeResponseEvent<Object> response) -> {
        System.out.println(response);
        return Unit.INSTANCE;
    }
);
```

The `Channel` helper supports all available channels and allows you to:
- Build channels with a fluent, chainable API
- Optionally specify resource IDs (omit IDs to subscribe to all resources)
- Add event filters like `.create()`, `.update()`, or `.delete()`

### Available channels

A list of all channels available you can subscribe to. When using `Channel` helpers, leaving an ID blank will subscribe using `*`.

| Channel | Channel Helper | Description |
| --- | --- | --- |
| `account` | `Channel.account()` | All account related events (session create, name update...) |
| `tablesdb.<ID>.tables.<ID>.rows` | `Channel.tablesdb('<DATABASE_ID>').table('<TABLE_ID>').row()` | Any create/update/delete events to any row in a table |
| `rows` | `Channel.rows()` | Any create/update/delete events to any row |
| `tablesdb.<ID>.tables.<ID>.rows.<ID>` | `Channel.tablesdb('<DATABASE_ID>').table('<TABLE_ID>').row('<ROW_ID>')` | Any update/delete events to a given row |
| `files` | `Channel.files()` | Any create/update/delete events to any file |
| `buckets.<ID>.files.<ID>` | `Channel.bucket('<BUCKET_ID>').file('<FILE_ID>')` | Any update/delete events to a given file of the given bucket |
| `buckets.<ID>.files` | `Channel.bucket('<BUCKET_ID>').file()` | Any update/delete events to any file of the given bucket |
| `teams.*` | `Channel.teams()` | Any create/update/delete events to any team |
| `teams.<ID>` | `Channel.team('<TEAM_ID>')` | Any update/delete events to a given team |
| `memberships` | `Channel.memberships()` | Any create/update/delete events to any membership |
| `memberships.<ID>` | `Channel.membership('<MEMBERSHIP_ID>')` | Any update/delete events to a given membership |
| `executions` | `Channel.executions()` | Any update to executions |
| `executions.<ID>` | `Channel.execution('<ID>')` | Any update to a given execution |
| `functions.<ID>` | `Channel.function('<FUNCTION_ID>')` | Any execution event to a given function |
| `presences` | `Channel.presences()` | Any upsert, update, or delete event on any [presence](/docs/apis/realtime/presences) the subscriber can read. |
| `presences.<ID>` | `Channel.presence('<PRESENCE_ID>')` | Any upsert, update, or delete event on a given presence record. |

### Event filters

You can also filter events by appending event methods to the channel helpers:
- `.create()` - Listen only to create events
- `.update()` - Listen only to update events
- `.delete()` - Listen only to delete events

For example, `Channel.tablesdb('<DATABASE_ID>').table('<TABLE_ID>').row('<ROW_ID>').update()` will only trigger on row updates.

---

## Custom endpoint

URL: https://appwrite.io/docs/apis/realtime/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://<REGION>.cloud.appwrite.io/v1/realtime`.

```client-web
import { Client } from "appwrite";
const client = new Client();

client.setEndpointRealtime('wss://<REGION>.cloud.appwrite.io/v1/realtime');
```

```client-flutter
import 'package:appwrite/appwrite.dart';

final client = Client();
client.setEndpointRealtime('wss://<REGION>.cloud.appwrite.io/v1/realtime');
```

```client-apple
import Appwrite

let client = Client()
client.setEndpointRealtime("wss://<REGION>.cloud.appwrite.io/v1/realtime")
```

```client-android-kotlin
import io.appwrite.Client

val client = Client(context)
client.setEndpointRealtime("wss://<REGION>.cloud.appwrite.io/v1/realtime")
```

```client-android-java
import io.appwrite.Client;

Client client = new Client(context);
client.setEndpointRealtime("wss://<REGION>.cloud.appwrite.io/v1/realtime");
```

---

## Payload

URL: https://appwrite.io/docs/apis/realtime/payload

When you receive an update from a Realtime subscription, the payload contains information about the event and the affected resource. Understanding this structure helps you handle updates effectively in your application.

### Response structure

The payload from the subscription will contain the following properties:

| Name | Type | Description |
| --- | --- | --- |
| events | string[] | The [Appwrite events](/docs/apis/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. |

### Example

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": [
    "tablesdb.default.tables.sample.rows.63c98b9baea0938e1206.update",
    "tablesdb.*.tables.*.rows.*.update",
    "tablesdb.default.tables.*.rows.63c98b9baea0938e1206.update",
    "tablesdb.*.tables.*.rows.63c98b9baea0938e1206.update",
    "tablesdb.*.tables.sample.rows.63c98b9baea0938e1206.update",
    "tablesdb.default.tables.sample.rows.*.update",
    "tablesdb.*.tables.sample.rows.*.update",
    "tablesdb.default.tables.*.rows.*.update",
    "tablesdb.default.tables.sample.rows.63c98b9baea0938e1206",
    "tablesdb.*.tables.*.rows.*",
    "tablesdb.default.tables.*.rows.63c98b9baea0938e1206",
    "tablesdb.*.tables.*.rows.63c98b9baea0938e1206",
    "tablesdb.*.tables.sample.rows.63c98b9baea0938e1206",
    "tablesdb.default.tables.sample.rows.*",
    "tablesdb.*.tables.sample.rows.*",
    "tablesdb.default.tables.*.rows.*",
    "tablesdb.default.tables.sample",
    "tablesdb.*.tables.*",
    "tablesdb.default.tables.*",
    "tablesdb.*.tables.sample",
    "tablesdb.default",
    "tablesdb.*"
  ],
  "channels": [
    "rows",
    "tablesdb.default.tables.sample.rows",
    "tablesdb.default.tables.sample.rows.63c98b9baea0938e1206"
  ],
  "timestamp": "2023-01-19T18:30:04.051+00:00",
  "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"
  }
}
```

---

## Presences

URL: https://appwrite.io/docs/apis/realtime/presences

The Appwrite **Presences API** tracks which users are currently active in your app and lets every connected client see those statuses in realtime. You can use it to render online indicators next to teammates, show who is viewing a document, broadcast a "typing" status in a chat, or surface "looking at the same page" cues during collaboration.

A presence is a short-lived record tied to a user. Each record carries a `userId`, a `status` string (for example `online`, `away`, `editing`), an optional `metadata` JSON object for richer context (a cursor position, the document the user is viewing, the device they are on), and an `expiresAt` timestamp that controls when the record is automatically cleaned up.

Presences are exposed as both a regular HTTP resource and a [Realtime](/docs/apis/realtime) channel, so the same record can be written by any client or server SDK and read live by every subscriber that has permission.

### How it works

A presence has two sides that are always in sync.

**It is durable.** When you write a presence, it sticks around until it expires or you delete it. That means you can `list()` presences at any time to see who is online right now, including from a server-side function, without having to keep a Realtime connection open.

**It is live.** Every change to a presence fires an event on the `presences` and `presences.<PRESENCE_ID>` [Realtime](/docs/apis/realtime) channels. Subscribers get `upsert`, `update`, and `delete` events in milliseconds, over the same Realtime connection they are already using for rows and files.

A typical "online dot" loop looks like this:

1. Client A signs in and calls `presences.upsert({...})`. An `upsert` event fires on the presence channels.
2. Client B, subscribed to `Channel.presences()`, receives the event and shows A as online.
3. Client A keeps the record alive by upserting again on focus, route change, or a periodic timer, which slides `expiresAt` forward.
4. When `expiresAt` passes, the record is removed and a `delete` event fires. B drops A from its list.
5. If A signs out cleanly, they call `presences.delete(...)` and the `delete` event fires immediately, no waiting on expiry.

This gives you two ways to keep a presence alive, and you pick whichever fits your UI:

- **Heartbeat.** Upsert on focus, route change, or a periodic timer to push `expiresAt` forward. Best when presence should persist briefly across short disconnects (a quick network blip, a tab switch) or when you write presence from server code that has no live socket.
- **While connected.** Call `realtime.upsertPresence(...)` over an open Realtime connection and the record is automatically deleted when that connection closes. Best for "online while the tab is open" UIs where you do not want to manage a heartbeat yourself.

The `realtime.upsertPresence(...)` call mirrors the REST `presences.upsert(...)` signature, but the record's lifetime is tied to the WebSocket rather than to `expiresAt`:

```client-web
import { Client, Realtime, ID, Permission, Role } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

const realtime = new Realtime(client);

await realtime.upsertPresence({
    presenceId: ID.unique(),
    status: 'online',
    permissions: [
        Permission.read(Role.users())
    ]
});
```

```client-flutter
import 'package:appwrite/appwrite.dart';

final client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

final realtime = Realtime(client);

await realtime.upsertPresence(
    presenceId: ID.unique(),
    status: 'online',
    permissions: [
        Permission.read(Role.users()),
    ],
);
```

```client-apple
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

let realtime = Realtime(client)

try await realtime.upsertPresence(
    presenceId: ID.unique(),
    status: "online",
    permissions: [
        Permission.read(Role.users())
    ]
)
```

```client-android-kotlin
import io.appwrite.Client
import io.appwrite.ID
import io.appwrite.Permission
import io.appwrite.Role
import io.appwrite.services.Realtime

val client = Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

val realtime = Realtime(client)

realtime.upsertPresence(
    presenceId = ID.unique(),
    status = "online",
    permissions = listOf(
        Permission.read(Role.users())
    )
)
```

The SDK remembers the latest payload and re-sends it after a reconnect, so a brief network drop will not flip the user offline. There is no heartbeat to manage. The record disappears automatically the moment the WebSocket closes for good (tab close, sign out, sustained network loss).

### Upsert a presence

`upsert` creates a presence or updates the existing record with the same `presenceId`. Call it on every page navigation, focus change, or heartbeat without worrying about duplicates. From a client session, `userId` is inferred from the signed-in user; from a server SDK with an API key, pass `userId` explicitly. Server SDKs need an [API key](/docs/partners/project/api-keys) with the `presences.write` scope.

```client-web
import { Client, Presences, ID, Permission, Role } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

const presences = new Presences(client);

const presence = await presences.upsert({
    presenceId: ID.unique(),
    status: 'online',
    metadata: { page: '/dashboard' },
    permissions: [
        Permission.read(Role.users())
    ]
});
```

```client-flutter
import 'package:appwrite/appwrite.dart';

final client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

final presences = Presences(client);

final presence = await presences.upsert(
    presenceId: ID.unique(),
    status: 'online',
    metadata: { 'page': '/dashboard' },
    permissions: [
        Permission.read(Role.users()),
    ],
);
```

```client-apple
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

let presences = Presences(client)

let presence = try await presences.upsert(
    presenceId: ID.unique(),
    status: "online",
    metadata: ["page": "/dashboard"],
    permissions: [
        Permission.read(Role.users())
    ]
)
```

```client-android-kotlin
import io.appwrite.Client
import io.appwrite.ID
import io.appwrite.Permission
import io.appwrite.Role
import io.appwrite.services.Presences

val client = Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

val presences = Presences(client)

val presence = presences.upsert(
    presenceId = ID.unique(),
    status = "online",
    metadata = mapOf("page" to "/dashboard"),
    permissions = listOf(
        Permission.read(Role.users())
    )
)
```

```server-nodejs
const sdk = require('node-appwrite');
const { Permission, Role } = sdk;

const client = new sdk.Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<YOUR_PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const presences = new sdk.Presences(client);

const presence = await presences.upsert({
    presenceId: '<PRESENCE_ID>',
    userId: '<USER_ID>',
    status: 'online',
    permissions: [
        Permission.read(Role.users())
    ]
});
```

```server-python
from appwrite.client import Client
from appwrite.services.presences import Presences
from appwrite.permission import Permission
from appwrite.role import Role

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<YOUR_PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

presences = Presences(client)

presence = presences.upsert(
    presence_id = '<PRESENCE_ID>',
    user_id = '<USER_ID>',
    status = 'online',
    permissions = [
        Permission.read(Role.users())
    ]
)
```

```server-php
<?php

use Appwrite\Client;
use Appwrite\Permission;
use Appwrite\Role;
use Appwrite\Services\Presences;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<YOUR_PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$presences = new Presences($client);

$presence = $presences->upsert(
    presenceId: '<PRESENCE_ID>',
    userId: '<USER_ID>',
    status: 'online',
    permissions: [
        Permission::read(Role::users())
    ]
);
```

```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<YOUR_PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

presences = Presences.new(client)

presence = presences.upsert(
    presence_id: '<PRESENCE_ID>',
    user_id: '<USER_ID>',
    status: 'online',
    permissions: [
        Permission.read(Role.users())
    ]
)
```

```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<YOUR_PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Presences presences = Presences(client);

Presence presence = await presences.upsert(
    presenceId: '<PRESENCE_ID>',
    userId: '<USER_ID>',
    status: 'online',
    permissions: [
        Permission.read(Role.users()),
    ],
);
```

```server-kotlin
import io.appwrite.Client
import io.appwrite.Permission
import io.appwrite.Role
import io.appwrite.services.Presences

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<YOUR_PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val presences = Presences(client)

val presence = presences.upsert(
    presenceId = "<PRESENCE_ID>",
    userId = "<USER_ID>",
    status = "online",
    permissions = listOf(
        Permission.read(Role.users())
    )
)
```

```server-java
import io.appwrite.Client;
import io.appwrite.Permission;
import io.appwrite.Role;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Presences;

Client client = new Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<YOUR_PROJECT_ID>")
    .setKey("<YOUR_API_KEY>");

Presences presences = new Presences(client);

presences.upsert(
    "<PRESENCE_ID>", // presenceId
    "<USER_ID>",     // userId
    "online",        // status
    null,            // metadata (optional)
    null,            // expiresAt (optional)
    List.of(Permission.read(Role.users())), // permissions (optional)
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }
        System.out.println(result);
    })
);
```

```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<YOUR_PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let presences = Presences(client)

let presence = try await presences.upsert(
    presenceId: "<PRESENCE_ID>",
    userId: "<USER_ID>",
    status: "online",
    permissions: [
        Permission.read(Role.users())
    ]
)
```

```server-dotnet
using Appwrite;
using Appwrite.Models;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<YOUR_PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Presences presences = new Presences(client);

Presence presence = await presences.Upsert(
    presenceId: "<PRESENCE_ID>",
    userId: "<USER_ID>",
    status: "online",
    permissions: new List<string> {
        Permission.Read(Role.Users())
    }
);
```

```server-go
package main

import (
    "fmt"
    "github.com/appwrite/sdk-for-go/client"
    "github.com/appwrite/sdk-for-go/presences"
)

func main() {
    cli := client.New(
        client.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"),
        client.WithProject("<YOUR_PROJECT_ID>"),
        client.WithKey("<YOUR_API_KEY>"),
    )

    service := presences.New(cli)

    presence, err := service.Upsert(
        "<PRESENCE_ID>",
        "<USER_ID>",
        "online",
        presences.WithUpsertPermissions([]string{`read("users")`}),
    )
    if err != nil {
        panic(err)
    }
    fmt.Println(presence)
}
```

```server-rust
use appwrite::Client;
use appwrite::permission::Permission;
use appwrite::role::Role;
use appwrite::services::Presences;

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

    let presences = Presences::new(&client);

    let presence = presences.upsert(
        "<PRESENCE_ID>",
        "<USER_ID>",
        "online",
        Some(vec![Permission::read(Role::users(None)).to_string()]),
        None,
        None,
    ).await?;

    println!("{:?}", presence);

    Ok(())
}
```

A few notes on the parameters:

- `presenceId` (**required**) is the unique ID of the presence record. Use `ID.unique()` on first creation and persist it for subsequent updates so the same record is reused for the same user across sessions.
- `status` (**required**) is a free-form string up to 256 characters. There are no reserved values, so pick whatever vocabulary fits your app (`online`, `away`, `busy`, `editing`, `typing`).
- `userId` is set automatically from the authenticated session on client SDKs and is required on server SDKs.
- `metadata` is an arbitrary JSON object. Use it to carry any context that subscribers should see together with the status.
- `expiresAt` is optional. Without it, Appwrite applies a default TTL (see [Expiry and cleanup](#expiry-and-cleanup) below).
- `permissions` controls who can read or modify the presence record, the same way it works on rows and files. Without permissions, only the owner and project keys can see it.

### Get a presence

Fetch a single presence by its `presenceId`. Records whose `expiresAt` is in the past are treated as not found.

```client-web
import { Client, Presences } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

const presences = new Presences(client);

const presence = await presences.get({
    presenceId: '<PRESENCE_ID>'
});
```

```client-flutter
import 'package:appwrite/appwrite.dart';

final client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

final presences = Presences(client);

final presence = await presences.get(
    presenceId: '<PRESENCE_ID>',
);
```

```client-apple
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

let presences = Presences(client)

let presence = try await presences.get(
    presenceId: "<PRESENCE_ID>"
)
```

```client-android-kotlin
import io.appwrite.Client
import io.appwrite.services.Presences

val client = Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

val presences = Presences(client)

val presence = presences.get(
    presenceId = "<PRESENCE_ID>"
)
```

```server-nodejs
const sdk = require('node-appwrite');

const client = new sdk.Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<YOUR_PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const presences = new sdk.Presences(client);

const presence = await presences.get({
    presenceId: '<PRESENCE_ID>'
});
```

```server-python
from appwrite.client import Client
from appwrite.services.presences import Presences

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<YOUR_PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

presences = Presences(client)

presence = presences.get(
    presence_id = '<PRESENCE_ID>'
)
```

```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Presences;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<YOUR_PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$presences = new Presences($client);

$presence = $presences->get(
    presenceId: '<PRESENCE_ID>'
);
```

```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<YOUR_PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

presences = Presences.new(client)

presence = presences.get(
    presence_id: '<PRESENCE_ID>'
)
```

```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<YOUR_PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Presences presences = Presences(client);

Presence presence = await presences.get(
    presenceId: '<PRESENCE_ID>',
);
```

```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Presences

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<YOUR_PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val presences = Presences(client)

val presence = presences.get(
    presenceId = "<PRESENCE_ID>"
)
```

```server-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Presences;

Client client = new Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<YOUR_PROJECT_ID>")
    .setKey("<YOUR_API_KEY>");

Presences presences = new Presences(client);

presences.get(
    "<PRESENCE_ID>", // presenceId
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }
        System.out.println(result);
    })
);
```

```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<YOUR_PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let presences = Presences(client)

let presence = try await presences.get(
    presenceId: "<PRESENCE_ID>"
)
```

```server-dotnet
using Appwrite;
using Appwrite.Models;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<YOUR_PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Presences presences = new Presences(client);

Presence presence = await presences.Get(
    presenceId: "<PRESENCE_ID>"
);
```

```server-go
package main

import (
    "fmt"
    "github.com/appwrite/sdk-for-go/client"
    "github.com/appwrite/sdk-for-go/presences"
)

func main() {
    cli := client.New(
        client.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"),
        client.WithProject("<YOUR_PROJECT_ID>"),
        client.WithKey("<YOUR_API_KEY>"),
    )

    service := presences.New(cli)

    presence, err := service.Get("<PRESENCE_ID>")
    if err != nil {
        panic(err)
    }
    fmt.Println(presence)
}
```

```server-rust
use appwrite::Client;
use appwrite::services::Presences;

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

    let presences = Presences::new(&client);

    let presence = presences.get("<PRESENCE_ID>").await?;

    println!("{:?}", presence);

    Ok(())
}
```

### List presences

`list` returns the active set. Expired records are filtered out automatically, so the response is always "who is here right now". Pass [Queries](/docs/products/databases/queries) to filter by `status`, `userId`, or any indexed field, and pass `ttl` to cache the response server-side for a configurable number of seconds.

```client-web
import { Client, Presences, Query } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

const presences = new Presences(client);

const result = await presences.list({
    queries: [Query.equal('status', ['online'])]
});
```

```client-flutter
import 'package:appwrite/appwrite.dart';

final client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

final presences = Presences(client);

final result = await presences.list(
    queries: [Query.equal('status', ['online'])],
);
```

```client-apple
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

let presences = Presences(client)

let result = try await presences.list(
    queries: [Query.equal("status", value: ["online"])]
)
```

```client-android-kotlin
import io.appwrite.Client
import io.appwrite.Query
import io.appwrite.services.Presences

val client = Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

val presences = Presences(client)

val result = presences.list(
    queries = listOf(Query.equal("status", listOf("online")))
)
```

```server-nodejs
const sdk = require('node-appwrite');

const client = new sdk.Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<YOUR_PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const presences = new sdk.Presences(client);

const result = await presences.list({
    queries: [sdk.Query.equal('status', ['online'])]
});
```

```server-python
from appwrite.client import Client
from appwrite.services.presences import Presences
from appwrite.query import Query

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<YOUR_PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

presences = Presences(client)

result = presences.list(
    queries = [Query.equal('status', ['online'])]
)
```

```server-php
<?php

use Appwrite\Client;
use Appwrite\Query;
use Appwrite\Services\Presences;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<YOUR_PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$presences = new Presences($client);

$result = $presences->list(
    queries: [Query::equal('status', ['online'])]
);
```

```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<YOUR_PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

presences = Presences.new(client)

result = presences.list(
    queries: [Query.equal('status', ['online'])]
)
```

```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<YOUR_PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Presences presences = Presences(client);

PresenceList result = await presences.list(
    queries: [Query.equal('status', ['online'])],
);
```

```server-kotlin
import io.appwrite.Client
import io.appwrite.Query
import io.appwrite.services.Presences

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<YOUR_PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val presences = Presences(client)

val response = presences.list(
    queries = listOf(Query.equal("status", listOf("online")))
)
```

```server-java
import io.appwrite.Client;
import io.appwrite.Query;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Presences;

import java.util.List;

Client client = new Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<YOUR_PROJECT_ID>")
    .setKey("<YOUR_API_KEY>");

Presences presences = new Presences(client);

presences.list(
    List.of(Query.equal("status", List.of("online"))), // queries
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }
        System.out.println(result);
    })
);
```

```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<YOUR_PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let presences = Presences(client)

let response = try await presences.list(
    queries: [Query.equal("status", value: ["online"])]
)
```

```server-dotnet
using Appwrite;
using Appwrite.Models;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<YOUR_PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Presences presences = new Presences(client);

PresenceList result = await presences.List(
    queries: new List<string> { Query.Equal("status", new List<object> { "online" }) }
);
```

```server-go
package main

import (
    "fmt"
    "github.com/appwrite/sdk-for-go/client"
    "github.com/appwrite/sdk-for-go/presences"
    "github.com/appwrite/sdk-for-go/query"
)

func main() {
    cli := client.New(
        client.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"),
        client.WithProject("<YOUR_PROJECT_ID>"),
        client.WithKey("<YOUR_API_KEY>"),
    )

    service := presences.New(cli)

    result, err := service.List(
        service.WithListQueries([]string{
            query.Equal("status", "online"),
        }),
    )
    if err != nil {
        panic(err)
    }
    fmt.Println(result)
}
```

```server-rust
use appwrite::Client;
use appwrite::services::Presences;
use appwrite::query::Query;

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

    let presences = Presences::new(&client);

    let result = presences.list(
        Some(vec![Query::equal("status", vec!["online".to_string()]).to_string()]),
        None,
        None,
    ).await?;

    println!("{:?}", result);

    Ok(())
}
```

### Update a presence

`update` patches a subset of fields on an existing record without re-sending the whole payload. Every field except `presenceId` is optional, so a "go away" handler only needs to send `status`. **One naming difference to watch for:** the method is named `update` on client SDKs and `updatePresence` (with each language's case convention) on server SDKs, where it also requires `userId`. This is the only point at which the client and server surfaces diverge.

```client-web
import { Client, Presences } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

const presences = new Presences(client);

const presence = await presences.update({
    presenceId: '<PRESENCE_ID>',
    status: 'away'
});
```

```client-flutter
import 'package:appwrite/appwrite.dart';

final client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

final presences = Presences(client);

final presence = await presences.update(
    presenceId: '<PRESENCE_ID>',
    status: 'away',
);
```

```client-apple
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

let presences = Presences(client)

let presence = try await presences.update(
    presenceId: "<PRESENCE_ID>",
    status: "away"
)
```

```client-android-kotlin
import io.appwrite.Client
import io.appwrite.services.Presences

val client = Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

val presences = Presences(client)

val presence = presences.update(
    presenceId = "<PRESENCE_ID>",
    status = "away"
)
```

```server-nodejs
const sdk = require('node-appwrite');

const client = new sdk.Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<YOUR_PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const presences = new sdk.Presences(client);

const presence = await presences.updatePresence({
    presenceId: '<PRESENCE_ID>',
    userId: '<USER_ID>',
    status: 'away'
});
```

```server-python
from appwrite.client import Client
from appwrite.services.presences import Presences

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<YOUR_PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

presences = Presences(client)

presence = presences.update_presence(
    presence_id = '<PRESENCE_ID>',
    user_id = '<USER_ID>',
    status = 'away'
)
```

```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Presences;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<YOUR_PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$presences = new Presences($client);

$presence = $presences->updatePresence(
    presenceId: '<PRESENCE_ID>',
    userId: '<USER_ID>',
    status: 'away'
);
```

```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<YOUR_PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

presences = Presences.new(client)

presence = presences.update_presence(
    presence_id: '<PRESENCE_ID>',
    user_id: '<USER_ID>',
    status: 'away'
)
```

```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<YOUR_PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Presences presences = Presences(client);

Presence presence = await presences.updatePresence(
    presenceId: '<PRESENCE_ID>',
    userId: '<USER_ID>',
    status: 'away',
);
```

```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Presences

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<YOUR_PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val presences = Presences(client)

val presence = presences.updatePresence(
    presenceId = "<PRESENCE_ID>",
    userId = "<USER_ID>",
    status = "away"
)
```

```server-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Presences;

Client client = new Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<YOUR_PROJECT_ID>")
    .setKey("<YOUR_API_KEY>");

Presences presences = new Presences(client);

presences.updatePresence(
    "<PRESENCE_ID>", // presenceId
    "<USER_ID>",     // userId
    "away",          // status
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }
        System.out.println(result);
    })
);
```

```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<YOUR_PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let presences = Presences(client)

let presence = try await presences.updatePresence(
    presenceId: "<PRESENCE_ID>",
    userId: "<USER_ID>",
    status: "away"
)
```

```server-dotnet
using Appwrite;
using Appwrite.Models;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<YOUR_PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Presences presences = new Presences(client);

Presence presence = await presences.UpdatePresence(
    presenceId: "<PRESENCE_ID>",
    userId: "<USER_ID>",
    status: "away"
);
```

```server-go
package main

import (
    "fmt"
    "github.com/appwrite/sdk-for-go/client"
    "github.com/appwrite/sdk-for-go/presences"
)

func main() {
    cli := client.New(
        client.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"),
        client.WithProject("<YOUR_PROJECT_ID>"),
        client.WithKey("<YOUR_API_KEY>"),
    )

    service := presences.New(cli)

    presence, err := service.UpdatePresence(
        "<PRESENCE_ID>",
        "<USER_ID>",
        service.WithUpdatePresenceStatus("away"),
    )
    if err != nil {
        panic(err)
    }
    fmt.Println(presence)
}
```

```server-rust
use appwrite::Client;
use appwrite::services::Presences;

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

    let presences = Presences::new(&client);

    let presence = presences.update_presence(
        "<PRESENCE_ID>",
        "<USER_ID>",
        Some("away"),
        None,
        None,
        None,
        None,
    ).await?;

    println!("{:?}", presence);

    Ok(())
}
```

### Delete a presence

`delete` removes a record immediately and fires a `delete` event on the presence channels. Use it when you want a user to go offline without waiting for `expiresAt` to elapse, for example on sign out or admin force-offline.

```client-web
import { Client, Presences } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

const presences = new Presences(client);

await presences.delete({
    presenceId: '<PRESENCE_ID>'
});
```

```client-flutter
import 'package:appwrite/appwrite.dart';

final client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

final presences = Presences(client);

await presences.delete(
    presenceId: '<PRESENCE_ID>',
);
```

```client-apple
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

let presences = Presences(client)

_ = try await presences.delete(
    presenceId: "<PRESENCE_ID>"
)
```

```client-android-kotlin
import io.appwrite.Client
import io.appwrite.services.Presences

val client = Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

val presences = Presences(client)

presences.delete(
    presenceId = "<PRESENCE_ID>"
)
```

```server-nodejs
const sdk = require('node-appwrite');

const client = new sdk.Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<YOUR_PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const presences = new sdk.Presences(client);

await presences.delete({
    presenceId: '<PRESENCE_ID>'
});
```

```server-python
from appwrite.client import Client
from appwrite.services.presences import Presences

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<YOUR_PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

presences = Presences(client)

presences.delete(
    presence_id = '<PRESENCE_ID>'
)
```

```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Presences;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<YOUR_PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$presences = new Presences($client);

$presences->delete(
    presenceId: '<PRESENCE_ID>'
);
```

```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<YOUR_PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

presences = Presences.new(client)

presences.delete(
    presence_id: '<PRESENCE_ID>'
)
```

```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<YOUR_PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Presences presences = Presences(client);

await presences.delete(
    presenceId: '<PRESENCE_ID>',
);
```

```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Presences

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<YOUR_PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val presences = Presences(client)

presences.delete(
    presenceId = "<PRESENCE_ID>"
)
```

```server-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Presences;

Client client = new Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<YOUR_PROJECT_ID>")
    .setKey("<YOUR_API_KEY>");

Presences presences = new Presences(client);

presences.delete(
    "<PRESENCE_ID>", // presenceId
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }
        System.out.println(result);
    })
);
```

```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<YOUR_PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let presences = Presences(client)

_ = try await presences.delete(
    presenceId: "<PRESENCE_ID>"
)
```

```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<YOUR_PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Presences presences = new Presences(client);

await presences.Delete(
    presenceId: "<PRESENCE_ID>"
);
```

```server-go
package main

import (
    "fmt"
    "github.com/appwrite/sdk-for-go/client"
    "github.com/appwrite/sdk-for-go/presences"
)

func main() {
    cli := client.New(
        client.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"),
        client.WithProject("<YOUR_PROJECT_ID>"),
        client.WithKey("<YOUR_API_KEY>"),
    )

    service := presences.New(cli)

    _, err := service.Delete("<PRESENCE_ID>")
    if err != nil {
        panic(err)
    }
    fmt.Println("Presence deleted")
}
```

```server-rust
use appwrite::Client;
use appwrite::services::Presences;

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

    let presences = Presences::new(&client);

    presences.delete("<PRESENCE_ID>").await?;

    Ok(())
}
```

### Subscribe to presence updates

Presence is most useful when other clients can react to it live. Use the `Channel.presences()` helper to subscribe to the global presences channel, or `Channel.presence('<PRESENCE_ID>')` to follow a single record. All Realtime subscriptions are gated by the [permissions system](/docs/advanced/security/permissions), so a client will only receive updates for presences it has permission to read.

```client-web
import { Client, Realtime, Channel } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

const realtime = new Realtime(client);

const subscription = await realtime.subscribe(Channel.presences(), response => {
    if (response.events.includes('presences.*.delete')) {
        console.log('Presence expired or removed', response.payload);
    } else if (response.events.includes('presences.*.upsert') || response.events.includes('presences.*.update')) {
        console.log('Presence created or updated', response.payload);
    }
});
```

```client-flutter
import 'package:appwrite/appwrite.dart';

final client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

final realtime = Realtime(client);

final subscription = realtime.subscribe([Channel.presences()]);

subscription.stream.listen((response) {
    if (response.events.contains('presences.*.delete')) {
        print('Presence expired or removed: ${response.payload}');
    } else if (response.events.contains('presences.*.upsert') || response.events.contains('presences.*.update')) {
        print('Presence created or updated: ${response.payload}');
    }
});
```

```client-apple
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

let realtime = Realtime(client)

let subscription = realtime.subscribe(channels: [Channel.presences()]) { response in
    if (response.events?.contains("presences.*.delete") == true) {
        print("Presence expired or removed: \(String(describing: response.payload))")
    } else if (response.events?.contains("presences.*.upsert") == true || response.events?.contains("presences.*.update") == true) {
        print("Presence created or updated: \(String(describing: response.payload))")
    }
}
```

```client-android-kotlin
import io.appwrite.Channel
import io.appwrite.Client
import io.appwrite.services.Realtime

val client = Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

val realtime = Realtime(client)

val subscription = realtime.subscribe(Channel.presences()) {
    if (it.events.contains("presences.*.delete")) {
        println("Presence expired or removed: ${it.payload}")
    } else if (it.events.contains("presences.*.upsert") || it.events.contains("presences.*.update")) {
        println("Presence created or updated: ${it.payload}")
    }
}
```

The `events` array follows the same pattern as every other Appwrite resource:

- `presences.*.upsert` and `presences.<PRESENCE_ID>.upsert` for the unified create-or-update path that fires on every `upsert()` call.
- `presences.*.update` and `presences.<PRESENCE_ID>.update` for status, metadata, or expiry changes made via the REST `update()` operation.
- `presences.*.delete` and `presences.<PRESENCE_ID>.delete` for records that were deleted explicitly or expired automatically.

Note that there is no separate `create` event, the `upsert` event covers both first-time creation and subsequent writes.

This gives you a clean signal for "user just came online", "user changed status", and "user went offline", without writing any custom socket logic.

### Presence channels

| Channel | Channel Helper | Description |
| --- | --- | --- |
| `presences` | `Channel.presences()` | Any upsert, update, or delete event on any presence the subscriber can read. |
| `presences.<ID>` | `Channel.presence('<PRESENCE_ID>')` | Any upsert, update, or delete event on a specific presence record. |

You can also append `.upsert()`, `.update()`, or `.delete()` to `Channel.presence('<PRESENCE_ID>')` to narrow the stream to a single event type, identical to how channel filters work on every other resource.

### Expiry and cleanup

Every presence carries an `expiresAt` timestamp. Once that time passes, Appwrite removes the record automatically and emits a `delete` event on the presence channels, so subscribers can react to "user went offline" without any explicit signal from the client that owned the presence.

You can pass an explicit `expiresAt` up to **30 days in the future**. If you omit it, Appwrite applies a sensible default that fits the typical heartbeat pattern: keep upserting the presence every few seconds while the user is active, and let it expire naturally a short time after the last heartbeat.

```client-web
import { Client, Presences, ID, Permission, Role } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

const presences = new Presences(client);

const presence = await presences.upsert({
    presenceId: ID.unique(),
    status: 'online',
    expiresAt: new Date(Date.now() + 5 * 60 * 1000).toISOString(),
    permissions: [
        Permission.read(Role.users())
    ]
});
```

```client-flutter
import 'package:appwrite/appwrite.dart';

final client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

final presences = Presences(client);

final presence = await presences.upsert(
    presenceId: ID.unique(),
    status: 'online',
    expiresAt: DateTime.now().add(Duration(minutes: 5)).toIso8601String(),
    permissions: [
        Permission.read(Role.users()),
    ],
);
```

```client-apple
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

let presences = Presences(client)

let formatter = ISO8601DateFormatter()
let presence = try await presences.upsert(
    presenceId: ID.unique(),
    status: "online",
    expiresAt: formatter.string(from: Date().addingTimeInterval(300)),
    permissions: [
        Permission.read(Role.users())
    ]
)
```

```client-android-kotlin
import io.appwrite.Client
import io.appwrite.ID
import io.appwrite.Permission
import io.appwrite.Role
import io.appwrite.services.Presences
import java.time.Instant
import java.time.temporal.ChronoUnit

val client = Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

val presences = Presences(client)

val presence = presences.upsert(
    presenceId = ID.unique(),
    status = "online",
    expiresAt = Instant.now().plus(5, ChronoUnit.MINUTES).toString(),
    permissions = listOf(
        Permission.read(Role.users())
    )
)
```

To remove a presence immediately, for example on sign out or when the user closes a document, use the [Delete a presence](#delete-a-presence) operation above.

### Permissions and scopes

Presences use the standard Appwrite [permissions system](/docs/advanced/security/permissions). Set read permissions on a presence to control who can subscribe to it:

- `Role.any()` makes the presence visible to anyone, including unauthenticated visitors.
- `Role.users()` restricts visibility to signed-in users.
- `Role.team('<TEAM_ID>')` shares the presence with a specific team, which is the right choice for collaboration features where only teammates should see each other's status.

Pass a `permissions` array to `upsert()` to attach roles to a presence. For example, to make a presence visible only to a specific team:

```client-web
import { Client, Presences, ID, Permission, Role } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

const presences = new Presences(client);

const presence = await presences.upsert({
    presenceId: ID.unique(),
    status: 'online',
    permissions: [
        Permission.read(Role.team('<TEAM_ID>'))
    ]
});
```

```client-flutter
import 'package:appwrite/appwrite.dart';

final client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

final presences = Presences(client);

final presence = await presences.upsert(
    presenceId: ID.unique(),
    status: 'online',
    permissions: [
        Permission.read(Role.team('<TEAM_ID>')),
    ],
);
```

```client-apple
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

let presences = Presences(client)

let presence = try await presences.upsert(
    presenceId: ID.unique(),
    status: "online",
    permissions: [
        Permission.read(Role.team("<TEAM_ID>"))
    ]
)
```

```client-android-kotlin
import io.appwrite.Client
import io.appwrite.ID
import io.appwrite.Permission
import io.appwrite.Role
import io.appwrite.services.Presences

val client = Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

val presences = Presences(client)

val presence = presences.upsert(
    presenceId = ID.unique(),
    status = "online",
    permissions = listOf(
        Permission.read(Role.team("<TEAM_ID>"))
    )
)
```

Server SDKs need an API key with the `presences.read` scope to list or read presences, and `presences.write` to upsert or delete them. Client sessions can always update their own presence without an extra scope.

If you do not pass a `permissions` array when upserting a presence, Appwrite defaults to giving read access only to the user who created it, so no other client can subscribe to it. To share a presence more broadly, you must set permissions explicitly.

### Use cases

The Presences API is a good fit any time you need to render "who is here right now" rather than "what has been written to storage":

- **Online indicators** in a directory or contacts list
- **Collaboration cursors** that show which document or section each teammate is viewing
- **Typing indicators** in chat or comment threads
- **Live attendee lists** for live streams, classrooms, or shared dashboards
- **Locking signals** that warn a teammate when someone else is already editing a row

For longer-lived state, like a user's profile or settings, use [account preferences](/docs/products/auth/preferences) or a row in your database instead. Presence is intentionally short-lived and self-cleaning.

### Related

- [Realtime overview](/docs/apis/realtime)
- [Realtime channels reference](/docs/apis/realtime/channels)
- [Realtime payload structure](/docs/apis/realtime/payload)
- [Authentication: Presences](/docs/products/auth/presences)

---

## Queries

URL: https://appwrite.io/docs/apis/realtime/queries

You can filter realtime events by passing queries as a third parameter when subscribing. Events are filtered server-side based on your queries, so your callback only receives updates that match your conditions. This allows you to use familiar SDK queries like `Query.equal` to automatically filter events instead of filtering manually in your callback.

```client-web
import { Client, Realtime, Channel, Query } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

const realtime = new Realtime(client);

// Subscribe to all updates
const allVotes = await realtime.subscribe(
    Channel.tablesdb('<DATABASE_ID>').table('<TABLE_ID>').row(),
    response => {
        console.log(response.payload);
    }
);

// Subscribe to updates where person equals 'person1'
const person1Votes = await realtime.subscribe(
    Channel.tablesdb('<DATABASE_ID>').table('<TABLE_ID>').row(),
    response => {
        console.log(response.payload);
    },
    [Query.equal('person', ['person1'])]
);

// Subscribe to updates where person is not 'person1'
const otherVotes = await realtime.subscribe(
    Channel.tablesdb('<DATABASE_ID>').table('<TABLE_ID>').row(),
    response => {
        console.log(response.payload);
    },
    [Query.notEqual('person', 'person1')]
);
```

```client-flutter
import 'package:appwrite/appwrite.dart';

final client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

final realtime = Realtime(client);

// Subscribe to all updates
final allVotes = realtime.subscribe(
    [Channel.tablesdb('<DATABASE_ID>').table('<TABLE_ID>').row()]
);

allVotes.stream.listen((response) {
    print(response.payload);
});

// Subscribe to updates where person equals 'person1'
final person1Votes = realtime.subscribe(
    [Channel.tablesdb('<DATABASE_ID>').table('<TABLE_ID>').row()],
    queries: [Query.equal('person', ['person1'])]
);

person1Votes.stream.listen((response) {
    print(response.payload);
});

// Subscribe to updates where person is not 'person1'
final otherVotes = realtime.subscribe(
    [Channel.tablesdb('<DATABASE_ID>').table('<TABLE_ID>').row()],
    queries: [Query.notEqual('person', 'person1')]
);

otherVotes.stream.listen((response) {
    print(response.payload);
});
```

```client-apple
import Appwrite
import AppwriteModels

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

let realtime = Realtime(client)

// Subscribe to all updates
let allVotes = realtime.subscribe(
    channels: [Channel.tablesdb("<DATABASE_ID>").table("<TABLE_ID>").row()]
) { response in
    print(String(describing: response.payload))
}

// Subscribe to updates where person equals 'person1'
let person1Votes = realtime.subscribe(
    channels: [Channel.tablesdb("<DATABASE_ID>").table("<TABLE_ID>").row()],
    callback: { response in
        print(String(describing: response.payload))
    },
    queries: [Query.equal("person", value: ["person1"])]
)

// Subscribe to updates where person is not 'person1'
let otherVotes = realtime.subscribe(
    channels: [Channel.tablesdb("<DATABASE_ID>").table("<TABLE_ID>").row()],
    callback: { response in
        print(String(describing: response.payload))
    },
    queries: [Query.notEqual("person", value: "person1")]
)
```

```client-android-kotlin
import io.appwrite.Channel
import io.appwrite.Client
import io.appwrite.Query
import io.appwrite.services.Realtime

val client = Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

val realtime = Realtime(client)

// Subscribe to all updates
val allVotes = realtime.subscribe(
    Channel.tablesdb("<DATABASE_ID>").table("<TABLE_ID>").row()
) {
    print(it.payload.toString())
}

// Subscribe to updates where person equals 'person1'
val person1Votes = realtime.subscribe(
    Channel.tablesdb("<DATABASE_ID>").table("<TABLE_ID>").row(),
    payloadType = Any::class.java,
    queries = setOf(Query.equal("person", listOf("person1")))
) {
    print(it.payload.toString())
}

// Subscribe to updates where person is not 'person1'
val otherVotes = realtime.subscribe(
    Channel.tablesdb("<DATABASE_ID>").table("<TABLE_ID>").row(),
    payloadType = Any::class.java,
    queries = setOf(Query.notEqual("person", "person1"))
) {
    print(it.payload.toString())
}
```

```client-android-java
import io.appwrite.Client;
import io.appwrite.Query;
import io.appwrite.models.RealtimeResponseEvent;
import io.appwrite.models.RealtimeSubscription;
import io.appwrite.services.Realtime;
import java.util.Arrays;
import java.util.HashSet;
import kotlin.Unit;

Client client = new Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>");

Realtime realtime = new Realtime(client);

// Subscribe to all updates
RealtimeSubscription allVotes = realtime.subscribe(
    new String[] {"tablesdb.<DATABASE_ID>.tables.<TABLE_ID>.rows"},
    (RealtimeResponseEvent<Object> response) -> {
        System.out.println(response.getPayload());
        return Unit.INSTANCE;
    }
);

// Subscribe to updates where person equals 'person1'
RealtimeSubscription person1Votes = realtime.subscribe(
    new String[] {"tablesdb.<DATABASE_ID>.tables.<TABLE_ID>.rows"},
    Object.class,
    new HashSet<>(Arrays.asList(Query.equal("person", Arrays.asList("person1")))),
    (RealtimeResponseEvent<Object> response) -> {
        System.out.println(response.getPayload());
        return Unit.INSTANCE;
    }
);

// Subscribe to updates where person is not 'person1'
RealtimeSubscription otherVotes = realtime.subscribe(
    new String[] {"tablesdb.<DATABASE_ID>.tables.<TABLE_ID>.rows"},
    Object.class,
    new HashSet<>(Arrays.asList(Query.notEqual("person", "person1"))),
    (RealtimeResponseEvent<Object> response) -> {
        System.out.println(response.getPayload());
        return Unit.INSTANCE;
    }
);
```

### Supported queries

The following query methods are supported for realtime filtering:

| Category | Queries |
| --- | --- |
| Comparison | `Query.equal()`, `Query.notEqual()`, `Query.greaterThan()`, `Query.greaterThanEqual()`, `Query.lessThan()`, `Query.lessThanEqual()` |
| Null checks | `Query.isNull()`, `Query.isNotNull()` |
| Logical | `Query.and()`, `Query.or()` |

---

## Subscribe

URL: https://appwrite.io/docs/apis/realtime/subscribe

The Appwrite Realtime API lets you subscribe to events from any Appwrite service through [channels](/docs/apis/realtime/channels). You can subscribe to a single channel, multiple channels at once, and unsubscribe when you no longer need updates. On supported client SDKs (including the Web SDK), multiple subscriptions share one WebSocket and can be added, **updated**, or removed without reconnecting the whole client until you call **`disconnect()`** on `Realtime`.

### 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.

```client-web
import { Client, Realtime, Channel } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

const realtime = new Realtime(client);

const subscription = await realtime.subscribe(Channel.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://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

final realtime = Realtime(client);

final subscription = realtime.subscribe([Channel.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://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

let realtime = Realtime(client)

let subscription = realtime.subscribe(channels: [Channel.account()]) { response in
    // Callback will be executed on all account events.
    print(String(describing: response))
}
```

```client-android-kotlin
import io.appwrite.Channel
import io.appwrite.Client
import io.appwrite.services.Realtime

val client = Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

val realtime = Realtime(client)

val subscription = realtime.subscribe(Channel.account()) {
    // Callback will be executed on all account events.
    print(it.payload.toString())
}
```

```client-android-java
import io.appwrite.Client;
import io.appwrite.models.RealtimeResponseEvent;
import io.appwrite.models.RealtimeSubscription;
import io.appwrite.services.Realtime;
import kotlin.Unit;

Client client = new Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>");

Realtime realtime = new Realtime(client);

RealtimeSubscription subscription = realtime.subscribe(
    new String[] {"account"},
    (RealtimeResponseEvent<Object> response) -> {
        // Callback will be executed on all account events.
        System.out.println(response);
        return Unit.INSTANCE;
    }
);
```

### Subscribe to multiple channels

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 `Channel.tablesdb("<DATABASE_ID>").table("<TABLE_ID>").row("<ROW_ID>")` and `Channel.files()` channels.

```client-web
import { Client, Realtime, Channel } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

const realtime = new Realtime(client);

const subscription = await realtime.subscribe([
    Channel.tablesdb('<DATABASE_ID>').table('<TABLE_ID>').row('<ROW_ID>'),
    Channel.files()
], response => {
    // Callback will be executed on changes for the row and all files.
    console.log(response);
});
```

```client-flutter
import 'package:appwrite/appwrite.dart';

final client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

final realtime = Realtime(client);

final subscription = realtime.subscribe([
    Channel.tablesdb('<DATABASE_ID>').table('<TABLE_ID>').row('<ROW_ID>'),
    Channel.files()
]);

subscription.stream.listen((response) {
    // Callback will be executed on changes for the row and all files.
    print(response);
});
```

```client-apple
import Appwrite
import AppwriteModels

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

let realtime = Realtime(client)

realtime.subscribe(channels: [
    Channel.tablesdb("<DATABASE_ID>").table("<TABLE_ID>").row("<ROW_ID>"),
    Channel.files()
]) { response in
    // Callback will be executed on changes for the row and all files.
    print(String(describing: response))
}
```

```client-android-kotlin
import io.appwrite.Channel
import io.appwrite.Client
import io.appwrite.services.Realtime

val client = Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
val realtime = Realtime(client)

realtime.subscribe(
    Channel.tablesdb("<DATABASE_ID>").table("<TABLE_ID>").row("<ROW_ID>"),
    Channel.files()
) {
    // Callback will be executed on changes for the row and all files.
    print(it.toString())
}
```

```client-android-java
import io.appwrite.Client;
import io.appwrite.models.RealtimeResponseEvent;
import io.appwrite.models.RealtimeSubscription;
import io.appwrite.services.Realtime;
import kotlin.Unit;

Client client = new Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>");
Realtime realtime = new Realtime(client);

RealtimeSubscription subscription = realtime.subscribe(
    new String[] {
        "tablesdb.<DATABASE_ID>.tables.<TABLE_ID>.rows.<ROW_ID>",
        "files"
    },
    (RealtimeResponseEvent<Object> response) -> {
        // Callback will be executed on changes for the row and all files.
        System.out.println(response);
        return Unit.INSTANCE;
    }
);
```

### Update channels or queries

Channels and queries on an active subscription can be replaced in place without recreating the WebSocket. This is useful when, for example, a user changes which row they're viewing. Swap the channel on the existing subscription instead of unsubscribing and resubscribing.

`update()` accepts either or both of `channels` and `queries`. Pass only the field you want to replace; omitted fields are left unchanged.

```client-web
import { Client, Realtime, Channel } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

const realtime = new Realtime(client);

const subscription = await realtime.subscribe(
    Channel.tablesdb('<DATABASE_ID>').table('<TABLE_ID>').row('<ROW_ID>'),
    response => console.log(response),
);

// Switch to a different row — no reconnect required.
await subscription.update({
    channels: [Channel.tablesdb('<DATABASE_ID>').table('<TABLE_ID>').row('<NEW_ROW_ID>')],
});
```

```client-flutter
import 'package:appwrite/appwrite.dart';

final client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

final realtime = Realtime(client);

final subscription = realtime.subscribe([
    Channel.tablesdb('<DATABASE_ID>').table('<TABLE_ID>').row('<ROW_ID>'),
]);

subscription.stream.listen((response) {
    print(response);
});

// Switch to a different row — no reconnect required.
await subscription.update(
    channels: [Channel.tablesdb('<DATABASE_ID>').table('<TABLE_ID>').row('<NEW_ROW_ID>')],
);
```

```client-apple
import Appwrite
import AppwriteModels

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

let realtime = Realtime(client)

let subscription = realtime.subscribe(channels: [
    Channel.tablesdb("<DATABASE_ID>").table("<TABLE_ID>").row("<ROW_ID>"),
]) { response in
    print(String(describing: response))
}

// Switch to a different row — no reconnect required.
try await subscription.update(RealtimeSubscriptionUpdate(
    channels: [Channel.tablesdb("<DATABASE_ID>").table("<TABLE_ID>").row("<NEW_ROW_ID>")]
))
```

```client-android-kotlin
import io.appwrite.Channel
import io.appwrite.Client
import io.appwrite.services.Realtime
import io.appwrite.models.RealtimeSubscriptionUpdate

val client = Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

val realtime = Realtime(client)

val subscription = realtime.subscribe(
    Channel.tablesdb("<DATABASE_ID>").table("<TABLE_ID>").row("<ROW_ID>"),
) {
    print(it.toString())
}

// Switch to a different row — no reconnect required.
subscription.update(RealtimeSubscriptionUpdate(
    channels = listOf(Channel.tablesdb("<DATABASE_ID>").table("<TABLE_ID>").row("<NEW_ROW_ID>")),
))
```

```client-android-java
import io.appwrite.Client;
import io.appwrite.models.RealtimeResponseEvent;
import io.appwrite.models.RealtimeSubscription;
import io.appwrite.models.RealtimeSubscriptionUpdate;
import io.appwrite.services.Realtime;
import kotlin.Unit;

import java.util.Arrays;

Client client = new Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>");

Realtime realtime = new Realtime(client);

RealtimeSubscription subscription = realtime.subscribe(
    new String[] {"tablesdb.<DATABASE_ID>.tables.<TABLE_ID>.rows.<ROW_ID>"},
    (RealtimeResponseEvent<Object> response) -> {
        System.out.println(response);
        return Unit.INSTANCE;
    }
);

// Switch to a different row — no reconnect required.
subscription.update(new RealtimeSubscriptionUpdate(
    Arrays.asList("tablesdb.<DATABASE_ID>.tables.<TABLE_ID>.rows.<NEW_ROW_ID>"),
    null
));
```

### Unsubscribe

If you no longer want to receive updates from a particular subscription, call `unsubscribe()` on it. Other subscriptions and the underlying WebSocket connection are not affected, so callbacks for the rest of your app keep firing. To close the entire connection at once, see [Disconnect](#disconnect) below.

```client-web
import { Client, Realtime, Channel } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

const realtime = new Realtime(client);

const subscription = await realtime.subscribe(Channel.files(), response => {
    // Callback will be executed on changes for all files.
    console.log(response);
});

// Removes only this subscription. Other subscriptions and the WebSocket stay open.
await subscription.unsubscribe();
```

```client-flutter
import 'package:appwrite/appwrite.dart';

final client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

final realtime = Realtime(client);

final subscription = realtime.subscribe([Channel.files()]);

subscription.stream.listen((response) {
    // Callback will be executed on changes for all files.
    print(response);
});

// Removes only this subscription. Other subscriptions and the WebSocket stay open.
await subscription.unsubscribe();
```

```client-apple
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

let realtime = Realtime(client)

let subscription = realtime.subscribe(channels: [Channel.files()]) { response in
    // Callback will be executed on changes for all files.
    print(response.toString())
}

// Removes only this subscription. Other subscriptions and the WebSocket stay open.
try await subscription.unsubscribe()
```

```client-android-kotlin
import io.appwrite.Channel
import io.appwrite.Client
import io.appwrite.services.Realtime

val client = Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

val realtime = Realtime(client)

val subscription = realtime.subscribe(Channel.files()) {
    // Callback will be executed on changes for all files.
    print(it.toString())
}

// Removes only this subscription. Other subscriptions and the WebSocket stay open.
subscription.unsubscribe()
```

```client-android-java
import io.appwrite.Client;
import io.appwrite.models.RealtimeResponseEvent;
import io.appwrite.models.RealtimeSubscription;
import io.appwrite.services.Realtime;
import kotlin.Unit;

Client client = new Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>");

Realtime realtime = new Realtime(client);

RealtimeSubscription subscription = realtime.subscribe(
    new String[] {"files"},
    (RealtimeResponseEvent<Object> response) -> {
        // Callback will be executed on changes for all files.
        System.out.println(response);
        return Unit.INSTANCE;
    }
);

// Removes only this subscription. Other subscriptions and the WebSocket stay open.
subscription.unsubscribe();
```

**Legacy close()**

`subscription.close()` still works for backwards compatibility. It calls `unsubscribe()` and additionally closes the WebSocket if this was the last active subscription. New code should prefer `unsubscribe()` and call `realtime.disconnect()` explicitly when full teardown is needed.

### Disconnect

Call `realtime.disconnect()` to drop **all** active subscriptions and close the WebSocket in one step. Use this at app teardown or when a user logs out.

```client-web
import { Client, Realtime } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

const realtime = new Realtime(client);

// ... subscribe to one or more channels ...

// Drop all subscriptions and close the WebSocket.
await realtime.disconnect();
```

```client-flutter
import 'package:appwrite/appwrite.dart';

final client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

final realtime = Realtime(client);

// ... subscribe to one or more channels ...

// Drop all subscriptions and close the WebSocket.
await realtime.disconnect();
```

```client-apple
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

let realtime = Realtime(client)

// ... subscribe to one or more channels ...

// Drop all subscriptions and close the WebSocket.
try await realtime.disconnect()
```

```client-android-kotlin
import io.appwrite.Client
import io.appwrite.services.Realtime

val client = Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

val realtime = Realtime(client)

// ... subscribe to one or more channels ...

// Drop all subscriptions and close the WebSocket.
realtime.disconnect()
```

```client-android-java
import io.appwrite.Client;
import io.appwrite.services.Realtime;

Client client = new Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>");

Realtime realtime = new Realtime(client);

// ... subscribe to one or more channels ...

// Drop all subscriptions and close the WebSocket.
realtime.disconnect();
```

---

## Release policy

URL: https://appwrite.io/docs/apis/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

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
Appwrite will provide two phases of continued support for older versions of Appwrite.

| Phase | Scope |
| --- | --- |
| Support | Receive continued bug fixes and security updates. |
| Extended security support | Receives only security updates. |

### 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.
| Releases | 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 |
### 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

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

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.

[Learn more about updating self-hosted Appwrite](/docs/advanced/self-hosting/update)

### 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

| Language | 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/) |

#### Runtimes OS
| OS | 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/) |

---

## Response codes

URL: https://appwrite.io/docs/apis/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
| 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/security/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

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

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 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
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
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
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
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
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
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
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. |

### Error handling

The response codes and [error types](#error-types) above are the building blocks for handling errors. The practices below help you turn them into a recoverable, user-friendly experience.

#### Return user-friendly messages

Avoid surfacing Appwrite's raw error messages directly to your users. They're written for developers and can expose implementation details or confuse non-technical users. Instead, catch the error, inspect its `type`, and map it to a message that fits your application.

The `type` field is more specific than the HTTP status code, so it lets you respond precisely: a `400` can mean many things, but `user_invalid_credentials` means exactly one. Matching on `type` also lets you treat a transient error like `general_rate_limit_exceeded` differently from a permanent one.

| Error type | User-friendly message |
|------------|----------------------|
| `user_invalid_credentials` | "The email or password you entered is incorrect. Please try again." |
| `user_blocked` | "Your account has been temporarily suspended. Please contact support." |
| `general_rate_limit_exceeded` | "Please wait a moment before trying again." |
| `storage_file_not_found` | "The file you requested is not available." |
| `row_not_found` | "The information you're looking for could not be found." |

#### Recommended practices

- **Log the full error server-side** for debugging, while showing only the friendly message to users.
- **Handle common scenarios explicitly** and offer clear next steps, such as "Try resetting your password."
- **Retry transient errors** like rate limiting (`429`) or service unavailability (`503`), ideally with backoff.
- **Keep error messaging consistent** in tone and styling across your application.

---

## REST

URL: 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

Appwrite's REST APIs expect certain headers to be included with each request:

- 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.

---

- X-Appwrite-Impersonate-User-Id: [USER-ID]
- optional
- Resolves the effective user for an already authenticated impersonator request by Appwrite user ID. Only works when the authenticated user has impersonation enabled.

---

- X-Appwrite-Impersonate-User-Email: [EMAIL]
- optional
- Resolves the effective user for an already authenticated impersonator request by email address. Only works when the authenticated user has impersonation enabled.

---

- X-Appwrite-Impersonate-User-Phone: [PHONE]
- optional
- Resolves the effective user for an already authenticated impersonator request by phone number. Only works when the authenticated user has impersonation enabled.

### 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

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: <PROJECT_ID>

{
  "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: <PROJECT_ID>
```

#### 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/partners/project/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: <PROJECT_ID>
X-Appwrite-Key: [API_KEY]
```

#### 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: <PROJECT_ID>
X-Appwrite-JWT: [TOKEN]
```

#### Impersonation headers

When a request is already authenticated as a user with impersonation enabled, you can add one of Appwrite's impersonation headers to resolve a different effective user for that request.

Use exactly one of these headers:

- `X-Appwrite-Impersonate-User-Id`
- `X-Appwrite-Impersonate-User-Email`
- `X-Appwrite-Impersonate-User-Phone`

These headers are ignored for plain API key requests. They are only honored when the request already belongs to a signed-in user who has been marked as an impersonator in the Appwrite Console or through the Users API.

If you are using an Appwrite SDK instead of raw REST calls, use the corresponding client setters rather than manually attaching these headers.

Learn more in the [user impersonation docs](/docs/products/auth/impersonation).

### Files

Appwrite implements resumable, chunked uploads for files larger than 5MB. Each chunk is up to **5MB**, which reduces memory footprint and increases resilience when handling large files. [Appwrite SDKs](/docs/sdks) split uploads, attach the headers below, and on runtimes with native concurrency they may also **send multiple chunk requests in parallel** for higher throughput, while your high-level upload code stays unchanged. You can still implement chunked uploads 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 over REST, send **one HTTP request per chunk** using the headers in the tables below. The **first** request establishes the file; every later chunk must repeat the same file **id** in `X-Appwrite-ID` and set `Content-Range` to the byte span carried in that request. After the first response, you may **issue the remaining chunk requests sequentially or in parallel** (for example from a thread pool or async tasks), as long as each byte of the file is uploaded **exactly once** and each part stays within the maximum chunk size. For Storage-specific guidance (including SDK behavior), see [Upload and download](/docs/products/storage/upload-download#large-files).

- 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.

The multipart form data is structured as follows:

- 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.

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

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
<img src="[ENDPOINT]/v1/storage/buckets/[BUCKET_ID]/files/[FILE_ID]/preview?project=<PROJECT_ID>"/>
```

### Permissions

Appwrite SDKs have helpers to generate permission string formats, but when using Appwrite without SDKs, you'd need to create the strings yourself.

- Query method
- API string

---

- `Permission.read()`
- `read("<PERMISSION_ROLE>")`

---

- `Permission.create()`
- `read("<PERMISSION_ROLE>")`

---

- `Permission.update()`
- `update("<PERMISSION_ROLE>")`

---

- `Permission.delete()`
- `delete("<PERMISSION_ROLE>")`

---

- `Permission.write()`
- `write("<PERMISSION_ROLE>")`

#### Roles

Appwrite SDKs have helpers to generate roles string formats, but when using Appwrite without SDKs, you'd need to create the strings yourself.

- 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]`

### 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

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

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": "<QUERY_METHOD>",
  "column": "<ATTRIBUTE>",
  "values": [
    <VALUE1>,
    <VALUE2>,
    ...
  ]
}
```

**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.

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

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

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/security/rate-limits).

### 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).

---

## Webhooks

URL: https://appwrite.io/docs/apis/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

To add a webhook from the Appwrite Console:

1. Navigate to your project's **Settings** page.
2. Select the **Webhooks** tab.
3. Click **Create webhook**.
4. Enter a **Name** and the **POST URL** for your webhook endpoint.
5. Click **Add an event** to choose the [events](#events) that should trigger the webhook.
6. Optionally, enable **Certificate verification (SSL/TLS)** and set **HTTP authentication** credentials to secure your endpoint.
7. Click **Create webhook**.

![Create webhook screen](/images/docs/platform/create-webhook.avif)

### Manage webhooks with a Server SDK

You can also manage webhooks programmatically using a Server SDK. This requires an API key with the `webhooks.read` and `webhooks.write` [scopes](/docs/partners/project/api-keys#scopes).

#### Create a webhook

The response includes a `secret` field containing the webhook's signing key. This is the only time the `secret` is returned, so store it securely right away.

```server-nodejs
import { Client, Webhooks, ID } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const webhooks = new Webhooks(client);

const result = await webhooks.create({
    webhookId: ID.unique(),
    url: 'https://example.com/webhook',
    name: 'My Webhook',
    events: ['users.*.create'],
    tls: true,
    secret: '<SECRET>' // optional
});

console.log(result.secret); // store it now, not returned again
```
```server-deno
import { Client, Webhooks, ID } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const webhooks = new Webhooks(client);

const result = await webhooks.create({
    webhookId: ID.unique(),
    url: 'https://example.com/webhook',
    name: 'My Webhook',
    events: ['users.*.create'],
    tls: true,
    secret: '<SECRET>' // optional
});

console.log(result.secret); // store it now, not returned again
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\ID;
use Appwrite\Services\Webhooks;

$client = new Client();

$client
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$webhooks = new Webhooks($client);

$result = $webhooks->create(
    webhookId: ID::unique(),
    url: 'https://example.com/webhook',
    name: 'My Webhook',
    events: ['users.*.create'],
    tls: true,
    secret: '<SECRET>' // optional
);

echo $result->secret; // store it now, not returned again
```
```server-python
from appwrite.client import Client
from appwrite.id import ID
from appwrite.services.webhooks import Webhooks

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

webhooks = Webhooks(client)

result = webhooks.create(
    webhook_id=ID.unique(),
    url='https://example.com/webhook',
    name='My Webhook',
    events=['users.*.create'],
    tls=True,
    secret='<SECRET>' # optional
)

print(result.secret) # store it now, not returned again
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

webhooks = Webhooks.new(client)

response = webhooks.create(
    webhook_id: ID.unique(),
    url: 'https://example.com/webhook',
    name: 'My Webhook',
    events: ['users.*.create'],
    tls: true,
    secret: '<SECRET>' # optional
)

puts response.secret # store it now, not returned again
```
```server-dotnet
using Appwrite;
using Appwrite.Services;
using Appwrite.Models;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Webhooks webhooks = new Webhooks(client);

var result = await webhooks.Create(
    webhookId: ID.Unique(),
    url: "https://example.com/webhook",
    name: "My Webhook",
    events: new List<string> {"users.*.create"},
    tls: true,
    secret: "<SECRET>" // optional
);

Console.WriteLine(result.Secret); // store it now, not returned again
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Webhooks webhooks = Webhooks(client);

final result = await webhooks.create(
    webhookId: ID.unique(),
    url: 'https://example.com/webhook',
    name: 'My Webhook',
    events: ['users.*.create'],
    tls: true,
    secret: '<SECRET>', // optional
);

print(result.secret); // store it now, not returned again
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.ID
import io.appwrite.services.Webhooks

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val webhooks = Webhooks(client)

val result = webhooks.create(
    webhookId = ID.unique(),
    url = "https://example.com/webhook",
    name = "My Webhook",
    events = listOf("users.*.create"),
    tls = true,
    secret = "<SECRET>" // optional
)

println(result.secret) // store it now, not returned again
```
```server-java
import io.appwrite.Client;
import io.appwrite.ID;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Webhooks;

Client client = new Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>");

Webhooks webhooks = new Webhooks(client);

webhooks.create(
    ID.unique(),                                     // webhookId
    "https://example.com/webhook",                   // url
    "My Webhook",                                    // name
    List.of("users.*.create"), // events
    true,                                            // enabled
    true,                                            // tls
    null,                                            // authUsername
    null,                                            // authPassword
    "<SECRET>",                                      // secret (optional)
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        System.out.println(result.getSecret()); // store it now, not returned again
    })
);
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let webhooks = Webhooks(client)

let result = try await webhooks.create(
    webhookId: ID.unique(),
    url: "https://example.com/webhook",
    name: "My Webhook",
    events: ["users.*.create"],
    tls: true,
    secret: "<SECRET>" // optional
)

print(result.secret) // store it now, not returned again
```
```server-go
package main

import (
    "fmt"
    "github.com/appwrite/sdk-for-go/appwrite"
    "github.com/appwrite/sdk-for-go/id"
)

func main() {
    client := appwrite.NewClient(
        appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"),
        appwrite.WithProject("<PROJECT_ID>"),
        appwrite.WithKey("<YOUR_API_KEY>"),
    )

    webhooks := appwrite.NewWebhooks(client)
    result, err := webhooks.Create(
        id.Unique(),
        "https://example.com/webhook",
        "My Webhook",
        []string{"users.*.create"},
        appwrite.WithCreateSecret("<SECRET>"), // optional
    )

    if err != nil {
        panic(err)
    }

    fmt.Println(result.Secret) // store it now, not returned again
}
```
```server-rust
use appwrite::Client;
use appwrite::id::ID;
use appwrite::services::Webhooks;

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

    let webhooks = Webhooks::new(&client);

    let result = webhooks.create(
        ID::unique(),                                  // webhook_id
        "https://example.com/webhook",                 // url
        "My Webhook",                                  // name
        vec!["users.*.create"], // events
        Some(true),                                    // enabled
        Some(true),                                    // tls
        None,                                          // auth_username
        None,                                          // auth_password
        Some("<SECRET>"),                              // secret (optional)
    ).await?;

    println!("{}", result.secret); // store it now, not returned again
    Ok(())
}
```

#### List webhooks

```server-nodejs
import { Client, Webhooks } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const webhooks = new Webhooks(client);

const result = await webhooks.list({
    queries: [], // optional
    total: false // optional
});
```
```server-deno
import { Client, Webhooks } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const webhooks = new Webhooks(client);

const result = await webhooks.list({
    queries: [], // optional
    total: false // optional
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Webhooks;

$client = new Client();

$client
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$webhooks = new Webhooks($client);

$result = $webhooks->list(
    queries: [], // optional
    total: false // optional
);
```
```server-python
from appwrite.client import Client
from appwrite.services.webhooks import Webhooks

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

webhooks = Webhooks(client)

result = webhooks.list(
    queries=[], # optional
    total=False # optional
)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

webhooks = Webhooks.new(client)

response = webhooks.list(
    queries: [], # optional
    total: false # optional
)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;
using Appwrite.Models;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Webhooks webhooks = new Webhooks(client);

var result = await webhooks.List(
    queries: new List<string>(), // optional
    total: false // optional
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Webhooks webhooks = Webhooks(client);

final result = await webhooks.list(
    queries: [], // optional
    total: false, // optional
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Webhooks

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val webhooks = Webhooks(client)

val result = webhooks.list(
    queries = listOf(), // optional
    total = false // optional
)
```
```server-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Webhooks;

Client client = new Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>");

Webhooks webhooks = new Webhooks(client);

webhooks.list(
    List.of(), // queries (optional)
    false, // total (optional)
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }
        System.out.println(result);
    })
);
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let webhooks = Webhooks(client)

let result = try await webhooks.list(
    queries: [], // optional
    total: false // optional
)
```
```server-go
package main

import (
    "fmt"
    "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("<YOUR_API_KEY>"),
    )

    webhooks := appwrite.NewWebhooks(client)
    result, err := webhooks.List()

    if err != nil {
        panic(err)
    }

    fmt.Println(result)
}
```
```server-rust
use appwrite::Client;
use appwrite::services::Webhooks;

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

    let webhooks = Webhooks::new(&client);

    let result = webhooks.list(
        Some(vec![]), // queries (optional)
        Some(false), // total (optional)
    ).await?;

    println!("{:?}", result);
    Ok(())
}
```

#### Get a webhook

```server-nodejs
import { Client, Webhooks } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const webhooks = new Webhooks(client);

const result = await webhooks.get({
    webhookId: '<WEBHOOK_ID>'
});
```
```server-deno
import { Client, Webhooks } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const webhooks = new Webhooks(client);

const result = await webhooks.get({
    webhookId: '<WEBHOOK_ID>'
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Webhooks;

$client = new Client();

$client
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$webhooks = new Webhooks($client);

$result = $webhooks->get(
    webhookId: '<WEBHOOK_ID>'
);
```
```server-python
from appwrite.client import Client
from appwrite.services.webhooks import Webhooks

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

webhooks = Webhooks(client)

result = webhooks.get(
    webhook_id='<WEBHOOK_ID>'
)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

webhooks = Webhooks.new(client)

response = webhooks.get(
    webhook_id: '<WEBHOOK_ID>'
)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;
using Appwrite.Models;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Webhooks webhooks = new Webhooks(client);

var result = await webhooks.Get(
    webhookId: "<WEBHOOK_ID>"
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Webhooks webhooks = Webhooks(client);

final result = await webhooks.get(
    webhookId: '<WEBHOOK_ID>',
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Webhooks

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val webhooks = Webhooks(client)

val result = webhooks.get(
    webhookId = "<WEBHOOK_ID>"
)
```
```server-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Webhooks;

Client client = new Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>");

Webhooks webhooks = new Webhooks(client);

webhooks.get(
    "<WEBHOOK_ID>", // webhookId
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }
        System.out.println(result);
    })
);
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let webhooks = Webhooks(client)

let result = try await webhooks.get(
    webhookId: "<WEBHOOK_ID>"
)
```
```server-go
package main

import (
    "fmt"
    "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("<YOUR_API_KEY>"),
    )

    webhooks := appwrite.NewWebhooks(client)
    result, err := webhooks.Get(
        "<WEBHOOK_ID>",
    )

    if err != nil {
        panic(err)
    }

    fmt.Println(result)
}
```
```server-rust
use appwrite::Client;
use appwrite::services::Webhooks;

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

    let webhooks = Webhooks::new(&client);

    let result = webhooks.get(
        "<WEBHOOK_ID>", // webhook_id
    ).await?;

    println!("{:?}", result);
    Ok(())
}
```

#### Update a webhook

```server-nodejs
import { Client, Webhooks } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const webhooks = new Webhooks(client);

const result = await webhooks.update({
    webhookId: '<WEBHOOK_ID>',
    name: 'Updated Webhook',
    url: 'https://example.com/webhook-updated',
    events: ['users.*.update'],
    tls: true
});
```
```server-deno
import { Client, Webhooks } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const webhooks = new Webhooks(client);

const result = await webhooks.update({
    webhookId: '<WEBHOOK_ID>',
    name: 'Updated Webhook',
    url: 'https://example.com/webhook-updated',
    events: ['users.*.update'],
    tls: true
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Webhooks;

$client = new Client();

$client
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$webhooks = new Webhooks($client);

$result = $webhooks->update(
    webhookId: '<WEBHOOK_ID>',
    name: 'Updated Webhook',
    url: 'https://example.com/webhook-updated',
    events: ['users.*.update'],
    tls: true
);
```
```server-python
from appwrite.client import Client
from appwrite.services.webhooks import Webhooks

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

webhooks = Webhooks(client)

result = webhooks.update(
    webhook_id='<WEBHOOK_ID>',
    name='Updated Webhook',
    url='https://example.com/webhook-updated',
    events=['users.*.update'],
    tls=True
)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

webhooks = Webhooks.new(client)

response = webhooks.update(
    webhook_id: '<WEBHOOK_ID>',
    name: 'Updated Webhook',
    url: 'https://example.com/webhook-updated',
    events: ['users.*.update'],
    tls: true
)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;
using Appwrite.Models;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Webhooks webhooks = new Webhooks(client);

var result = await webhooks.Update(
    webhookId: "<WEBHOOK_ID>",
    name: "Updated Webhook",
    url: "https://example.com/webhook-updated",
    events: new List<string> {"users.*.update"},
    tls: true
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Webhooks webhooks = Webhooks(client);

final result = await webhooks.update(
    webhookId: '<WEBHOOK_ID>',
    name: 'Updated Webhook',
    url: 'https://example.com/webhook-updated',
    events: ['users.*.update'],
    tls: true,
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Webhooks

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val webhooks = Webhooks(client)

val result = webhooks.update(
    webhookId = "<WEBHOOK_ID>",
    name = "Updated Webhook",
    url = "https://example.com/webhook-updated",
    events = listOf("users.*.update"),
    tls = true
)
```
```server-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Webhooks;

Client client = new Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>");

Webhooks webhooks = new Webhooks(client);

webhooks.update(
    "<WEBHOOK_ID>",                                  // webhookId
    "Updated Webhook",                               // name
    "https://example.com/webhook-updated",           // url
    List.of("users.*.update"), // events
    true,                                            // enabled
    true,                                            // tls
    null,                                            // authUsername
    null,                                            // authPassword
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }
        System.out.println(result);
    })
);
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let webhooks = Webhooks(client)

let result = try await webhooks.update(
    webhookId: "<WEBHOOK_ID>",
    name: "Updated Webhook",
    url: "https://example.com/webhook-updated",
    events: ["users.*.update"],
    tls: true
)
```
```server-go
package main

import (
    "fmt"
    "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("<YOUR_API_KEY>"),
    )

    webhooks := appwrite.NewWebhooks(client)
    result, err := webhooks.Update(
        "<WEBHOOK_ID>",
        "Updated Webhook",
        "https://example.com/webhook-updated",
        []string{"users.*.update"},
    )

    if err != nil {
        panic(err)
    }

    fmt.Println(result)
}
```
```server-rust
use appwrite::Client;
use appwrite::services::Webhooks;

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

    let webhooks = Webhooks::new(&client);

    let result = webhooks.update(
        "<WEBHOOK_ID>",                                // webhook_id
        "Updated Webhook",                             // name
        "https://example.com/webhook-updated",         // url
        vec!["users.*.update"], // events
        Some(true),                                    // enabled
        Some(true),                                    // tls
        None,                                          // auth_username
        None,                                          // auth_password
    ).await?;

    println!("{:?}", result);
    Ok(())
}
```

#### Delete a webhook

```server-nodejs
import { Client, Webhooks } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const webhooks = new Webhooks(client);

await webhooks.delete({
    webhookId: '<WEBHOOK_ID>'
});
```
```server-deno
import { Client, Webhooks } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const webhooks = new Webhooks(client);

await webhooks.delete({
    webhookId: '<WEBHOOK_ID>'
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Webhooks;

$client = new Client();

$client
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$webhooks = new Webhooks($client);

$webhooks->delete(
    webhookId: '<WEBHOOK_ID>'
);
```
```server-python
from appwrite.client import Client
from appwrite.services.webhooks import Webhooks

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

webhooks = Webhooks(client)

webhooks.delete(
    webhook_id='<WEBHOOK_ID>'
)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

webhooks = Webhooks.new(client)

webhooks.delete(
    webhook_id: '<WEBHOOK_ID>'
)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Webhooks webhooks = new Webhooks(client);

await webhooks.Delete(
    webhookId: "<WEBHOOK_ID>"
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Webhooks webhooks = Webhooks(client);

await webhooks.delete(
    webhookId: '<WEBHOOK_ID>',
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Webhooks

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val webhooks = Webhooks(client)

webhooks.delete(
    webhookId = "<WEBHOOK_ID>"
)
```
```server-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Webhooks;

Client client = new Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>");

Webhooks webhooks = new Webhooks(client);

webhooks.delete(
    "<WEBHOOK_ID>",                                  // webhookId
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }
        System.out.println(result);
    })
);
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let webhooks = Webhooks(client)

try await webhooks.delete(
    webhookId: "<WEBHOOK_ID>"
)
```
```server-go
package main

import (
    "fmt"
    "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("<YOUR_API_KEY>"),
    )

    webhooks := appwrite.NewWebhooks(client)
    _, err := webhooks.Delete(
        "<WEBHOOK_ID>",
    )

    if err != nil {
        panic(err)
    }

    fmt.Println("Webhook deleted")
}
```
```server-rust
use appwrite::Client;
use appwrite::services::Webhooks;

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

    let webhooks = Webhooks::new(&client);

    webhooks.delete(
        "<WEBHOOK_ID>",
    ).await?;

    Ok(())
}
```

#### Rotate signing key

Calling `updateSecret` without a value generates a new random signing key. You can optionally provide your own `secret` (8-256 characters) to set a specific key, which is useful for zero-downtime key rotation.

```server-nodejs
import { Client, Webhooks } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const webhooks = new Webhooks(client);

const result = await webhooks.updateSecret({
    webhookId: '<WEBHOOK_ID>',
    secret: '<SECRET>' // optional
});
```
```server-deno
import { Client, Webhooks } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const webhooks = new Webhooks(client);

const result = await webhooks.updateSecret({
    webhookId: '<WEBHOOK_ID>',
    secret: '<SECRET>' // optional
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Webhooks;

$client = new Client();

$client
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$webhooks = new Webhooks($client);

$result = $webhooks->updateSecret(
    webhookId: '<WEBHOOK_ID>',
    secret: '<SECRET>' // optional
);
```
```server-python
from appwrite.client import Client
from appwrite.services.webhooks import Webhooks

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

webhooks = Webhooks(client)

result = webhooks.update_secret(
    webhook_id='<WEBHOOK_ID>',
    secret='<SECRET>' # optional
)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

webhooks = Webhooks.new(client)

response = webhooks.update_secret(
    webhook_id: '<WEBHOOK_ID>',
    secret: '<SECRET>' # optional
)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Webhooks webhooks = new Webhooks(client);

var result = await webhooks.UpdateSecret(
    webhookId: "<WEBHOOK_ID>",
    secret: "<SECRET>" // optional
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Webhooks webhooks = Webhooks(client);

final result = await webhooks.updateSecret(
    webhookId: '<WEBHOOK_ID>',
    secret: '<SECRET>', // optional
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Webhooks

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val webhooks = Webhooks(client)

val result = webhooks.updateSecret(
    webhookId = "<WEBHOOK_ID>",
    secret = "<SECRET>" // optional
)
```
```server-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Webhooks;

Client client = new Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>");

Webhooks webhooks = new Webhooks(client);

webhooks.updateSecret(
    "<WEBHOOK_ID>", // webhookId
    "<SECRET>", // secret (optional)
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }
        System.out.println(result);
    })
);
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let webhooks = Webhooks(client)

let result = try await webhooks.updateSecret(
    webhookId: "<WEBHOOK_ID>",
    secret: "<SECRET>" // optional
)
```
```server-go
package main

import (
    "fmt"
    "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("<YOUR_API_KEY>"),
    )

    webhooks := appwrite.NewWebhooks(client)
    result, err := webhooks.UpdateSecret(
        "<WEBHOOK_ID>",
        appwrite.WithUpdateSecretSecret("<SECRET>"), // optional
    )

    if err != nil {
        panic(err)
    }

    fmt.Println(result)
}
```
```server-rust
use appwrite::Client;
use appwrite::services::Webhooks;

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

    let webhooks = Webhooks::new(&client);

    let result = webhooks.update_secret(
        "<WEBHOOK_ID>", // webhook_id
        Some("<SECRET>") // optional
    ).await?;

    println!("{:?}", result);
    Ok(())
}
```

### 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/apis/events).

### 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/apis/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

You can verify that a webhook request genuinely came from your Appwrite instance using the `X-Appwrite-Webhook-Signature` header. The signature key can be found in your webhook's properties in the Appwrite Console.

To verify the signature:

1. Concatenate the **webhook URL** and the **request body** (no spaces in between).
2. Generate an HMAC-SHA1 hash of the concatenated string using your webhook's **signature key**.
3. Base64 encode the resulting hash.
4. Compare the result to the `X-Appwrite-Webhook-Signature` header value. If they match, the payload is authentic.

```server-nodejs
import crypto from 'crypto';

function verifyWebhook(req, signatureKey) {
    const url = 'https://example.com/webhook'; // Your webhook URL
    const payload = req.body; // Raw request body as string
    const signature = req.headers['x-appwrite-webhook-signature'];

    const generated = crypto
        .createHmac('sha1', signatureKey)
        .update(url + payload)
        .digest('base64');

    return crypto.timingSafeEqual(
        Buffer.from(generated),
        Buffer.from(signature)
    );
}
```
```server-python
import hmac
import hashlib
import base64

def verify_webhook(url, payload, signature_key, signature):
    generated = base64.b64encode(
        hmac.new(
            signature_key.encode(),
            (url + payload).encode(),
            hashlib.sha1
        ).digest()
    ).decode()

    return hmac.compare_digest(generated, signature)
```
```server-php
<?php

function verifyWebhook(
    string $url,
    string $payload,
    string $signatureKey,
    string $signature
): bool {
    $generated = base64_encode(
        hash_hmac('sha1', $url . $payload, $signatureKey, true)
    );

    return hash_equals($generated, $signature);
}
```
```server-ruby
require 'openssl'
require 'base64'

def verify_webhook(url, payload, signature_key, signature)
  generated = Base64.strict_encode64(
    OpenSSL::HMAC.digest('sha1', signature_key, url + payload)
  )

  OpenSSL.secure_compare(generated, signature)
end
```
```server-dart
import 'dart:convert';
import 'package:crypto/crypto.dart';

bool verifyWebhook(
    String url, String payload, String signatureKey, String signature) {
  final hmac = Hmac(sha1, utf8.encode(signatureKey));
  final digest = hmac.convert(utf8.encode(url + payload));
  final generated = base64.encode(digest.bytes);

  if (generated.length != signature.length) return false;
  var result = 0;
  for (var i = 0; i < generated.length; i++) {
    result |= generated.codeUnitAt(i) ^ signature.codeUnitAt(i);
  }
  return result == 0;
}
```
```server-go
package main

import (
    "crypto/hmac"
    "crypto/sha1"
    "crypto/subtle"
    "encoding/base64"
)

func verifyWebhook(url, payload, signatureKey, signature string) bool {
    mac := hmac.New(sha1.New, []byte(signatureKey))
    mac.Write([]byte(url + payload))
    generated := base64.StdEncoding.EncodeToString(mac.Sum(nil))

    return subtle.ConstantTimeCompare([]byte(generated), []byte(signature)) == 1
}
```
```server-kotlin
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
import java.security.MessageDigest
import java.util.Base64

fun verifyWebhook(
    url: String,
    payload: String,
    signatureKey: String,
    signature: String
): Boolean {
    val mac = Mac.getInstance("HmacSHA1")
    mac.init(SecretKeySpec(signatureKey.toByteArray(), "HmacSHA1"))
    val generated = Base64.getEncoder()
        .encodeToString(mac.doFinal((url + payload).toByteArray()))

    return MessageDigest.isEqual(generated.toByteArray(), signature.toByteArray())
}
```
```server-swift
import Foundation
import Crypto

func verifyWebhook(
    url: String, payload: String, signatureKey: String, signature: String
) -> Bool {
    let key = SymmetricKey(data: Data(signatureKey.utf8))
    let data = Data((url + payload).utf8)
    let mac = HMAC<Insecure.SHA1>.authenticationCode(for: data, using: key)
    let generated = Data(mac).base64EncodedString()

    guard generated.count == signature.count else { return false }
    var diff: UInt8 = 0
    for (a, b) in zip(generated.utf8, signature.utf8) {
        diff |= a ^ b
    }
    return diff == 0
}
```
```server-dotnet
using System.Security.Cryptography;
using System.Text;

bool VerifyWebhook(
    string url, string payload, string signatureKey, string signature)
{
    using var hmac = new HMACSHA1(Encoding.UTF8.GetBytes(signatureKey));
    var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(url + payload));
    var generated = Convert.ToBase64String(hash);

    return CryptographicOperations.FixedTimeEquals(
        Encoding.UTF8.GetBytes(generated),
        Encoding.UTF8.GetBytes(signature)
    );
}
```
```server-rust
use hmac::{Hmac, Mac};
use sha1::Sha1;
use base64::Engine;
use base64::engine::general_purpose::STANDARD;
use subtle::ConstantTimeEq;

fn verify_webhook(
    url: &str, payload: &str, signature_key: &str, signature: &str
) -> bool {
    let mut mac = Hmac::<Sha1>::new_from_slice(
        signature_key.as_bytes()
    ).expect("HMAC key error");
    mac.update(format!("{}{}", url, payload).as_bytes());
    let generated = STANDARD.encode(mac.finalize().into_bytes());

    generated.as_bytes().ct_eq(signature.as_bytes()).into()
}
```

### 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.

**Authentication events**

- Name
- Description

---

- `teams.*`
- This event triggers on any teams event.
  Returns [Team Object](/docs/references/cloud/models/team)

---

- `teams.*.create`
- This event triggers when a team is created.
  Returns [Team Object](/docs/references/cloud/models/team)

---

- `teams.*.delete`
- This event triggers when a team is deleted.
  Returns [Team Object](/docs/references/cloud/models/team)

---

- `teams.*.memberships.*`
- This event triggers on any team memberships event.
  Returns [Membership Object](/docs/references/cloud/models/membership)

---

- `teams.*.memberships.*.create`
- This event triggers when a membership is created.
  Returns [Membership Object](/docs/references/cloud/models/membership)

---

- `teams.*.memberships.*.delete`
- This event triggers when a membership is deleted.
  Returns [Membership Object](/docs/references/cloud/models/membership)

---

- `teams.*.memberships.*.update`
- This event triggers when a membership is updated.
  Returns [Membership Object](/docs/references/cloud/models/membership)

---

- `teams.*.memberships.*.update.status`
- This event triggers when a team memberships status is updated.
  Returns [Membership Object](/docs/references/cloud/models/membership)

---

- `teams.*.update`
- This event triggers when a team is updated.
  Returns [Team Object](/docs/references/cloud/models/team)

---

- `teams.*.update.prefs`
- This event triggers when a team's preferences are updated.
  Returns [Team Object](/docs/references/cloud/models/team)

---

- `users.*`
- This event triggers on any user's event.
  Returns [User Object](/docs/references/cloud/models/user)

---

- `users.*.create`
- This event triggers when a user is created.
  Returns [User Object](/docs/references/cloud/models/user)

---

- `users.*.delete`
- This event triggers when a user is deleted.
  Returns [User Object](/docs/references/cloud/models/user)

---

- `users.*.recovery.*`
- This event triggers on any user's recovery token event.
  Returns [Token Object](/docs/references/cloud/models/token)

---

- `users.*.recovery.*.create`
- This event triggers when a recovery token for a user is created.
  Returns [Token Object](/docs/references/cloud/models/token)

---

- `users.*.recovery.*.update`
- This event triggers when a recovery token for a user is validated.
  Returns [Token Object](/docs/references/cloud/models/token)

---

- `users.*.sessions.*`
- This event triggers on any user's sessions event.
  Returns [Session Object](/docs/references/cloud/models/session)

---

- `users.*.sessions.*.create`
- This event triggers when a session for a user is created.
  Returns [Session Object](/docs/references/cloud/models/session)

---

- `users.*.sessions.*.delete`
- This event triggers when a session for a user is deleted.
  Returns [Session Object](/docs/references/cloud/models/session)

---

- `users.*.update`
- This event triggers when a user is updated.
  Returns [User Object](/docs/references/cloud/models/user)

---

- `users.*.update.email`
- This event triggers when a user's email address is updated.
  Returns [User Object](/docs/references/cloud/models/user)

---

- `users.*.update.name`
- This event triggers when a user's name is updated.
  Returns [User Object](/docs/references/cloud/models/user)

---

- `users.*.update.password`
- This event triggers when a user's password is updated.
  Returns [User Object](/docs/references/cloud/models/user)

---

- `users.*.update.prefs`
- This event triggers when a user's preferences is updated.
  Returns [User Object](/docs/references/cloud/models/user)

---

- `users.*.update.status`
- This event triggers when a user's status is updated.
  Returns [User Object](/docs/references/cloud/models/user)

---

- `users.*.verification.*`
- This event triggers on any user's verification token event.
  Returns [Token Object](/docs/references/cloud/models/token)

---

- `users.*.verification.*.create`
- This event triggers when a verification token for a user is created.
  Returns [Token Object](/docs/references/cloud/models/token)

---

- `users.*.verification.*.update`
- This event triggers when a verification token for a user is validated.
  Returns [Token Object](/docs/references/cloud/models/token)

**Databases events**

- Name
- Description

---

- `tablesdb.*`
- This event triggers on any database event.
  Returns [Database Object](/docs/references/cloud/models/database)

---

- `tablesdb.*.tables.*`
- This event triggers on any table event.
  Returns [Table Object](/docs/references/cloud/models/table)

---

- `tablesdb.*.tables.*.columns.*`
- This event triggers on any columns event.
  Returns [Column Object](/docs/references/cloud/models/columnList)

---

- `tablesdb.*.tables.*.columns.*.create`
- This event triggers when a column is created.
  Returns [Column Object](/docs/references/cloud/models/columnList)

---

- `tablesdb.*.tables.*.columns.*.update`
- This event triggers when a column is updated.
  Returns [Column Object](/docs/references/cloud/models/columnList)

---

- `tablesdb.*.tables.*.columns.*.delete`
- This event triggers when a column is deleted.
  Returns [Column Object](/docs/references/cloud/models/columnList)

---

- `tablesdb.*.tables.*.create`
- This event triggers when a table is created.
  Returns [Table Object](/docs/references/cloud/models/table)

---

- `tablesdb.*.tables.*.delete`
- This event triggers when a table is deleted.
  Returns [Table Object](/docs/references/cloud/models/table)

---

- `tablesdb.*.tables.*.rows.*`
- This event triggers on any rows event.
  Returns [Row Object](/docs/references/cloud/models/row)

---

- `tablesdb.*.tables.*.rows.*.create`
- This event triggers when a row is created.
  Returns [Row Object](/docs/references/cloud/models/row)

---

- `tablesdb.*.tables.*.rows.*.delete`
- This event triggers when a row is deleted.
  Returns [Row Object](/docs/references/cloud/models/row)

---

- `tablesdb.*.tables.*.rows.*.update`
- This event triggers when a row is updated.
  Returns [Row Object](/docs/references/cloud/models/row)

---

- `tablesdb.*.tables.*.rows.*.upsert`
- This event triggers when a row is upserted.
  Returns [Row Object](/docs/references/cloud/models/row)

---

- `tablesdb.*.tables.*.indexes.*`
- This event triggers on any indexes event.
  Returns [Index Object](/docs/references/cloud/models/columnIndex)

---

- `tablesdb.*.tables.*.indexes.*.create`
- This event triggers when an index is created.
  Returns [Index Object](/docs/references/cloud/models/columnIndex)

---

- `tablesdb.*.tables.*.indexes.*.update`
- This event triggers when an index is updated.
  Returns [Index Object](/docs/references/cloud/models/columnIndex)

---

- `tablesdb.*.tables.*.indexes.*.delete`
- This event triggers when an index is deleted.
  Returns [Index Object](/docs/references/cloud/models/columnIndex)

---

- `tablesdb.*.tables.*.update`
- This event triggers when a table is updated.
  Returns [Table Object](/docs/references/cloud/models/table)

---

- `tablesdb.*.create`
- This event triggers when a database is created.
  Returns [Database Object](/docs/references/cloud/models/database)

---

- `tablesdb.*.delete`
- This event triggers when a database is deleted.
  Returns [Database Object](/docs/references/cloud/models/database)

---

- `tablesdb.*.update`
- This event triggers when a database is updated.
  Returns [Database Object](/docs/references/cloud/models/database)

**Storage events**

- Name
- Description

---

- `buckets.*`
- This event triggers on any buckets event.
  Returns [Bucket Object](/docs/references/cloud/models/bucket)

---

- `buckets.*.create`
- This event triggers when a bucket is created.
  Returns [Bucket Object](/docs/references/cloud/models/bucket)

---

- `buckets.*.delete`
- This event triggers when a bucket is deleted.
  Returns [Bucket Object](/docs/references/cloud/models/bucket)

---

- `buckets.*.files.*`
- This event triggers on any files event.
  Returns [File Object](/docs/references/cloud/models/file)

---

- `buckets.*.files.*.create`
- Since the Appwrite SDK chunks files in 5MB increments, this event will trigger for each 5MB chunk. A file is fully uploaded when `chunksTotal` equals `chunksUploaded`.
  Returns [File Object](/docs/references/cloud/models/file)

---

- `buckets.*.files.*.delete`
- This event triggers when a file is deleted.
  Returns [File Object](/docs/references/cloud/models/file)

---

- `buckets.*.files.*.update`
- This event triggers when a file is updated.
  Returns [File Object](/docs/references/cloud/models/file)

---

- `buckets.*.update`
- This event triggers when a bucket is updated.
  Returns [Bucket Object](/docs/references/cloud/models/bucket)

**Functions events**

- Name
- Description

---

- `functions.*`
- This event triggers on any functions event.
  Returns [Function Object](/docs/references/cloud/models/function)

---

- `functions.*.create`
- This event triggers when a function is created.
  Returns [Function Object](/docs/references/cloud/models/function)

---

- `functions.*.delete`
- This event triggers when a function is deleted.
  Returns [Function Object](/docs/references/cloud/models/function)

---

- `functions.*.deployments.*`
- This event triggers on any deployments event.
  Returns [Deployment Object](/docs/references/cloud/models/deployment)

---

- `functions.*.deployments.*.create`
- This event triggers when a deployment is created.
  Returns [Deployment Object](/docs/references/cloud/models/deployment)

---

- `functions.*.deployments.*.delete`
- This event triggers when a deployment is deleted.
  Returns [Deployment Object](/docs/references/cloud/models/deployment)

---

- `functions.*.deployments.*.update`
- This event triggers when a deployment is updated.
  Returns [Deployment Object](/docs/references/cloud/models/deployment)

---

- `functions.*.executions.*`
- This event triggers on any executions event.
  Returns [Execution Object](/docs/references/cloud/models/execution)

---

- `functions.*.executions.*.create`
- This event triggers when an execution is created.
  Returns [Execution Object](/docs/references/cloud/models/execution)

---

- `functions.*.executions.*.delete`
- This event triggers when an execution is deleted.
  Returns [Execution Object](/docs/references/cloud/models/execution)

---

- `functions.*.executions.*.update`
- This event triggers when an execution is updated.
  Returns [Execution Object](/docs/references/cloud/models/execution)

---

- `functions.*.update`
- This event triggers when a function is updated.
  Returns [Function Object](/docs/references/cloud/models/function)

**Messaging events**

- Name
- Description

---

- `providers.*`
- This event triggers on any providers event.
  Returns [Provider Object](/docs/references/cloud/models/provider)

---

- `providers.*.create`
- This event triggers when a provider is created.
  Returns [Provider Object](/docs/references/cloud/models/provider)

---

- `providers.*.delete`
- This event triggers when a provider is deleted.
  Returns [Provider Object](/docs/references/cloud/models/provider)

---

- `providers.*.update`
- This event triggers when a provider is updated.
  Returns [Provider Object](/docs/references/cloud/models/provider)

---

- `topics.*`
- This event triggers on any topic event.
  Returns [Topic Object](/docs/references/cloud/models/topic)

---

- `topics.*.create`
- This event triggers when a topic is created.
  Returns [Topic Object](/docs/references/cloud/models/topic)

---

- `topics.*.delete`
- This event triggers when a topic is deleted.
  Returns [Topic Object](/docs/references/cloud/models/topic)

---

- `topics.*.update`
- This event triggers when a topic is updated.
  Returns [Topic Object](/docs/references/cloud/models/topic)

---

- `topics.*.subscribers.*.create`
- This event triggers when a subscriber to a topic is created.
  Returns [Topic Object](/docs/references/cloud/models/topic)

---

- `topics.*.subscribers.*.delete`
- This event triggers when a subscriber to a topic is deleted.
  Returns [Topic Object](/docs/references/cloud/models/topic)

---

- `messages.*`
- This event triggers on any message event.
  Returns [Message Object](/docs/references/cloud/models/message)

---

- `messages.*.create`
- This event triggers when a message is created.
  Returns [Message Object](/docs/references/cloud/models/message)

---

- `messages.*.delete`
- This event triggers when a message is deleted.
  Returns [Message Object](/docs/references/cloud/models/message)

---

- `messages.*.update`
- This event triggers when a message is updated.
  Returns [Message Object](/docs/references/cloud/models/message)

[Learn more about events](/docs/apis/events)

---

## Partners

URL: https://appwrite.io/docs/partners

Partner documentation is for teams building platforms on top of Appwrite: products that provision Appwrite backends for users, connect AI agents to their accounts, or orchestrate organizations and projects from a control plane.

Developer documentation covers building apps inside a single Appwrite project. Partner documentation covers orchestrating Appwrite across organizations and projects from your own platform.

### Use cases for platform builders

- **Vibe coding and agentic platforms** that provision Appwrite projects, auth, and databases when users build with prompts or agents
- **AI agents and MCP tools** that connect to a user's Appwrite account and manage resources during autonomous workflows
- **Multi-tenant SaaS control planes** that isolate each customer in a dedicated Appwrite project or organization
- **Embedded and white-label backends** that expose your own product UX while Appwrite handles provisioning, domains, and project lifecycle

### Integration models

Appwrite supports two ways to integrate at the platform level:

- [OAuth connect](/docs/partners/oauth-connect): Let users authorize your platform to access their Appwrite organizations and projects. Best when customers already have Appwrite accounts and want to link them to your product.
- [Partners keys](/docs/partners/org-api-keys): Authenticate Console APIs from your backend with a key bound to your own Appwrite organization, and provision projects for your customers.

Many platforms combine both: OAuth connect for user-linked accounts and Partners keys for provisioning resources in your platform's Appwrite organization.

### Partners APIs

Partner integrations call these APIs from an Appwrite server SDK:

- [Organization](/docs/partners/organizations): Read and update the organization, manage members, and provision projects with a Partners key.
- [Project](/docs/partners/project): Configure auth methods, platforms, API keys, SMTP, and policies for each customer's project.
- [Proxy](/docs/partners/proxy): Point custom domains at a customer's project API, functions, and sites with a project API key, then verify them over DNS.
- [Apps](/docs/partners/apps): Register OAuth apps, manage client credentials, and start authorization flows for integrations and marketplaces.

### Getting started

Start with the quick start to choose an integration model, then follow the guides for your use case.

[Quick start](/docs/partners/quick-start)

---

## Sign in with Appwrite

URL: https://appwrite.io/docs/partners/apps

Appwrite is an **OAuth 2.1 and OpenID Connect provider**. Your app can send any Appwrite user to a consent screen, ask for access to the projects and organizations they choose, and receive tokens that call their project APIs directly. This is **Sign in with Appwrite**: the same consent flow users know from "Sign in with Google", pointed at their Appwrite account and backend.

Before this, a tool that worked with a user's Appwrite project asked them to create an API key and paste it in. The key carried whatever scopes and expiry the user picked at creation, worked for one project only, and lived outside their control once pasted. With Sign in with Appwrite, the user approves once, picks the projects your app can reach, and can revoke everything from their account page at any time.

**Building your own provider?**

This section is for apps that build on top of Appwrite itself. To make your own product an OAuth2 provider so that third parties can offer "Sign in with your product", see the [OAuth2 server](/docs/products/auth/oauth-server) documentation.

### What you can build

Your app authenticates the user once and then acts on their Appwrite resources with the scopes they granted.

- **Dashboards and monitors** that read usage, logs, and data across the projects a user selects.
- **Deployment tools** that push functions and sites into a customer's project without holding a key.
- **CLIs and devices** that sign in with a short user code instead of a browser redirect.
- **AI agents and MCP clients** that operate under scopes the user can narrow to read-only.

### How it works

![Your app sends the user through the Appwrite consent screen and calls their projects with the issued tokens](/images/docs/partners/apps/diagram-overview.avif)

Sign in with Appwrite is the authorization code flow from OAuth 2.1, served by Appwrite.

1. **Your app redirects the user** to the Appwrite authorization endpoint with the scopes it needs.
2. **Appwrite shows the consent screen.** The user sees your app's name and logo, reviews the requested permissions, and picks which projects and organizations they apply to. They can grant fewer projects than you asked for, or decline entirely.
3. **Your app receives an authorization code** at its redirect URI and exchanges it for an access token, a refresh token, and an ID token.
4. **Your app calls Appwrite APIs** with the access token as a bearer token, on any of the granted projects, in any region.

Because the provider is spec-compliant, any OAuth2 or OIDC library works against the discovery document without Appwrite-specific code:

```text
https://cloud.appwrite.io/v1/oauth2/console/.well-known/openid-configuration
```

Users stay in control after the redirect too. Every authorization appears on their account's applications page, where they see the scopes your app holds and the tokens issued under it, and can revoke a token family or the whole authorization at any time.

### Explore

- [Quick start](/docs/partners/apps/quick-start): Register an app and run the full flow, from consent to your first authorized API call.
- [Registration](/docs/partners/apps/registration): Register your app in the Console and shape its consent screen listing.
- [Scopes](/docs/partners/apps/scopes): The scope catalog and how grants target specific projects and organizations.
- [Consent](/docs/partners/apps/consent): What users see, what they can change, and what your app receives.
- [Tokens](/docs/partners/apps/tokens): Use, refresh, and revoke the tokens Appwrite issues to your app.
- [Dashboards](/docs/partners/apps/dashboards): Build a read-only dashboard over the projects a user selects.
- [Deployments](/docs/partners/apps/deployments): Deploy functions and sites into a user's project with write scopes.
- [Device flow](/docs/partners/apps/device-flow): Sign in from CLIs and devices with a short user code.
- [Agents](/docs/partners/apps/agents): Connect AI agents and MCP clients to your users' projects.

---

## Consent

URL: https://appwrite.io/docs/partners/apps/consent

![The consent screen turns your request into a granted token response or an access_denied redirect](/images/docs/partners/apps/diagram-consent.avif)

Consent is where the user decides. Appwrite hosts the screen, renders what your app asked for, and gives the user the final say over scopes and targets. Your app never sees the screen; it sees the outcome.

### The consent screen

![Consent screen showing app identity, requested permissions, and project selection](/images/docs/partners/apps/consent-screen.avif)

The screen is built from your [registration](/docs/partners/apps/registration) and your request:

- **Your app's identity**: the name, logo, and tagline you registered.
- **Permissions**: each requested scope as a plain-language line, under a one-line summary of the overall reach.
- **Project access**: a picker for the projects or organizations the grant covers when the request carries project or organization scopes.

The user signs in first if no session exists, and can switch accounts from the screen itself.

### Users can grant less

What you request is not always what you get. On the consent screen, the user can:

- Deselect projects or organizations, shrinking where the grant applies.
- Decline the whole request.

The permission list itself is not editable. Scopes are granted as you requested them or not at all, which is another reason to request the smallest set that serves your app.

The token response tells you what you got: `scope` holds the granted scopes, `authorization_details` the granted projects. Build for this from the start. A dashboard that asked for five projects but got two should show two projects, not an error.

### When the screen is skipped

Appwrite remembers approvals. When a returning user's request asks for nothing new, they skip the screen and land straight back in your app. When anything differs from the stored approval, the screen reappears. Send the same authorization parameters on every sign-in and returning users sign in silently.

Two `prompt` values override this:

- `prompt=consent` always shows the screen, even when an approval exists.
- `prompt=none` never shows it, returning `error=consent_required` or `error=login_required` instead. Use it to check for existing access silently.

### Revocation

![Account applications page listing authorized apps with their token families](/images/docs/partners/apps/account-applications.avif)

Every approval appears on the user's account applications page. From there, the user revokes a single token or the whole app, which kills every token your app holds for them.

Your app gets no notification. Calls and refreshes start returning `401`, and the fix is to send the user through authorization again.

Revocation goes both ways: when a user disconnects your app on your side, revoke the tokens you hold at the [revocation endpoint](/docs/partners/apps/tokens#revocation).

---

## Device flow

URL: https://appwrite.io/docs/partners/apps/device-flow

![The device shows a short code, the user approves it on a second device, and the device polls for tokens](/images/docs/partners/apps/diagram-device.avif)

Some clients cannot run the redirect flow: a TV has no browser to send the user back to, and a CLI has no redirect URI to receive a code. The device flow ([RFC 8628](https://datatracker.ietf.org/doc/html/rfc8628)) replaces the redirect with a short code. Your app shows the code, the user approves it from their phone or laptop, and your app picks up the tokens by polling.

The flow is off by default. Turn it on with the device flow toggle on your app's [OAuth client page](/docs/partners/apps/registration#console-oauth-client); the authorization request fails without it.

### Request a device code

Instead of building an authorization URL, ask for a device code, authenticated with your client credentials:

```curl
curl -X POST https://cloud.appwrite.io/v1/oauth2/console/device_authorization \
  -H "Content-Type: application/json" \
  -d '{
    "client_id": "<CLIENT_ID>",
    "client_secret": "<CLIENT_SECRET>",
    "scope": "openid profile email project:databases.read"
  }'
```

```hurl
POST https://cloud.appwrite.io/v1/oauth2/console/device_authorization
{
  "client_id": "<CLIENT_ID>",
  "client_secret": "<CLIENT_SECRET>",
  "scope": "openid profile email project:databases.read"
}
```

Public clients send only their `client_id`. The response carries everything both sides need:

```json
{
  "device_code": "6015241e3d2023091dcabf86d5ab85c3f16c985c49d418f37f0c446e1d2c2a9c",
  "user_code": "MDF2TN39",
  "verification_uri": "https://appwrite.io/oauth2/device",
  "verification_uri_complete": "https://appwrite.io/oauth2/device?user_code=MDF2TN39",
  "expires_in": 600,
  "interval": 1
}
```

Show the user the `user_code` and the `verification_uri`. The `verification_uri_complete` variant carries the code in the URL, which is what you encode into a QR code so the user skips typing it. The codes expire after `expires_in` seconds, ten minutes here, after which your app requests a fresh pair.

### The user approves

![Device verification page asking for the code shown on the device](/images/docs/partners/apps/device-code.avif)

On their phone or laptop, the user opens the verification URL, signs in if no session exists, and enters the code. From there they land on the same [consent screen](/docs/partners/apps/consent) as the redirect flow, with one difference: after approving, the screen tells them to return to their device.

### Poll for tokens

While the user approves, your app polls the token endpoint with the `device_code`, waiting at least `interval` seconds between attempts:

```curl
curl -X POST https://cloud.appwrite.io/v1/oauth2/console/token \
  -H "Content-Type: application/json" \
  -d '{
    "grant_type": "urn:ietf:params:oauth:grant-type:device_code",
    "device_code": "<DEVICE_CODE>",
    "client_id": "<CLIENT_ID>",
    "client_secret": "<CLIENT_SECRET>"
  }'
```

```hurl
POST https://cloud.appwrite.io/v1/oauth2/console/token
{
  "grant_type": "urn:ietf:params:oauth:grant-type:device_code",
  "device_code": "<DEVICE_CODE>",
  "client_id": "<CLIENT_ID>",
  "client_secret": "<CLIENT_SECRET>"
}
```

Until the user decides, each poll returns a pending error:

```json
{
  "error": "authorization_pending",
  "error_description": "The user has not yet approved or denied the device authorization request."
}
```

Keep polling. The moment the user approves, the same call returns the full [token response](/docs/partners/apps/tokens), with the access, refresh, and ID tokens and the granted scopes. If the user declines instead, polls return `error=access_denied`, and your app starts over with a fresh device code.

From here, nothing is device-specific. The tokens behave like the tokens from any other grant: the same scopes and targets apply, and the refresh token [rotates on every use](/docs/partners/apps/tokens#refresh-tokens).

---

## Installations

URL: https://appwrite.io/docs/partners/apps/installations

An installation connects your app to one organization. An organization owner installs your app once. After that, your app creates its own access tokens for that organization. The tokens carry the scopes the installation granted, and they work without a signed-in user.

Use an installation when your app acts as itself: a sync job that runs at night, a bot that posts build results, a backend that provisions resources. Use [Sign in with Appwrite](/docs/partners/apps) when your app acts as a person.

### Two access models

Your app can hold both kinds of access at the same time. They differ in who grants them and how long they live.

| | User consent | Installation |
| --- | --- | --- |
| Who grants it | One user, on the consent screen | An organization owner, once |
| The token acts as | The user who signed in | Your app |
| Scope types | Identity, project, and organization scopes | Project and organization scopes only |
| Token renewal | Refresh token rotation | Create a new token with your app key |
| Revoked by | The user, per token or per app | The owner, by removing the installation |

A user token disappears when the user revokes it or leaves. An installation stays until the organization removes it, so it fits work that must survive any single person.

### Request installation scopes

Your app declares the scopes it wants at install time in its `installationScopes` setting. Only `project:` and `organization:` scopes qualify. Identity scopes such as `openid` or `email` have no meaning here, because no user takes part.

Set the scopes on your app's settings in the Console, where you [registered](/docs/partners/apps/registration) it. You can also set an `installationRedirectUrl`. Appwrite redirects the owner there after each install or update. The [scope reference](/docs/partners/apps/scopes) lists every project and organization scope you can request.

Request the smallest set that serves your app. The owner sees every scope you ask for, and a long list costs you installs.

**Scopes are a snapshot**

Each installation copies your app's scopes at the moment the owner creates or updates it. When you change the scopes later, existing installations keep their old grant. The new scopes apply to an installation only after its organization owner updates it. Design your scope list before you ask anyone to install.

### Install an app

Installation happens in the Appwrite Console, on your customer's side. Only members with the owner role can install an app, and each app installs once per organization.

The owner finds your app in their organization's **Marketplace** tab and opens its listing. The **Install app** action on the listing starts the installation.

![Marketplace listing of an app with the Install app action](/images/docs/partners/apps/marketplace-install.avif)

The installation copies your app's current scopes at that moment; changes you make later wait for the owner to update the installation.

When your app has an `installationRedirectUrl`, the owner lands there after the install. Use that page to finish setup: read the new installation from [your list](#list-installations), store its ID, and show the owner what happens next.

### App keys

Your app authenticates its installation calls with an app key. Create one on your app's settings in the Console. The secret appears once, at creation; store it in your deployment environment right away. Appwrite keeps a hint, the secret's last six characters, so you can tell keys apart later.

App keys carry no scopes of their own. A key proves that a call comes from your app, and each installation decides what that call may do. Send the key in the `X-Appwrite-Key` header together with your app's ID in the `X-Appwrite-App` header.

Store the secret like a password. Anyone who holds it can mint tokens for every installation of your app.

### List your installations

Your app can list where it is installed, with standard [queries](/docs/products/databases/tablesdb/queries) for filtering and pagination:

```curl
curl https://cloud.appwrite.io/v1/apps/<APP_ID>/installations \
  -H "X-Appwrite-App: <APP_ID>" \
  -H "X-Appwrite-Key: <APP_KEY_SECRET>"
```

```hurl
GET https://cloud.appwrite.io/v1/apps/<APP_ID>/installations
X-Appwrite-App: <APP_ID>
X-Appwrite-Key: <APP_KEY_SECRET>
```

The response wraps the records in an `installations` array with a `total` count. Each record carries its organization, the granted scopes, and who installed it. The `lastAccessedAt` timestamp shows when your app last minted a token for it:

```json
{
  "total": 1,
  "installations": [
    {
      "$id": "6a5f09b1000c2d4e8f31",
      "$createdAt": "2026-08-05T09:30:00.000+00:00",
      "$updatedAt": "2026-08-05T09:30:00.000+00:00",
      "appId": "<APP_ID>",
      "teamId": "<ORGANIZATION_ID>",
      "scopes": ["organization:projects.read", "project:databases.read"],
      "authorizationDetails": [{ "type": "project", "identifiers": ["*"] }],
      "createdById": "6a150ace003bc4c2919e",
      "createdByName": "Walter O'Brien",
      "lastAccessedAt": null
    }
  ]
}
```

### Mint an installation token

Exchange your app key for an access token bound to one installation:

```curl
curl -X POST https://cloud.appwrite.io/v1/apps/<APP_ID>/installations/<INSTALLATION_ID>/tokens \
  -H "X-Appwrite-App: <APP_ID>" \
  -H "X-Appwrite-Key: <APP_KEY_SECRET>"
```

```hurl
POST https://cloud.appwrite.io/v1/apps/<APP_ID>/installations/<INSTALLATION_ID>/tokens
X-Appwrite-App: <APP_ID>
X-Appwrite-Key: <APP_KEY_SECRET>
```

The response is a standard OAuth2 token response. The `refresh_token` field comes back empty and `id_token` comes back `null`; only user consent grants carry those:

```json
{
  "access_token": "eyJ0eXAiOiJhdCtqd3QiLCJhbGciOiJSUzI1NiJ9...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "",
  "scope": "organization:projects.read project:databases.read",
  "authorization_details": [
    { "type": "project", "identifiers": ["*"] },
    { "type": "organization", "identifiers": ["<ORGANIZATION_ID>"] }
  ],
  "id_token": null
}
```

Three things to know about this token:

- **It acts as the installation, not as a user.** The token's `sub` claim is the installation ID, and its `client_id` is your app.
- **It is pinned to the installed organization.** Appwrite writes the `organization` entry in `authorization_details` itself. A token can never reach beyond the organization that installed your app.
- **There is no refresh token.** Tokens last 1 hour by default. When one expires, mint another; the installation record is the durable grant, so no consent or renewal step exists. Several tokens can be active at once, so each worker of your backend can hold its own.

Use it as a bearer token, the same way as any [Sign in with Appwrite access token](/docs/partners/apps/tokens#access-tokens). It works on every granted project, and on the organization APIs that the scopes cover:

```curl
curl https://<REGION>.cloud.appwrite.io/v1/databases \
  -H "X-Appwrite-Project: <PROJECT_ID>" \
  -H "Authorization: Bearer <ACCESS_TOKEN>"
```

```hurl
GET https://<REGION>.cloud.appwrite.io/v1/databases
X-Appwrite-Project: <PROJECT_ID>
Authorization: Bearer <ACCESS_TOKEN>
```

To find each granted project's region endpoint, list the projects with the token and read the `endpoint` field, as the [tokens page](/docs/partners/apps/tokens#access-tokens) shows.

### Updates and removal

The organization owner stays in control after the install.

- **Update.** The owner refreshes the installation in the Console, for example to accept scopes you added to your app, or to change the granted projects. The installation takes your app's current scopes, and Appwrite revokes every active token for it.
- **Removal.** The owner uninstalls your app in the Console. The grant and every active token disappear together.

An update or a removal revokes tokens immediately. When a token stops working, mint a fresh one. When the mint also fails, the installation is gone, and your app should mark that organization as disconnected.

Appwrite emits an event for each step of the lifecycle, so your app can react through [webhooks](/docs/apis/webhooks):

| Event | When |
| --- | --- |
| `teams.[teamId].installations.[installationId].create` | An owner installed your app |
| `teams.[teamId].installations.[installationId].update` | An owner refreshed the grant |
| `teams.[teamId].installations.[installationId].delete` | The installation was removed |

### Errors

Your app's list and token calls can return these errors:

| Error | Cause |
| --- | --- |
| `app_installation_not_found` | The installation ID does not exist, or it belongs to another app. The owner may have uninstalled your app. |
| `app_not_found` | The app ID in the path does not match the `X-Appwrite-App` header. |
| `app_key_invalid` | The `X-Appwrite-Key` header does not hold a valid app key for this app. |

---

## Start with Sign in with Appwrite

URL: https://appwrite.io/docs/partners/apps/quick-start

This guide builds Sign in with Appwrite into an app, start to finish. The running example is Horizon, a deployment dashboard:

- A user clicks its sign-in button.
- They approve access on the Appwrite consent screen.
- Horizon reads their projects with the tokens it receives.

By the end, you will have run the same journey with your own app.

You need a server that can receive a redirect and keep a client secret. The examples use `http://localhost:7700/oauth/callback` as the redirect URI; replace it with your own.

### Register your app

![Marketplace tab of an organization with the Add app action](/images/docs/partners/apps/registration-marketplace.avif)

Before Appwrite can show your app to anyone, it needs to know the app exists. In the Console, open your organization's **Marketplace** tab and click **Add app**. Give it a name, a slug, a category, and a short description; the slug becomes your client ID, so Horizon signs in as `horizon`.

![OAuth client page with client type and redirect URIs](/images/docs/partners/apps/registration-oauth-client.avif)

The new app opens on its settings pages. Go to **OAuth client** and configure two things:

- **Client type**: keep **Confidential**. Your app has a server, and the server will hold the client secret.
- **Redirect URIs**: add your callback URL, `http://localhost:7700/oauth/callback` in this guide. After the user approves, Appwrite only ever redirects to a URL on this list, so a stolen client ID cannot send your users anywhere else.

![OAuth secrets page with active secrets](/images/docs/partners/apps/registration-secrets.avif)

Last, your app needs a way to prove it is really Horizon when it exchanges codes for tokens. On **OAuth secrets**, select **Create secret** and copy the value; it is shown once. Store the client ID and the secret in your server's environment.

[Registration](/docs/partners/apps/registration) covers the Console pages in more detail.

### Redirect the user

With credentials in your environment, Horizon can offer a Sign in with Appwrite button. The button sends the user to the authorization endpoint.

The query string carries everything your app asks for:

- Identity scopes, so Horizon knows who signed in.
- One project scope, `project:databases.read`, for the data it needs.

```text
https://cloud.appwrite.io/v1/oauth2/console/authorize
  ?client_id=<CLIENT_ID>
  &redirect_uri=http://localhost:7700/oauth/callback
  &response_type=code
  &scope=openid profile email project:databases.read
```

URL-encode the values. Because the request carries a project scope, the consent screen asks the user which projects the grant covers. Scopes are described in the [scope catalog](/docs/partners/apps/scopes).

### User grants access

![Consent screen asking to authorize the app with selected projects](/images/docs/partners/apps/quick-start-consent.avif)

This is the moment the user meets your app inside Appwrite. The consent screen shows Horizon's name and logo, lists each requested permission in plain language, and lets the user select the projects the grant covers. On approval, the browser returns to your redirect URI:

```text
http://localhost:7700/oauth/callback?code=<CODE>
```

Use the code within five minutes. It is single-use.

### Exchange the code

From your server, exchange the code for tokens, sending your client credentials in the request body.

```curl
curl -X POST https://cloud.appwrite.io/v1/oauth2/console/token \
  -H "Content-Type: application/json" \
  -d '{
    "grant_type": "authorization_code",
    "code": "<CODE>",
    "redirect_uri": "http://localhost:7700/oauth/callback",
    "client_id": "<CLIENT_ID>",
    "client_secret": "<CLIENT_SECRET>"
  }'
```

```hurl
POST https://cloud.appwrite.io/v1/oauth2/console/token
{
  "grant_type": "authorization_code",
  "code": "<CODE>",
  "redirect_uri": "http://localhost:7700/oauth/callback",
  "client_id": "<CLIENT_ID>",
  "client_secret": "<CLIENT_SECRET>"
}
```

The response carries the three tokens and echoes what the user actually granted:

```json
{
  "access_token": "eyJ0eXAiOiJhdCtqd3QiLCJhbGciOiJSUzI1NiJ9...",
  "token_type": "Bearer",
  "expires_in": 28800,
  "refresh_token": "eyJhbGciOiJIUzI1NiJ9...",
  "scope": "openid profile email project:databases.read",
  "authorization_details": [{ "type": "project", "identifiers": ["*"] }],
  "id_token": "eyJhbGciOiJSUzI1NiJ9..."
}
```

Check `authorization_details` for the projects the user selected; they may cover less than you asked for. Store the refresh token like a password. [Tokens](/docs/partners/apps/tokens) covers lifetimes, refresh rotation, and revocation.

### Verify the token

Before doing anything else with the token, ask Appwrite who it belongs to. The userinfo endpoint answers with the user's identity, and only for tokens that are still active:

```curl
curl https://cloud.appwrite.io/v1/oauth2/console/userinfo \
  -H "Authorization: Bearer <ACCESS_TOKEN>"
```

```hurl
GET https://cloud.appwrite.io/v1/oauth2/console/userinfo
Authorization: Bearer <ACCESS_TOKEN>
```

The response is the signed-in user:

```json
{
  "sub": "6a150ace003bc4c2919e",
  "name": "Walter O'Brien",
  "email": "walter@example.com",
  "email_verified": true,
  "updated_at": 1784707579
}
```

An expired or revoked token returns a `401` of type `oauth2_invalid_token` instead, so a successful response also proves the token is still live.

### List the user's projects

The user is signed in and Horizon holds its tokens. Now the payoff: every Sign in with Appwrite token can list the projects and organizations it was granted. Call the listing endpoint with the access token as a bearer token.

```curl
curl https://cloud.appwrite.io/v1/oauth2/console/projects \
  -H "Authorization: Bearer <ACCESS_TOKEN>"
```

```hurl
GET https://cloud.appwrite.io/v1/oauth2/console/projects
Authorization: Bearer <ACCESS_TOKEN>
```

The response lists every project the grant covers:

```json
{
  "total": 2,
  "projects": [
    {
      "$id": "6a357f7e001c7237296b",
      "region": "fra",
      "endpoint": "https://fra.cloud.appwrite.io/v1"
    },
    {
      "$id": "6a357fa40031c857fa9f",
      "region": "nyc",
      "endpoint": "https://nyc.cloud.appwrite.io/v1"
    }
  ]
}
```

Each entry carries the project's `$id`, `region`, and `endpoint`, which is everything your app needs to call it. The listing returns 25 projects per page and accepts `limit` and `offset` parameters for paging through more. A matching `/oauth2/console/organizations` endpoint lists granted organizations the same way.

### Call a project API

The listing gives Horizon everything it needs to do real work. Point a client at a granted project's endpoint, keep the same bearer token, and call the APIs your scopes allow. With `project:databases.read`, that is listing the databases the project holds.

**No SDK required**

Prefer not to use an SDK? The cURL and Hurl tabs show the raw API call: the project's endpoint, the `X-Appwrite-Project` header, and the access token in the `Authorization` header. Every Appwrite API accepts the same shape.

```curl
curl "<PROJECT_ENDPOINT>/tablesdb" \
  -H "X-Appwrite-Project: <PROJECT_ID>" \
  -H "Authorization: Bearer <ACCESS_TOKEN>"
```

```hurl
GET <PROJECT_ENDPOINT>/tablesdb
X-Appwrite-Project: <PROJECT_ID>
Authorization: Bearer <ACCESS_TOKEN>
```

```server-nodejs
import { Client, TablesDB } from 'node-appwrite';

const client = new Client()
    .setEndpoint('<PROJECT_ENDPOINT>') // From the project listing
    .setProject('<PROJECT_ID>')
    .setBearer('Bearer <ACCESS_TOKEN>'); // The same access token

const tablesDB = new TablesDB(client);

const result = await tablesDB.list();
```

```server-deno
import { Client, TablesDB } from 'npm:node-appwrite';

const client = new Client()
    .setEndpoint('<PROJECT_ENDPOINT>') // From the project listing
    .setProject('<PROJECT_ID>')
    .setBearer('Bearer <ACCESS_TOKEN>'); // The same access token

const tablesDB = new TablesDB(client);

const result = await tablesDB.list();
```

```server-python
from appwrite.client import Client
from appwrite.services.tables_db import TablesDB

client = Client()
client.set_endpoint('<PROJECT_ENDPOINT>')  # From the project listing
client.set_project('<PROJECT_ID>')
client.set_bearer('Bearer <ACCESS_TOKEN>')  # The same access token

tables_db = TablesDB(client)

result = tables_db.list()
```

```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\TablesDB;

$client = (new Client())
    ->setEndpoint('<PROJECT_ENDPOINT>') // From the project listing
    ->setProject('<PROJECT_ID>')
    ->setBearer('Bearer <ACCESS_TOKEN>'); // The same access token

$tablesDB = new TablesDB($client);

$result = $tablesDB->list();
```

```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('<PROJECT_ENDPOINT>') # From the project listing
    .set_project('<PROJECT_ID>')
    .set_bearer('Bearer <ACCESS_TOKEN>') # The same access token

tables_db = TablesDB.new(client)

result = tables_db.list()
```

```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('<PROJECT_ENDPOINT>') // From the project listing
    .setProject('<PROJECT_ID>')
    .setBearer('Bearer <ACCESS_TOKEN>'); // The same access token

TablesDB tablesDB = TablesDB(client);

DatabaseList result = await tablesDB.list();
```

```server-kotlin
import io.appwrite.Client
import io.appwrite.services.TablesDB

val client = Client()
    .setEndpoint("<PROJECT_ENDPOINT>") // From the project listing
    .setProject("<PROJECT_ID>")
    .setBearer("Bearer <ACCESS_TOKEN>") // The same access token

val tablesDB = TablesDB(client)

val result = tablesDB.list()
```

```server-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.TablesDB;

Client client = new Client()
    .setEndpoint("<PROJECT_ENDPOINT>") // From the project listing
    .setProject("<PROJECT_ID>")
    .setBearer("Bearer <ACCESS_TOKEN>"); // The same access token

TablesDB tablesDB = new TablesDB(client);

tablesDB.list(
    null, // queries (optional)
    null, // search (optional)
    null, // total (optional)
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        System.out.println(result);
    })
);
```

```server-swift
import Appwrite

let client = Client()
    .setEndpoint("<PROJECT_ENDPOINT>") // From the project listing
    .setProject("<PROJECT_ID>")
    .setBearer("Bearer <ACCESS_TOKEN>") // The same access token

let tablesDB = TablesDB(client)

let databaseList = try await tablesDB.list()
```

```server-dotnet
using Appwrite;
using Appwrite.Models;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("<PROJECT_ENDPOINT>") // From the project listing
    .SetProject("<PROJECT_ID>")
    .SetBearer("Bearer <ACCESS_TOKEN>"); // The same access token

TablesDB tablesDB = new TablesDB(client);

DatabaseList result = await tablesDB.List();
```

```server-go
package main

import (
    "fmt"

    "github.com/appwrite/sdk-for-go/client"
    "github.com/appwrite/sdk-for-go/tablesdb"
)

func main() {
    c := client.New(
        client.WithEndpoint("<PROJECT_ENDPOINT>"), // From the project listing
        client.WithProject("<PROJECT_ID>"),
        client.WithBearer("Bearer <ACCESS_TOKEN>"), // The same access token
    )

    service := tablesdb.New(c)

    result, err := service.List()
    if err != nil {
        panic(err)
    }

    fmt.Println(result)
}
```

```server-rust
use appwrite::Client;
use appwrite::services::TablesDB;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    client.set_endpoint("<PROJECT_ENDPOINT>"); // From the project listing
    client.set_project("<PROJECT_ID>");
    client.set_bearer("Bearer <ACCESS_TOKEN>"); // The same access token

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

    let result = tables_db.list(None, None, None).await?;
    println!("{:?}", result.total);

    Ok(())
}
```

A call outside the granted scopes or projects fails with a `401` error of type `general_unauthorized_scope`. The token works on every region, so your app never maps regions itself; use each project's `endpoint` value from the listing.

### Refresh the token

Access tokens expire after 8 hours. When one does, trade the refresh token for a new pair instead of sending the user back through consent:

```curl
curl -X POST https://cloud.appwrite.io/v1/oauth2/console/token \
  -H "Content-Type: application/json" \
  -d '{
    "grant_type": "refresh_token",
    "refresh_token": "<REFRESH_TOKEN>",
    "client_id": "<CLIENT_ID>",
    "client_secret": "<CLIENT_SECRET>"
  }'
```

```hurl
POST https://cloud.appwrite.io/v1/oauth2/console/token
{
  "grant_type": "refresh_token",
  "refresh_token": "<REFRESH_TOKEN>",
  "client_id": "<CLIENT_ID>",
  "client_secret": "<CLIENT_SECRET>"
}
```

The response has the same shape as the original exchange: a fresh access token and a new refresh token. Refresh tokens are single-use, so store the new one as soon as it arrives; reusing a spent token invalidates the whole pair. [Tokens](/docs/partners/apps/tokens#refresh-tokens) covers rotation in detail.

### Sign in through Appwrite Auth

![Appwrite provider settings with quick setup filling credentials from an organization app](/images/docs/partners/apps/quick-start-provider-setup.avif)

If your product itself runs on an Appwrite project, you do not need to handle the flow by hand. Appwrite Auth ships an **Appwrite** OAuth2 provider.

To enable it:

- In your project, open **Auth**, then **Social providers**, and select **Appwrite**.
- Use **Quick setup** to create or pick an organization app. Appwrite registers the callback URI and fills in the client ID and secret.

Then sign users in with the same call you would use for GitHub or Google:

```client-web
import { Client, Account, OAuthProvider } from 'appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<YOUR_PROJECT_ID>'); // Your project, not the user's

const account = new Account(client);

account.createOAuth2Session({
    provider: OAuthProvider.Appwrite,
    success: 'https://example.com/success',
    failure: 'https://example.com/failure',
    scopes: ['project:databases.read']
});
```

```client-flutter
import 'package:appwrite/appwrite.dart';
import 'package:appwrite/enums.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<YOUR_PROJECT_ID>'); // Your project, not the user's

Account account = Account(client);

await account.createOAuth2Session(
    provider: OAuthProvider.appwrite,
    scopes: ['project:databases.read'],
);
```

```client-apple
import Appwrite
import AppwriteEnums

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<YOUR_PROJECT_ID>") // Your project, not the user's

let account = Account(client)

try await account.createOAuth2Session(
    provider: .appwrite,
    scopes: ["project:databases.read"]
)
```

```client-android-kotlin
import io.appwrite.Client
import io.appwrite.enums.OAuthProvider
import io.appwrite.services.Account

val client = Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<YOUR_PROJECT_ID>") // Your project, not the user's

val account = Account(client)

account.createOAuth2Session(
    activity = activity,
    provider = OAuthProvider.APPWRITE,
    scopes = listOf("project:databases.read"),
)
```

```client-react-native
import { Client, Account, OAuthProvider } from 'react-native-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<YOUR_PROJECT_ID>'); // Your project, not the user's

const account = new Account(client);

account.createOAuth2Token({
    provider: OAuthProvider.Appwrite,
    success: '<YOUR_DEEP_LINK>',
    failure: '<YOUR_DEEP_LINK>',
    scopes: ['project:databases.read']
});
```

The provider always requests the identity scopes. The `scopes` parameter adds project scopes on top, and the consent screen lets the user select which projects they apply to.

After sign-in, the issued tokens live on the user's [identity](/docs/products/auth/identities). Your backend reads them from there to call the granted projects.

Use this path when your product runs on Appwrite. Use the manual flow above when you want to handle the tokens yourself.

### Next steps

- [Registration](/docs/partners/apps/registration): Give your app a logo and consent screen details, and manage secrets.
- [Scopes](/docs/partners/apps/scopes): Request exactly the access your app needs.
- [Dashboards](/docs/partners/apps/dashboards): Build a read-only dashboard over granted projects.
- [Tokens](/docs/partners/apps/tokens): Refresh tokens before they expire and handle revocation.

---

## Registration

URL: https://appwrite.io/docs/partners/apps/registration

Your app appears on the consent screen as a registered client: a name, a logo, and a set of credentials tied to redirect URIs. Registration happens in the Console, in your organization's Marketplace tab.

### Client types

Every client is `confidential` or `public`, and the choice decides how it authenticates.

- **Confidential** clients have a backend that keeps a secret. They authenticate token requests with the client secret, and their tokens live longer: 8 hours for access tokens and 365 days for refresh tokens by default.
- **Public** clients run where a secret cannot survive: single-page apps, native apps, CLIs. They carry no secret and must use [PKCE](https://datatracker.ietf.org/doc/html/rfc7636) with `S256` on every authorization. Their tokens default to 1 hour and 30 days.

Choose confidential whenever a server takes part in the flow. Public exists for the rest.

### Console

![Marketplace tab of an organization showing the app catalog and the Add app action](/images/docs/partners/apps/registration-marketplace.avif)

Register through the Console when your app is a product: the same flow that creates the client also builds its marketplace listing. In your organization, open the **Marketplace** tab and click **Add app**. Give the app a name, a slug, a category, and a description. The slug becomes the app's client ID, and the app starts as a draft that only your organization sees.

Once created, the app opens on its settings pages. Each page below is one of them, and every page has an **Update** button that saves that page's fields.

#### General

![General page with the app name, tagline, description, and category fields](/images/docs/partners/apps/registration-general.avif)

The **General** page holds the app's identity: its name, a one-line tagline, a longer description, and a category. The name appears on the consent screen; all four appear on the marketplace listing. This page also shows the app ID and the control to delete the app.

#### OAuth client

![OAuth client settings with client type, device flow, and redirect URIs](/images/docs/partners/apps/registration-oauth-client.avif)

The **OAuth client** page sets the protocol details: the [client type](#client-types), the device flow toggle for TVs and CLIs, and the redirect URIs. Add every URI your app returns to; the authorization endpoint rejects anything not on the list.

#### OAuth secrets

![OAuth secrets page listing an active secret with its hint](/images/docs/partners/apps/registration-secrets.avif)

The **OAuth secrets** page issues client secrets for confidential apps. Each secret's value appears once, at creation; store it in your deployment environment before closing the dialog. The list keeps only a hint of each secret, its creation date, and when it was last used.

An app holds several active secrets at once, so rotation needs no downtime: create a new secret, deploy it, then delete the old one. The last-used timestamp tells you when the old secret is safe to delete.

#### Legal

![Legal page with privacy policy, terms of service, and data deletion URL fields](/images/docs/partners/apps/registration-legal.avif)

The **Legal** page holds the privacy policy, terms of service, and data deletion URLs. The consent screen links these so users can read them before they approve your app.

#### Support

![Support page with a support URL and contact emails field](/images/docs/partners/apps/registration-support.avif)

The **Support** page holds a support URL and contact emails, so users have somewhere to turn when they need help with your app.

#### Publish

![Publish page with the published toggle and the branding card](/images/docs/partners/apps/registration-publish.avif)

The **Publish** page carries the branding shown on the consent screen, a homepage URL, a logo, and optional listing images, along with the **Published** toggle. Turning it on lists the app in the marketplace catalog for every organization. Publishing changes discovery, not authorization: users consent to your app the same way whether it is a draft or published.

### Labels

Labels such as `official` and `partner` mark apps the platform vouches for, and consent screens surface them as trust signals. Labels are assigned by Appwrite, not by the app itself, so there is no field for them in the Console. Contact your partner manager to have your app labeled.

---

## Scopes

URL: https://appwrite.io/docs/partners/apps/scopes

![Scopes and targets combine into a grant that the access token carries and every request is checked against](/images/docs/partners/apps/diagram-scopes.avif)

A scope names an action your app wants to perform. A grant pairs scopes with the projects and organizations they apply to. Both halves matter: `project:databases.read` by itself says what, and the user's project selection on the consent screen says where.

Request the smallest set that serves your app. Every scope you ask for appears on the consent screen as a permission line, and users decline requests that want too much.

### Identity scopes

The OpenID Connect scopes cover who the user is. They are the same four scopes every OIDC provider uses.

| Scope | Grants |
| --- | --- |
| `openid` | The user's subject identifier. Required to receive an ID token. |
| `profile` | Profile claims, such as the user's name. |
| `email` | The user's email address and its verification state. |
| `phone` | The user's phone number and its verification state. |

An app that only signs users in requests `openid profile email` and stops there.

### Project scopes

Project scopes carry the `project:` prefix and map to the permissions [API keys](/docs/partners/project/api-keys) use, one `.read` and one `.write` scope per resource. The catalog covers every Appwrite product:

| Product | Scopes |
| --- | --- |
| Databases | `project:databases.read`, `project:databases.write`, `project:tables.read`, `project:tables.write`, `project:columns.read`, `project:columns.write`, `project:rows.read`, `project:rows.write`, `project:indexes.read`, `project:indexes.write` |
| Auth | `project:users.read`, `project:users.write`, `project:sessions.read`, `project:sessions.write`, `project:teams.read`, `project:teams.write` |
| Storage | `project:buckets.read`, `project:buckets.write`, `project:files.read`, `project:files.write`, `project:tokens.read`, `project:tokens.write` |
| Functions | `project:functions.read`, `project:functions.write`, `project:executions.read`, `project:executions.write` |
| Sites | `project:sites.read`, `project:sites.write`, `project:log.read`, `project:log.write` |
| Messaging | `project:providers.read`, `project:providers.write`, `project:topics.read`, `project:topics.write`, `project:subscribers.read`, `project:subscribers.write`, `project:targets.read`, `project:targets.write`, `project:messages.read`, `project:messages.write` |
| Projects | `project:project.read`, `project:project.write`, `project:keys.read`, `project:keys.write`, `project:platforms.read`, `project:platforms.write`, `project:webhooks.read`, `project:webhooks.write`, `project:policies.read`, `project:policies.write` |
| Proxy | `project:rules.read`, `project:rules.write` |
| Domains | `project:domains.read`, `project:domains.write` |
| WAF | `project:wafRules.read`, `project:wafRules.write` |
| Usage | `project:usage.read` |
| Health | `project:health.read` |
| Migrations | `project:migrations.read`, `project:migrations.write` |
| Backups | `project:backups.policies.read`, `project:backups.policies.write`, `project:archives.read`, `project:archives.write`, `project:restorations.read`, `project:restorations.write` |

`project:all` grants the full project catalog at once. Reserve it for tools that genuinely administer whole projects; the consent screen presents it as full project access.

The full list is published in the discovery document under `scopes_supported`:

```text
https://cloud.appwrite.io/v1/oauth2/console/.well-known/openid-configuration
```

### Organization scopes

Organization scopes carry the `organization:` prefix and cover what a user's organization owns: its profile, members, projects, domains, and keys.

| Area | Scopes |
| --- | --- |
| Organization | `organization:organization.read`, `organization:organization.write` |
| Members | `organization:organization.memberships.read`, `organization:organization.memberships.write` |
| Projects | `organization:projects.read`, `organization:projects.write` |
| Domains | `organization:domains.read`, `organization:domains.write` |
| Keys | `organization:organization.keys.read`, `organization:organization.keys.write` |

`organization:all` grants the full set. `organization:projects.write` is the scope that creates projects, which lets an app provision on a user's behalf.

### Targeting projects and organizations

Scopes say what your app can do. Targets say where. Every grant carries both.

When your request includes a project or organization scope, the consent screen asks the user which projects or organizations the scope applies to. You send only the scopes; the user's selection is written into the grant, and your app does nothing extra to make it happen.

#### Reading what was granted

The token response's `authorization_details` field holds the projects the user ended up granting.

To check a token you already hold, decode it. The access token is a JWT, and its payload carries the granted scopes and targets alongside the standard claims:

```json
{
  "iss": "https://cloud.appwrite.io/v1/oauth2/console",
  "sub": "6a150ace003bc4c2919e",
  "client_id": "horizon",
  "scope": "openid project:databases.read",
  "authorization_details": [{ "type": "project", "identifiers": ["*"] }],
  "exp": 1784832895
}
```

Neither value changes after issuance, so a decode is authoritative for what the token holds. [Tokens](/docs/partners/apps/tokens#validation) covers verifying the signature.

To turn the targets into concrete projects, list them with the token itself:

```curl
curl https://cloud.appwrite.io/v1/oauth2/console/projects \
  -H "Authorization: Bearer <ACCESS_TOKEN>"
```

```hurl
GET https://cloud.appwrite.io/v1/oauth2/console/projects
Authorization: Bearer <ACCESS_TOKEN>
```

Each project in the response carries its `$id`, `region`, and `endpoint`, which is everything your app needs to call it. The listing returns 25 projects per page and accepts `limit` and `offset` parameters; a matching `/oauth2/console/organizations` endpoint lists granted organizations the same way.

A scope only works inside its granted targets. A token with `project:databases.read` lists databases in the selected projects and returns `401 general_unauthorized_scope` everywhere else.

### When requests fail

A scope outside the catalog fails before the user ever sees a consent screen: the authorization endpoint redirects straight back to your app with `error=invalid_scope`. The catalog is published in the discovery document under `scopes_supported`, so your app can validate requests before sending them.

The user can also grant less than you asked by deselecting projects or organizations on the consent screen, so treat the token response's `authorization_details` as the truth, not your request. [Consent](/docs/partners/apps/consent) shows what partial grants look like.

---

## Tokens

URL: https://appwrite.io/docs/partners/apps/tokens

![Your app exchanges the code for tokens, calls granted projects, renews with the refresh token, and revokes when done](/images/docs/partners/apps/diagram-tokens.avif)

Your app holds three tokens with different jobs:

- The **access token** calls APIs.
- The **refresh token** replaces expired access tokens.
- The **ID token** proves who signed in.

All three come from one call. After the user approves your app on the consent screen, exchange the authorization code at the token endpoint, sending your client credentials in the request body:

```curl
curl -X POST https://cloud.appwrite.io/v1/oauth2/console/token \
  -H "Content-Type: application/json" \
  -d '{
    "grant_type": "authorization_code",
    "code": "<CODE>",
    "redirect_uri": "<REDIRECT_URI>",
    "client_id": "<CLIENT_ID>",
    "client_secret": "<CLIENT_SECRET>"
  }'
```

```hurl
POST https://cloud.appwrite.io/v1/oauth2/console/token
{
  "grant_type": "authorization_code",
  "code": "<CODE>",
  "redirect_uri": "<REDIRECT_URI>",
  "client_id": "<CLIENT_ID>",
  "client_secret": "<CLIENT_SECRET>"
}
```

The response carries all three tokens, along with what the user granted:

```json
{
  "access_token": "eyJ0eXAiOiJhdCtqd3QiLCJhbGciOiJSUzI1NiJ9...",
  "token_type": "Bearer",
  "expires_in": 28800,
  "refresh_token": "eyJhbGciOiJIUzI1NiJ9...",
  "scope": "openid profile email project:databases.read",
  "authorization_details": [{ "type": "project", "identifiers": ["6a357f7e001c7237296b"] }],
  "id_token": "eyJhbGciOiJSUzI1NiJ9..."
}
```

### Public clients

Mobile apps, desktop apps, and SPAs cannot keep a client secret, so they register as [public clients](/docs/partners/apps/registration#client-types) and prove themselves with PKCE ([RFC 7636](https://datatracker.ietf.org/doc/html/rfc7636)) instead. Before redirecting the user, generate a random `code_verifier`, hash it with SHA-256, and send the hash along in the authorization request:

```text
https://cloud.appwrite.io/v1/oauth2/console/authorize
  ?client_id=<CLIENT_ID>
  &redirect_uri=<REDIRECT_URI>
  &response_type=code
  &scope=openid profile email project:databases.read
  &code_challenge=<CODE_CHALLENGE>
  &code_challenge_method=S256
```

The challenge is not optional: a public client authorizing without one is redirected back with `error=invalid_request`. At the token endpoint, the original verifier replaces the client secret:

```curl
curl -X POST https://cloud.appwrite.io/v1/oauth2/console/token \
  -H "Content-Type: application/json" \
  -d '{
    "grant_type": "authorization_code",
    "code": "<CODE>",
    "redirect_uri": "<REDIRECT_URI>",
    "client_id": "<CLIENT_ID>",
    "code_verifier": "<CODE_VERIFIER>"
  }'
```

```hurl
POST https://cloud.appwrite.io/v1/oauth2/console/token
{
  "grant_type": "authorization_code",
  "code": "<CODE>",
  "redirect_uri": "<REDIRECT_URI>",
  "client_id": "<CLIENT_ID>",
  "code_verifier": "<CODE_VERIFIER>"
}
```

The response carries the same three tokens, on shorter leases: access tokens last 1 hour and refresh tokens 30 days, against 8 hours and 365 days for confidential clients.

### Access tokens

The access token is a JWT of type `at+jwt` ([RFC 9068](https://datatracker.ietf.org/doc/html/rfc9068)), signed with `RS256`. Its claims carry the whole grant:

- `sub`: the user who signed in.
- `client_id`: your app.
- `scope`: the granted scopes.
- `authorization_details`: the granted projects and organizations.
- The standard JWT claims:
    - `iss`: who issued the token, always `https://cloud.appwrite.io/v1/oauth2/console`.
    - `aud`: who the token is for, the Appwrite API.
    - `exp`: when the token expires, as a Unix timestamp.
    - `iat`: when the token was issued.
    - `jti`: a unique ID for this token.
    - `auth_time`: when the user last authenticated.

Decoded, the payload reads:

```json
{
  "iss": "https://cloud.appwrite.io/v1/oauth2/console",
  "aud": ["https://cloud.appwrite.io/v1/console"],
  "sub": "6a150ace003bc4c2919e",
  "client_id": "horizon",
  "scope": "openid profile email project:databases.read",
  "authorization_details": [{ "type": "project", "identifiers": ["*"] }],
  "exp": 1784832895,
  "iat": 1784804095,
  "jti": "d0fe924a1fe6113ba1bab1eaf631603d",
  "auth_time": 1784803731,
  "tokenId": "6a61f2f3c94806401116"
}
```

Pass it as a bearer token in the `Authorization` header. The userinfo endpoint is the simplest call to try it on:

```curl
curl https://cloud.appwrite.io/v1/oauth2/console/userinfo \
  -H "Authorization: Bearer <ACCESS_TOKEN>"
```

```hurl
GET https://cloud.appwrite.io/v1/oauth2/console/userinfo
Authorization: Bearer <ACCESS_TOKEN>
```

The response is the signed-in user:

```json
{
  "sub": "6a150ace003bc4c2919e",
  "name": "Walter O'Brien",
  "email": "walter@example.com",
  "email_verified": true,
  "updated_at": 1784707579
}
```

The same token works in two places:

- The [listing endpoints](/docs/partners/apps/scopes#targets-granted), to see which projects and organizations the user granted.
- Every granted project's API, whatever region the project lives in.

Your app never handles regions itself. Take each project's `endpoint` from the listing, call it, and send the same token.

Access tokens last 8 hours for confidential clients and 1 hour for public ones.

### Refresh tokens

When the access token expires, exchange the refresh token for a new pair:

```curl
curl -X POST https://cloud.appwrite.io/v1/oauth2/console/token \
  -H "Content-Type: application/json" \
  -d '{
    "grant_type": "refresh_token",
    "refresh_token": "<REFRESH_TOKEN>",
    "client_id": "<CLIENT_ID>",
    "client_secret": "<CLIENT_SECRET>"
  }'
```

```hurl
POST https://cloud.appwrite.io/v1/oauth2/console/token
{
  "grant_type": "refresh_token",
  "refresh_token": "<REFRESH_TOKEN>",
  "client_id": "<CLIENT_ID>",
  "client_secret": "<CLIENT_SECRET>"
}
```

The response is a fresh pair, in the same shape as the original exchange:

```json
{
  "access_token": "eyJ0eXAiOiJhdCtqd3QiLCJhbGciOiJSUzI1NiJ9...",
  "token_type": "Bearer",
  "expires_in": 28800,
  "refresh_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
  "scope": "openid profile email project:databases.read",
  "id_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9..."
}
```

Refresh tokens are single-use. Each exchange invalidates the token you sent and returns a new one, so store the new token as soon as the response arrives.

This is deliberate. A refresh token that gets used twice looks like theft, and Appwrite responds by killing the entire token family. The user has to authorize your app again. The most common way to trip this by accident is a crash between refreshing and saving, after which your app retries with the token it already spent.

Refresh tokens last 365 days for confidential clients and 30 days for public ones. Treat them like passwords and store them encrypted.

### ID tokens

The ID token is an OpenID Connect JWT, issued when the grant includes `openid`. It answers one question: who signed in. Decoded, it looks like this:

```json
{
  "iss": "https://cloud.appwrite.io/v1/oauth2/console",
  "sub": "6a150ace003bc4c2919e",
  "aud": "horizon",
  "name": "Walter O'Brien",
  "email": "walter@example.com",
  "email_verified": true,
  "updated_at": 1784707579,
  "auth_time": 1784617272,
  "iat": 1784711196,
  "exp": 1784739996,
  "at_hash": "ro6WSzsVqgrWqQy4m7_DGA"
}
```

The granted scopes decide which of these fields the token carries:

- `profile` adds `name` and `updated_at`.
- `email` adds `email` and `email_verified`.
- `phone` adds `phone_number` and `phone_number_verified`.

Never use the ID token to call APIs; it only tells your app who signed in, and Appwrite rejects it as a bearer token. To fetch fresh values for the same fields, call the userinfo endpoint with the access token.

### Validating tokens

Your app never needs to check a token before using it. Every Appwrite API validates the bearer token on each call and rejects anything expired or revoked, so the normal move is to send the token and handle the `401`.

Decoding the token still helps in two places:

- **UX**: read `exp` locally and send the user to sign-in the moment the token expires, instead of letting a request fail first.
- **Your own API**: if your backend accepts Appwrite access tokens from its clients, verify them like any JWT against the published keys:

```text
https://cloud.appwrite.io/v1/oauth2/console/.well-known/jwks.json
```

Check the signature, the `iss` claim, the audience, the expiry, and require the `at+jwt` type so an ID token can never pass as an access token.

Signature checks cannot see revocation, so your own API accepts a revoked token until it expires; the short access token lifetime bounds that window. Appwrite's own APIs reject revoked tokens the moment revocation happens.

### Revocation

Revoke tokens when a user disconnects your app:

```curl
curl -X POST https://cloud.appwrite.io/v1/oauth2/console/revoke \
  -H "Content-Type: application/json" \
  -d '{
    "token": "<REFRESH_TOKEN>",
    "client_id": "<CLIENT_ID>",
    "client_secret": "<CLIENT_SECRET>"
  }'
```

```hurl
POST https://cloud.appwrite.io/v1/oauth2/console/revoke
{
  "token": "<REFRESH_TOKEN>",
  "client_id": "<CLIENT_ID>",
  "client_secret": "<CLIENT_SECRET>"
}
```

The response is an empty `200`, whether or not the token existed, as [RFC 7009](https://datatracker.ietf.org/doc/html/rfc7009) requires. Revoking either token of a pair invalidates both.

Revocation also arrives from outside your app. Users revoke from their account applications page, and reuse detection tears tokens down on its own. Treat a `401` with a previously working token as the signal to re-authorize, not to retry.

---

## Architecture

URL: https://appwrite.io/docs/partners/architecture

Partner platforms sit above Appwrite organizations and projects. Your backend creates the organization-level resources, then uses project credentials to manage each customer's Appwrite backend.

### High-level flow

1. **Your platform** authenticates with Appwrite using an OAuth token (user-linked) or a Partners key (platform-owned).
2. **Console APIs** manage the organization, its members, and the projects inside it.
3. **Project APIs** manage databases, storage, functions, auth users, and other resources inside each project.
4. **Your product UI** exposes a subset of these capabilities to your customers.

### OAuth connect architecture

With OAuth connect, a user signs in to Appwrite and grants your OAuth app permission to act on their organizations:

- Your platform stores refresh tokens on the server
- Console API calls use the user's delegated access
- Scopes limit which organizations your app can access and which operations it can perform
- Users can revoke the grant from Appwrite at any time

This model fits products where customers bring their own Appwrite organization or want explicit consent before you manage their infrastructure.

Learn more in [OAuth connect](/docs/partners/oauth-connect).

### Partners key architecture

With a Partners key, your platform acts as a trusted backend inside your own Appwrite organization:

- You create a project per customer and provision the resources it needs
- A Partners key authenticates Console API calls against one organization
- You map customers in your database to Appwrite project IDs
- Your backend calls Appwrite APIs on behalf of your product's UI

This model fits multi-tenant platforms that provision isolated Appwrite projects per customer.

Learn more in [Partners keys](/docs/partners/org-api-keys).

### Two credentials, two boundaries

Partner platforms hold two kinds of credential, and each one reaches a different layer.

| Credential | Client setup | Reaches |
| ---------- | ------------ | ------- |
| Partners key | `setOrganization('<ORGANIZATION_ID>')` and `setKey()` | The organization, its members, and each project's existence, name, and region |
| Project API key | `setProject('<PROJECT_ID>')` and `setKey()` | Databases, storage, functions, users, messaging, and proxy rules in one project |

A Partners key carries no project, so it never sends a project ID. It creates and deletes projects but cannot see inside them. Use it for provisioning, then switch to a project API key for the resources the customer works with every day.

### Security considerations

Both credentials belong on your server and nowhere else. [Keep the key safe](/docs/partners/org-api-keys#keep-the-key-safe) covers scoping, storage, and rotation.

### Next steps

[Create a project](/docs/partners/project/create)

---

## Domains API

URL: https://appwrite.io/docs/partners/domains

**Unlinked draft**

This page is not in the Partners navigation. The Domains endpoints still declare session-only auth in the API spec, so no server SDK ships a Domains service. The `domains.read` and `domains.write` scopes exist on Partners keys, so relink this page once the endpoints declare key auth and the SDKs generate the service.

The Domains API lets partner platforms register, transfer, and manage organization domains programmatically. Use it when your product offers custom hostnames or domain management to customers.

### Console SDK access

Domains are organization-level resources. Authenticate with an organization API key that includes domain scopes:

```server-nodejs
import { Client, Domains } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://cloud.appwrite.io/v1')
    .setProject('console')
    .setKey(process.env.APPWRITE_ORG_API_KEY);

const domains = new Domains(client);
```

### Common operations

| Operation | Use case |
| --------- | -------- |
| List domains | Show domains attached to an organization |
| Register / transfer | Sell or provision domains through your platform |
| Manage DNS | Create records for customer subdomains |
| Connect to products | Point hostnames to Sites, Functions, or custom APIs |

### Product hostnames

Organization domains prove control of a zone. Product hostnames (for Sites, Functions, or custom domains) are configured inside each project after DNS is verified.

See [Domains product docs](/docs/products/domains) for registration, DNS, and product connection flows.

---

## Marketplaces

URL: https://appwrite.io/docs/partners/guides/marketplaces

**Unlinked draft**

This page is not in the Partners navigation. The Apps API and its install flows are still settling, so the steps below are not verified end to end. Relink this page once the flow is confirmed.

Use this guide when you run a **marketplace** where Appwrite organizations discover, install, and manage third-party integrations. Your platform lists OAuth apps, starts authorization on install, and tracks which apps are connected to each customer organization.

### Marketplace roles

| Role | Responsibility |
| ---- | -------------- |
| **Marketplace operator** (you) | Curates the catalog, registers or approves OAuth apps, runs install and uninstall flows |
| **Integration developer** | Builds the app that requests Console scopes after install |
| **Organization admin** | Browses your marketplace and approves access to their Appwrite organization |

Appwrite separates **publishing** an OAuth client from **consuming** OAuth on behalf of a user:

- **[Apps API](/docs/partners/apps)**: create and manage OAuth app records (`client_id`, redirect URIs, secrets, branding)
- **[OAuth connect](/docs/partners/oauth-connect)**: send users through consent and call Console APIs with delegated tokens

### Architecture

Typical install flow:

1. Organization admin clicks **Install** in your marketplace
2. Your backend calls `oauth2.authorize` with the app's `clientId` and scopes
3. Admin signs in to Appwrite and approves consent
4. Appwrite redirects to your callback with an authorization code
5. Your backend exchanges the code for tokens and saves an installation record
6. The integration backend calls your API; you use stored tokens for Console API requests on that org

Your marketplace backend owns the catalog and installation state. Integration backends call **your** API; you proxy Console requests with stored tokens or pass short-lived delegated credentials according to your trust model.

### Step 1: Register apps with the Apps API

Each listing in your marketplace maps to one app. Register the app in the Console, in your organization's **Marketplace** tab, then import its client ID into your catalog. The slug you choose during registration becomes the client ID.

Save the metadata your marketplace UI needs: the client ID, name, logo URIs, requested scopes, and developer contact. See [Registration](/docs/partners/apps/registration) for the full set of Console fields, and [Installations](/docs/partners/apps/installations) for app keys and the install flow.

### Step 2: Build your catalog

Your catalog is application data outside Appwrite. A typical record:

| Field | Purpose |
| ----- | ------- |
| `appwriteAppId` | OAuth `client_id` from the Apps API |
| `name`, `description`, `logoUri` | Marketplace listing (can mirror app fields) |
| `scopes` | Scopes shown to the admin before install |
| `category` | Browse and search in your UI |
| `published` | Whether the listing is visible |

Use `apps.list` and `apps.get` to sync branding or enabled state from Appwrite when integrations update their app settings.

### Step 3: Start install with OAuth

When an organization admin clicks **Install**, start the authorization code flow with the app's `clientId` and the scopes that integration requires.

```server-nodejs
import { Oauth2 } from 'node-appwrite';

const oauth2 = new Oauth2(client);

const result = await oauth2.authorize({
    clientId: listing.appwriteAppId,
    redirectUri: 'https://marketplace.example.com/oauth/callback',
    responseType: 'code',
    scope: listing.scopes.join(' '),
    state: signedStateForOrgAndListing,
    prompt: 'consent',
});

if (result.redirectUrl) {
    // Send the admin to Appwrite sign-in and consent
    redirect(result.redirectUrl);
}
```

Validate `state` on callback to bind the authorization to the correct organization in your platform. Request only the scopes from [OAuth connect scopes](/docs/partners/oauth-connect/scopes) that the integration needs.

### Step 4: Exchange tokens and record the install

On your callback route, exchange the authorization code for access and refresh tokens using the app's client secret. Persist an **installation** row keyed by your tenant or organization ID and `appwriteAppId`.

```server-nodejs
// After code exchange (see OAuth connect setup)
await marketplaceInstalls.create({
    orgId: customer.appwriteOrgId,
    appId: listing.appwriteAppId,
    accessToken: encrypted(tokens.access),
    refreshToken: encrypted(tokens.refresh),
    scopes: listing.scopes,
    installedAt: new Date(),
});
```

Refresh access tokens before they expire. When an admin uninstalls, revoke the grant in Appwrite and delete stored tokens.

### Step 5: Let integrations act on installed orgs

After install, the integration backend calls your marketplace API with an install ID or org ID. Your backend uses the stored token to call Console APIs:

```server-nodejs
import { Client, Organization } from 'node-appwrite';

const install = await marketplaceInstalls.get(orgId, appId);

const userClient = new Client()
    .setEndpoint('https://cloud.appwrite.io/v1')
    .setOrganization(install.organizationId)
    .setBearer(await refreshAccessToken(install));

const organization = new Organization(userClient);
const list = await organization.listProjects();
```

If you would rather not let third-party servers hold long-lived refresh tokens, mint a short-lived token per request and pass that to the integration instead.

### Developer onboarding

If third parties publish into your marketplace:

1. Developer registers with your platform
2. You review scopes and redirect URIs
3. You create the OAuth app with the Apps API (or approve a Console-registered app)
4. You publish the listing after testing the install flow in a staging organization

Keep redirect URIs under domains you control (`marketplace.example.com/oauth/callback`) so token exchange stays on your backend.

### Uninstall and revocation

When an admin removes an integration:

1. Delete or disable the installation record in your database
2. Revoke refresh tokens and grants in Appwrite
3. Notify the integration developer if you operate webhooks for install lifecycle events

Treat uninstall as a security event: integrations must stop calling Console APIs immediately.

### Security checklist

- Register redirect URIs that match your callback exactly
- Store client secrets and refresh tokens encrypted at rest
- Show requested scopes in your UI before redirecting to consent
- Rotate secrets with `createSecret` and `deleteSecret` on the Apps API
- Audit installs per organization and alert on scope changes

### Related

- [Apps API](/docs/partners/apps): Create and manage OAuth app records.
- [OAuth connect setup](/docs/partners/oauth-connect/setup): Authorization code flow and token exchange.
- [OAuth scopes](/docs/partners/oauth-connect/scopes): Request the minimum Console scopes per integration.

---

## Multi-tenancy

URL: https://appwrite.io/docs/partners/guides/multi-tenancy

**Unlinked draft**

This page is not in the Partners navigation. It predates the Partners key and still describes credentials and flows that do not match the current Organization API. Relink it once the patterns are rewritten against a Partners key.

Multi-tenant platforms give each customer an isolated Appwrite backend while sharing operational tooling on your side.

### Tenant isolation model

The recommended pattern is **one Appwrite project per tenant**:

- Data and permissions stay isolated by project boundary
- Project API keys can be scoped per tenant
- Blast radius of a leaked key is limited to one customer
- You can place tenants in different [regions](/docs/products/network/regions)

### Mapping tenants

Maintain a table in your platform:

| Your tenant ID | Appwrite org ID | Appwrite project ID | Region |
| -------------- | --------------- | ------------------- | ------ |

Use this mapping for every Console and project SDK call.

### Hybrid auth

Many platforms use:

- **Partners keys** for automated provisioning in the platform organization
- **OAuth connect** for enterprise customers who link their own Appwrite organization

Route requests based on whether the tenant is platform-provisioned or OAuth-linked.

### Onboarding checklist

1. Create project (Console API)
2. Create scoped project API key
3. Bootstrap default resources
4. Register platforms if the tenant uses client SDKs directly
5. Optionally attach custom domain

### Related

- [Provisioning](/docs/partners/guides/provisioning): Automate project creation on signup.
- [Architecture](/docs/partners/architecture): Compare OAuth connect and Partners keys.

---

## Provisioning

URL: https://appwrite.io/docs/partners/guides/provisioning

**Unlinked draft**

This page is not in the Partners navigation. It predates the Partners key and still describes credentials and flows that do not match the current Organization API. Relink it once the flow is rewritten against a Partners key.

This guide walks through provisioning a dedicated Appwrite project when a customer signs up for your platform.

### Flow overview

1. Customer completes signup in your product
2. Your backend creates or selects an Appwrite organization
3. Console API creates a new project in the target region
4. Your backend creates a scoped project API key
5. Your platform stores the mapping and initializes default resources

### Step 1: Create the project

```server-nodejs
const project = await organization.createProject({
    projectId: ID.unique(),
    name: `${customer.name} workspace`,
    region: customer.preferredRegion ?? 'fra',
});

await db.customers.update(customer.id, {
    appwriteProjectId: project.$id,
    appwriteRegion: project.region,
});
```

### Step 2: Create a project API key

Create a key with scopes for the services your platform manages. Store the secret in your secrets manager keyed by customer ID. A Partners key cannot mint project API keys, so create this key in the Console.

### Step 3: Bootstrap resources

Use the project SDK to create default databases, buckets, or functions your product expects. Keep bootstrap idempotent so retries are safe.

### Step 4: Expose to your UI

Your customer-facing UI calls **your** API. Your backend uses the stored project credentials to call Appwrite.

### Related

[Provision a project](/docs/partners/project/provisioning)

---

## OAuth connect

URL: https://appwrite.io/docs/partners/oauth-connect

OAuth connect lets your platform access a user's Appwrite organizations after they sign in and grant consent. Use it when customers already have Appwrite accounts and want to link them to your product without sharing passwords or API keys.

### How it works

1. Register an **OAuth app** in your Appwrite organization settings
2. Redirect users to Appwrite's authorization endpoint with your client ID and requested scopes
3. After consent, exchange the authorization code for access and refresh tokens on your server
4. Call Console APIs with the user's delegated access token

Your platform never stores the user's Appwrite password. Access is limited to the scopes the user approved and can be revoked from Appwrite.

### When to use OAuth connect

Choose OAuth connect when:

- Users manage their own Appwrite organizations
- You need explicit consent before accessing org or project data
- Your product enhances an existing Appwrite workflow rather than provisioning new orgs

For platform-owned provisioning inside your organization, use [Partners keys](/docs/partners/org-api-keys) instead or alongside OAuth connect.

### Getting started

- [Setup](/docs/partners/oauth-connect/setup): Register an OAuth app and implement the authorization flow.
- [Scopes](/docs/partners/oauth-connect/scopes): Request the minimum Console scopes your integration needs.

### Related

OAuth connect is different from [OAuth2 login](/docs/products/auth/oauth2) in project Auth, which authenticates **end users of your app**. Partner OAuth connect authorizes **your platform** to call Console APIs for a user's organizations.

---

## OAuth connect scopes

URL: https://appwrite.io/docs/partners/oauth-connect/scopes

OAuth scopes define what your platform can do after a user grants consent. Request only the scopes required for your integration.

### Scope principles

- Start with read-only scopes if your product only displays Appwrite data
- Add write scopes when your platform creates or updates resources
- Separate scopes for organizations, projects, and domains where possible
- Document which features require which scopes in your product UI

### Common scope categories

| Category | Typical use |
| -------- | ----------- |
| Organization read | List organizations the user belongs to |
| Organization write | Create or update organization settings |
| Projects read | List and inspect projects |
| Projects write | Create projects and update project settings |
| Domains read | List organization domains and DNS |
| Domains write | Register domains and manage DNS records |

Exact scope names are listed in the Appwrite Console when you configure your OAuth app.

### Least privilege

If your platform only provisions projects inside organizations the user selects, request project write scopes without domain write unless you manage hostnames.

If users can disconnect your integration, delete stored tokens and stop calling Console APIs immediately.

### Related

[OAuth connect setup](/docs/partners/oauth-connect/setup)

---

## OAuth connect setup

URL: https://appwrite.io/docs/partners/oauth-connect/setup

1. Open your organization in the Appwrite Console
2. Go to **Settings** > **OAuth apps**
3. Create an app with your platform name and redirect URIs
4. Copy the **client ID** and **client secret** to your server environment

Redirect URIs must match exactly what your backend uses to receive the authorization code.

### Authorization flow

1. Send the user to Appwrite's authorize URL with `client_id`, `redirect_uri`, `response_type=code`, and `scope`
2. User signs in and approves the requested scopes
3. Appwrite redirects back with an authorization `code`
4. Your server exchanges the code for tokens using the token endpoint and your client secret
5. Store refresh tokens securely and use access tokens for Console API calls

### Call the API with a user token

After the token exchange, set the access token as a bearer token. Pass the organization the user selected, the same way a Partners key does.

```server-nodejs
import { Client, Organization } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://cloud.appwrite.io/v1')
    .setOrganization('<ORGANIZATION_ID>')
    .setBearer(userAccessToken);

const organization = new Organization(client);
const org = await organization.get();
```
```server-rust
use appwrite::client::Client;
use appwrite::services::Organization;

let client = Client::new()
    .set_endpoint("https://cloud.appwrite.io/v1")
    .set_organization("<ORGANIZATION_ID>")
    .set_bearer(user_access_token);

let organization = Organization::new(&client);
let org = organization.get().await?;
```

Refresh access tokens before they expire using the refresh token Appwrite returned during exchange.

### Production checklist

- Use HTTPS redirect URIs only
- Validate `state` to prevent CSRF
- Store client secret and refresh tokens in a secrets manager
- Request the minimum scopes from [OAuth scopes](/docs/partners/oauth-connect/scopes)
- Handle revocation when users disconnect your integration

---

## Partners keys

URL: https://appwrite.io/docs/partners/org-api-keys

A Partners key authenticates server-to-server calls against one Appwrite organization. Your backend uses it to create projects, manage members, and register app installations for the customers you host.

Every request sends the key and the organization ID, and no project ID, so one key can never act on another organization.

### Create a Partners key

![Partners keys card in organization settings](/images/docs/partners/keys/partners-keys.avif)

In your organization, open **Settings** > **Partners** and find the **Partners keys** card. Click **Create API key**.

![Create API key panel with scopes selected](/images/docs/partners/keys/create-key.avif)

Give the key a name and choose an expiration. Select the scopes your backend needs, listed in [Scopes](#scopes). The secret starts with `organization_` and appears in the card once you save.

### Use the key

```server-nodejs
import { Client, Organization } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://cloud.appwrite.io/v1')
    .setOrganization('<ORGANIZATION_ID>')
    .setKey(process.env.APPWRITE_PARTNERS_KEY);

const organization = new Organization(client);
```
```server-rust
use appwrite::client::Client;
use appwrite::services::Organization;

let client = Client::new()
    .set_endpoint("https://cloud.appwrite.io/v1")
    .set_organization("<ORGANIZATION_ID>")
    .set_key(std::env::var("APPWRITE_PARTNERS_KEY")?);

let organization = Organization::new(&client);
```

If the organization ID does not match the key, Appwrite rejects the request.

### What a Partners key reaches

A Partners key creates, renames, and deletes projects, and it cannot read or write anything inside one. Databases, storage, users, functions, and proxy rules all need a project API key with the matching project scopes.

The key also has no billing scopes, so plans, invoices, credits, and payment methods stay in the Console.

### When to use a Partners key

Choose a Partners key when:

- Your platform creates a dedicated Appwrite project per customer
- Your backend runs a control plane over an organization you operate
- Your customers never sign in to Appwrite themselves

Use [OAuth connect](/docs/partners/oauth-connect) instead when customers bring their own Appwrite organizations and grant your product access. Many platforms run both.

### Keep the key safe

A key with project write scopes can create and delete every project in the organization. Treat it the way you treat a root credential.

- Keep the secret on your server. It does not belong in a browser, a mobile app, or a repository.
- Store it in a secrets manager and limit access to your deployment pipeline.
- Create one key per environment so a leaked staging key cannot reach production.
- Use a read-only key for dashboards so monitoring never holds write scopes.
- Rotate on a schedule: create the replacement, deploy it, then delete the old key.
- Set an expiration on keys you issue for a one-off migration or an audit.

### Scopes

A Partners key holds a fixed set of scopes, chosen when the key is created. The Console groups them under Organization and Projects.

| Organization scope | Grants |
| ------------------ | ------ |
| `organization.read` | Read the organization |
| `organization.write` | Update and delete the organization |
| `organization.memberships.read` | Read organization memberships |
| `organization.memberships.write` | Create, update, and delete organization memberships |
| `organization.keys.read` | Read organization keys |
| `organization.keys.write` | Create, update, and delete organization keys |
| `organization.installations.read` | Read organization app installations |
| `organization.installations.write` | Create, update, and delete organization app installations |
| `domains.read` | Read the organization's domains |
| `domains.write` | Create, update, and delete the organization's domains |

| Project scope | Grants |
| ------------- | ------ |
| `projects.read` | Read the organization's projects |
| `projects.write` | Create, update, and delete the organization's projects |

The two project scopes cover each project's existence, name, and region. They do not reach the services inside a project.

`keys.read` and `keys.write` still appear in the Console and are deprecated. Use `organization.keys.read` and `organization.keys.write` instead.

### Next steps

- [Organization API](/docs/partners/organizations): The operations a Partners key can call.
- [Project](/docs/partners/project): Configure each customer's project from your backend.

---

## Organization API

URL: https://appwrite.io/docs/partners/organizations

The Organization API manages one organization and everything inside it: projects, members, app installations, and the organization's own API keys. Partner platforms call it from a backend to provision customer workspaces.

### Authentication

Authenticate with a Partners key or a user's OAuth access token. A Partners key is bound to the organization that issued it, so every call applies to that organization and no other. Pass the organization ID with the key.

```server-nodejs
import { Client, Organization } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://cloud.appwrite.io/v1')
    .setOrganization('<ORGANIZATION_ID>')
    .setKey(process.env.APPWRITE_PARTNERS_KEY);

const organization = new Organization(client);
```
```server-rust
use appwrite::client::Client;
use appwrite::services::Organization;

let client = Client::new()
    .set_endpoint("https://cloud.appwrite.io/v1")
    .set_organization("<ORGANIZATION_ID>")
    .set_key(std::env::var("APPWRITE_PARTNERS_KEY")?);

let organization = Organization::new(&client);
```

### Operations

| Group | Operations | Scopes |
| ----- | ---------- | ------ |
| Organization | `get`, `update`, `delete` | `organization.read`, `organization.write` |
| Projects | `listProjects`, `createProject`, `getProject`, `updateProject`, `deleteProject` | `projects.read`, `projects.write` |
| Members | `listMemberships`, `createMembership`, `getMembership`, `updateMembership`, `deleteMembership` | `organization.memberships.read`, `organization.memberships.write` |
| Keys | `listKeys`, `createKey`, `getKey`, `updateKey`, `deleteKey` | `organization.keys.read`, `organization.keys.write` |
| Installations | `listInstallations`, `createInstallation`, `getInstallation`, `updateInstallation`, `deleteInstallation` | `organization.installations.read`, `organization.installations.write` |

Billing, plans, and invoices are not part of this API and a Partners key cannot reach them.

### Where each credential applies

A Partners key covers the organization and each project's existence, name, and region. It cannot read or write databases, storage, users, or functions. For work inside a project, create a project API key and see [Project](/docs/partners/project).

With OAuth connect, the access token carries the scopes the user granted, and the calls apply to the organization the user chose.

### Next steps

- [Manage organizations](/docs/partners/organizations/manage): Read, rename, and delete the organization your key belongs to.
- [Members and roles](/docs/partners/organizations/members): Invite members and assign organization roles from your platform.

---

## Manage organizations

URL: https://appwrite.io/docs/partners/organizations/manage

A Partners key acts on the organization it was created in. The read, update, and delete operations take no organization parameter, because the key already names the organization.

Creating an organization is a Console action and a Partners key cannot do it. Create the organization in the Console first, then create a key inside it.

### Read the organization

```server-nodejs
const org = await organization.get();

console.log(org.$id, org.name);
```
```server-rust
let org = organization.get().await?;

println!("{} {}", org.id, org.name);
```

### Rename the organization

The update operation takes the name and nothing else. Billing details stay in the Console.

```server-nodejs
await organization.update({
    name: 'Acme Platform',
});
```
```server-rust
organization.update("Acme Platform").await?;
```

### Delete the organization

Deleting an organization also deletes every project inside it. Back up customer data before you call this operation.

```server-nodejs
await organization.delete();
```
```server-rust
organization.delete().await?;
```

### Store the organization ID

Store the Appwrite organization ID next to your platform's own identifier for it. Your audit logs need the mapping to tie an Appwrite change back to a customer.

### Related

[Project](/docs/partners/project)

---

## Members and roles

URL: https://appwrite.io/docs/partners/organizations/members

Partner platforms often invite customer admins to an Appwrite organization or sync membership from an existing identity provider.

### Organization roles

Appwrite organizations use roles such as owner, developer, and analyst to control Console access. Your platform should assign the minimum role required for each member.

When you proxy Console access, enforce the same role boundaries in your product UI.

### Invite members

Use organization membership APIs to invite users by email. Invited users receive Appwrite Console access according to the role you assign.

Store membership state in your platform so you can show pending invites and revoke access when a customer removes a user from your product.

### OAuth-linked organizations

When using [OAuth connect](/docs/partners/oauth-connect), membership changes may originate in Appwrite or in your product. Decide which system is the source of truth and sync accordingly.

### Related

[Platform roles](/docs/advanced/security/roles)

---

## Project

URL: https://appwrite.io/docs/partners/project

An Appwrite **Project** is the top-level container for all the resources your app uses, from users and databases to storage buckets and functions. The settings on a project control which authentication methods are available, which client platforms can connect, which protocols and services are exposed, and which policies apply to the resources inside it.

### Built for platform teams

The Console configures one project at a time by hand. The Project API does the same configuration programmatically, which is what teams need when they operate not one project but many: a platform that gives every customer their own project, an agency running client environments, or an enterprise standardizing dozens of internal apps.

Driving configuration from code is what makes a fleet of projects manageable. Provision every new project to the same baseline, enforce one security posture across all of them, and send each customer branded emails from their own domain. The [guides](#guides) below walk through each of these end to end.

### Concepts

- [Auth methods](/docs/partners/project/auth-methods): Enable or disable authentication methods on your project.
- [OAuth providers](/docs/partners/project/oauth): Configure OAuth2 sign-in providers like Google, GitHub, and Apple.
- [API keys](/docs/partners/project/api-keys): Create and manage API keys used by Server SDKs.
- [Platforms](/docs/partners/project/platforms): Register the apps and origins that are allowed to call your project.
- [Protocols](/docs/partners/project/protocols): Toggle the API protocols clients can use to reach your project.
- [Services](/docs/partners/project/services): Turn individual Appwrite services on or off for your project.
- [Policies](/docs/partners/project/policies): Configure project-wide policies that apply across resources.
- [Mock phones](/docs/partners/project/mock-phones): Register fictional phone numbers and OTPs to test phone authentication.
- [Environment variables](/docs/partners/project/environment-variables): Share constants and secrets across your functions and sites.
- [SMTP](/docs/partners/project/smtp): Send emails from your own custom SMTP server.
- [Email templates](/docs/partners/project/email-templates): Customize the account management emails Appwrite sends to your users.
- [Labels](/docs/partners/project/labels): Tag projects with customizable labels to organize and filter them.

### Guides

- [Provisioning](/docs/partners/project/provisioning): Apply a standard baseline to a new customer's project from a script.
- [Key rotation](/docs/partners/project/key-rotation): Issue, audit, rotate, and revoke scoped API keys programmatically.
- [Branded emails](/docs/partners/project/branded-emails): Route a project's mail through custom SMTP and rebrand the templates.

### Retrieve your project

Fetch your project's current configuration in a single call, including its name, region, labels, and the settings managed on the pages above. Use a [Server SDK](/docs/sdks#server).

**Required scope**

The API key used for this call needs the `project.read` scope.

```server-nodejs
import { Client, Project } from '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 API key

const project = new Project(client);

const result = await project.get();
```
```server-deno
import { Client, Project } 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 API key

const project = new Project(client);

const result = await project.get();
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Project;

$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 API key

$project = new Project($client);

$result = $project->get();
```
```server-python
from appwrite.client import Client
from appwrite.services.project import Project
from appwrite.models import Project as ProjectModel

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 API key

project = Project(client)

result: ProjectModel = project.get()

print(result.model_dump())
```
```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 API key

project = Project.new(client)

result = project.get
```
```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 API key

Project project = new Project(client);

Project result = await project.Get();
```
```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 API key

Project project = Project(client);

Project result = await project.get();
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.coroutines.CoroutineCallback
import io.appwrite.services.Project

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 API key

val project = Project(client)

val response = project.get()
```
```server-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Project;

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 API key

Project project = new Project(client);

project.get(new CoroutineCallback<>((result, error) -> {
    if (error != null) {
        error.printStackTrace();
        return;
    }

    System.out.println(result);
}));
```
```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 API key

let project = Project(client)

let result = try await project.get()
```
```server-go
package main

import (
    "fmt"
    "github.com/appwrite/sdk-for-go/appwrite"
)

func main() {
    client := appwrite.NewClient(
        appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"),
        appwrite.WithProject("<YOUR_PROJECT_ID>"),
        appwrite.WithKey("<YOUR_API_KEY>"),
    )

    project := appwrite.NewProject(client)
    result, err := project.Get()

    if err != nil {
        panic(err)
    }

    fmt.Println(result)
}
```
```server-rust
use appwrite::Client;
use appwrite::services::Project;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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 API key

    let project = Project::new(&client);

    let result = project.get().await?;

    let _ = result;

    Ok(())
}
```

### Delete a project

**Permanent and irreversible**

Deleting a project permanently removes it along with every resource inside it, including users, databases, storage, functions, and sites. This action cannot be undone. The call targets the project tied to the API key and endpoint you initialize the SDK with, so make sure you are pointing at the project you intend to delete.

The `delete` method requires an API key with the `project.write` scope. It takes no parameters and acts on the project the request is scoped to.

```server-nodejs
import { Client, Project } from '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 API key

const project = new Project(client);

const result = await project.delete();
```
```server-deno
import { Client, Project } 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 API key

const project = new Project(client);

const result = await project.delete();
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Project;

$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 API key

$project = new Project($client);

$result = $project->delete();
```
```server-python
from appwrite.client import Client
from appwrite.services.project import Project

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 API key

project = Project(client)

result = project.delete()
```
```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 API key

project = Project.new(client)

result = project.delete()
```
```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 API key

Project project = new Project(client);

await project.Delete();
```
```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 API key

Project project = Project(client);

await project.delete();
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.coroutines.CoroutineCallback
import io.appwrite.services.Project

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 API key

val project = Project(client)

val response = project.delete()
```
```server-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Project;

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 API key

Project project = new Project(client);

project.delete(new CoroutineCallback<>((result, error) -> {
    if (error != null) {
        error.printStackTrace();
        return;
    }

    System.out.println(result);
}));
```
```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 API key

let project = Project(client)

let result = try await project.delete()
```
```server-go
package main

import (
    "fmt"
    "github.com/appwrite/sdk-for-go/appwrite"
)

func main() {
    client := appwrite.NewClient(
        appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"),
        appwrite.WithProject("<YOUR_PROJECT_ID>"),
        appwrite.WithKey("<YOUR_API_KEY>"),
    )

    project := appwrite.NewProject(client)
    result, err := project.Delete()

    if err != nil {
        panic(err)
    }

    fmt.Println(result)
}
```
```server-rust
use appwrite::Client;
use appwrite::services::Project;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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 API key

    let project = Project::new(&client);

    project.delete().await?;

    Ok(())
}
```
```bash
appwrite project delete
```

---

## API keys

URL: https://appwrite.io/docs/partners/project/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.

**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.

### API keys vs Dev keys

API keys and [Dev keys](/docs/advanced/security/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

![Project settings screen](/images/docs/project/create-api-key.avif)

To create a new API key, open **API Keys** in the project sidebar and click **Create API key**.

You can then use the API key to initialize the Appwrite client in your server-side apps.

```server-nodejs
const sdk = require('node-appwrite');

const client = new sdk.Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<YOUR_PROJECT_ID>')
    .setKey('<YOUR_API_KEY>'); // Your API key
```

```server-python
from appwrite.client import Client

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<YOUR_PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')  # Your API key
```

```server-php
use Appwrite\Client;

$client = new Client();
$client
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<YOUR_PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>'); // Your API key
```

```server-go
package main

import "github.com/appwrite/sdk-for-go/client"

func main() {
    c := client.NewClient()
    c.SetEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    c.SetProject("<YOUR_PROJECT_ID>")
    c.SetKey("<YOUR_API_KEY>") // Your API key
}
```

```server-ruby
require 'appwrite'

client = Appwrite::Client.new
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<YOUR_PROJECT_ID>')
client.set_key('<YOUR_API_KEY>') # Your API key
```

```server-deno
import { Client } from 'npm:node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<YOUR_PROJECT_ID>')
    .setKey('<YOUR_API_KEY>'); // Your API key
```

```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<YOUR_PROJECT_ID>')
    .setKey('<YOUR_API_KEY>'); // Your API key
```

```server-kotlin
import io.appwrite.Client

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<YOUR_PROJECT_ID>")
    .setKey("<YOUR_API_KEY>") // Your API key
```

```server-dotnet
using Appwrite;

var client = new Client()
    .SetEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<YOUR_PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>"); // Your API key
```

```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<YOUR_PROJECT_ID>")
    .setKey("<YOUR_API_KEY>") // Your API key
```

```server-java
import io.appwrite.Client;

Client client = new Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<YOUR_PROJECT_ID>")
    .setKey("<YOUR_API_KEY>"); // Your API key
```

```server-rust
use appwrite::Client;

let client = Client::new()
    .set_endpoint("https://<REGION>.cloud.appwrite.io/v1")
    .set_project("<YOUR_PROJECT_ID>")
    .set_key("<YOUR_API_KEY>"); // Your API key
```

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.

### Manage API keys with a Server SDK

You can also manage API keys programmatically using a Server SDK. This requires an API key with the `keys.read` and `keys.write` [scopes](#scopes).

**Creating keys**

Creating API keys with the Project API is deprecated as of 17 August 2026. Create long-lived keys in the Appwrite Console. The Project API can list, update, and delete existing keys, and can issue [ephemeral keys](#create-an-ephemeral-key) for short-lived access. [Learn more in the changelog](/changelog/entry/2026-08-17).

**Sensitive scopes**

The `keys.read` and `keys.write` scopes are sensitive. An API key with `keys.write` can change the scopes of existing keys and delete them. Only assign these scopes to keys used in trusted, secure environments, and never expose them in client-side applications.

#### List API keys

```server-nodejs
import { Client, Project } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.listKeys();
```
```server-deno
import { Client, Project } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.listKeys();
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Project;

$client = new Client();

$client
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$project = new Project($client);

$result = $project->listKeys();
```
```server-python
from appwrite.client import Client
from appwrite.services.project import Project

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

project = Project(client)

result = project.list_keys()
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

project = Project.new(client)

response = project.list_keys()
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Project project = new Project(client);

var result = await project.ListKeys();
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Project project = Project(client);

final result = await project.listKeys();
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Project

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val project = Project(client)

val result = project.listKeys()
```
```server-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Project;

Client client = new Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>");

Project project = new Project(client);

project.listKeys(
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }
        System.out.println(result);
    })
);
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let project = Project(client)

let result = try await project.listKeys()
```
```server-go
package main

import (
    "fmt"
    "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("<YOUR_API_KEY>"),
    )

    project := appwrite.NewProject(client)
    result, err := project.ListKeys()

    if err != nil {
        panic(err)
    }

    fmt.Println(result)
}
```
```server-rust
use appwrite::Client;
use appwrite::services::project::Project;

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

    let project = Project::new(&client);

    let result = project.list_keys(
        None,  // queries
        None,  // total
    ).await?;

    println!("{:?}", result);
    Ok(())
}
```

#### Get an API key

```server-nodejs
import { Client, Project } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.getKey({
    keyId: '<KEY_ID>'
});
```
```server-deno
import { Client, Project } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.getKey({
    keyId: '<KEY_ID>'
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Project;

$client = new Client();

$client
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$project = new Project($client);

$result = $project->getKey(
    keyId: '<KEY_ID>'
);
```
```server-python
from appwrite.client import Client
from appwrite.services.project import Project

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

project = Project(client)

result = project.get_key(
    key_id='<KEY_ID>'
)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

project = Project.new(client)

response = project.get_key(
    key_id: '<KEY_ID>'
)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Project project = new Project(client);

var result = await project.GetKey(
    keyId: "<KEY_ID>"
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Project project = Project(client);

final result = await project.getKey(
    keyId: '<KEY_ID>',
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Project

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val project = Project(client)

val result = project.getKey(
    keyId = "<KEY_ID>"
)
```
```server-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Project;

Client client = new Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>");

Project project = new Project(client);

project.getKey(
    "<KEY_ID>",                                      // keyId
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }
        System.out.println(result);
    })
);
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let project = Project(client)

let result = try await project.getKey(
    keyId: "<KEY_ID>"
)
```
```server-go
package main

import (
    "fmt"
    "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("<YOUR_API_KEY>"),
    )

    project := appwrite.NewProject(client)
    result, err := project.GetKey(
        "<KEY_ID>",
    )

    if err != nil {
        panic(err)
    }

    fmt.Println(result)
}
```
```server-rust
use appwrite::Client;
use appwrite::services::project::Project;

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

    let project = Project::new(&client);

    let result = project.get_key(
        "<KEY_ID>",
    ).await?;

    println!("{:?}", result);
    Ok(())
}
```

#### Create an ephemeral key

Ephemeral keys are short-lived API keys that expire after a set duration, useful for temporary or single-session server access without managing a long-lived key. Pass the `scopes` to grant and a `duration` in seconds (maximum 3600). The response includes the generated secret and its expiration time.

```server-nodejs
import { Client, Project, ProjectKeyScopes } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.createEphemeralKey({
    scopes: [ProjectKeyScopes.TablesRead, ProjectKeyScopes.TablesWrite],
    duration: 600
});
```
```server-deno
import { Client, Project, ProjectKeyScopes } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.createEphemeralKey({
    scopes: [ProjectKeyScopes.TablesRead, ProjectKeyScopes.TablesWrite],
    duration: 600
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Project;

$client = new Client();

$client
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$project = new Project($client);

$result = $project->createEphemeralKey(
    scopes: ['tables.read', 'tables.write'],
    duration: 600
);
```
```server-python
from appwrite.client import Client
from appwrite.services.project import Project
from appwrite.enums import ProjectKeyScopes

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

project = Project(client)

result = project.create_ephemeral_key(
    scopes=[ProjectKeyScopes.TABLES_READ, ProjectKeyScopes.TABLES_WRITE],
    duration=600
)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

project = Project.new(client)

response = project.create_ephemeral_key(
    scopes: ['tables.read', 'tables.write'],
    duration: 600
)
```
```server-dotnet
using Appwrite;
using Appwrite.Enums;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Project project = new Project(client);

var result = await project.CreateEphemeralKey(
    scopes: new List<ProjectKeyScopes> {ProjectKeyScopes.TablesRead, ProjectKeyScopes.TablesWrite},
    duration: 600
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';
import 'package:dart_appwrite/enums.dart' as enums;

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Project project = Project(client);

final result = await project.createEphemeralKey(
    scopes: [enums.ProjectKeyScopes.tablesRead, enums.ProjectKeyScopes.tablesWrite],
    duration: 600,
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.enums.ProjectKeyScopes
import io.appwrite.services.Project

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val project = Project(client)

val result = project.createEphemeralKey(
    scopes = listOf(ProjectKeyScopes.TABLES_READ, ProjectKeyScopes.TABLES_WRITE),
    duration = 600
)
```
```server-java
import io.appwrite.Client;
import io.appwrite.enums.ProjectKeyScopes;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Project;

Client client = new Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>");

Project project = new Project(client);

project.createEphemeralKey(
    List.of(ProjectKeyScopes.TABLES_READ, ProjectKeyScopes.TABLES_WRITE), // scopes
    600, // duration
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }
        System.out.println(result);
    })
);
```
```server-swift
import Appwrite
import AppwriteEnums

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let project = Project(client)

let result = try await project.createEphemeralKey(
    scopes: [ProjectKeyScopes.tablesRead, ProjectKeyScopes.tablesWrite],
    duration: 600
)
```
```server-go
package main

import (
    "fmt"
    "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("<YOUR_API_KEY>"),
    )

    project := appwrite.NewProject(client)
    result, err := project.CreateEphemeralKey(
        []string{"tables.read", "tables.write"},
        600,
    )

    if err != nil {
        panic(err)
    }

    fmt.Println(result)
}
```
```server-rust
use appwrite::Client;
use appwrite::enums::ProjectKeyScopes;
use appwrite::services::project::Project;

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

    let project = Project::new(&client);

    let result = project.create_ephemeral_key(
        vec![ProjectKeyScopes::TablesRead, ProjectKeyScopes::TablesWrite], // scopes
        600, // duration
    ).await?;

    println!("{:?}", result);
    Ok(())
}
```

#### Update an API key

```server-nodejs
import { Client, Project, ProjectKeyScopes } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.updateKey({
    keyId: '<KEY_ID>',
    name: 'Updated Key',
    scopes: [ProjectKeyScopes.DatabasesRead, ProjectKeyScopes.DatabasesWrite, ProjectKeyScopes.UsersRead],
    expire: '2027-06-30T23:59:59.000+00:00'
});
```
```server-deno
import { Client, Project, ProjectKeyScopes } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.updateKey({
    keyId: '<KEY_ID>',
    name: 'Updated Key',
    scopes: [ProjectKeyScopes.DatabasesRead, ProjectKeyScopes.DatabasesWrite, ProjectKeyScopes.UsersRead],
    expire: '2027-06-30T23:59:59.000+00:00'
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Project;

$client = new Client();

$client
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$project = new Project($client);

$result = $project->updateKey(
    keyId: '<KEY_ID>',
    name: 'Updated Key',
    scopes: ['databases.read', 'databases.write', 'users.read'],
    expire: '2027-06-30T23:59:59.000+00:00'
);
```
```server-python
from appwrite.client import Client
from appwrite.services.project import Project
from appwrite.enums import ProjectKeyScopes

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

project = Project(client)

result = project.update_key(
    key_id='<KEY_ID>',
    name='Updated Key',
    scopes=[ProjectKeyScopes.DATABASES_READ, ProjectKeyScopes.DATABASES_WRITE, ProjectKeyScopes.USERS_READ],
    expire='2027-06-30T23:59:59.000+00:00'
)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

project = Project.new(client)

response = project.update_key(
    key_id: '<KEY_ID>',
    name: 'Updated Key',
    scopes: ['databases.read', 'databases.write', 'users.read'],
    expire: '2027-06-30T23:59:59.000+00:00'
)
```
```server-dotnet
using Appwrite;
using Appwrite.Enums;
using Appwrite.Services;
using Appwrite.Models;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Project project = new Project(client);

var result = await project.UpdateKey(
    keyId: "<KEY_ID>",
    name: "Updated Key",
    scopes: new List<ProjectKeyScopes> {ProjectKeyScopes.DatabasesRead, ProjectKeyScopes.DatabasesWrite, ProjectKeyScopes.UsersRead},
    expire: "2027-06-30T23:59:59.000+00:00"
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';
import 'package:dart_appwrite/enums.dart' as enums;

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Project project = Project(client);

final result = await project.updateKey(
    keyId: '<KEY_ID>',
    name: 'Updated Key',
    scopes: [enums.ProjectKeyScopes.databasesRead, enums.ProjectKeyScopes.databasesWrite, enums.ProjectKeyScopes.usersRead],
    expire: '2027-06-30T23:59:59.000+00:00',
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.enums.ProjectKeyScopes
import io.appwrite.services.Project

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val project = Project(client)

val result = project.updateKey(
    keyId = "<KEY_ID>",
    name = "Updated Key",
    scopes = listOf(ProjectKeyScopes.DATABASES_READ, ProjectKeyScopes.DATABASES_WRITE, ProjectKeyScopes.USERS_READ),
    expire = "2027-06-30T23:59:59.000+00:00"
)
```
```server-java
import io.appwrite.Client;
import io.appwrite.enums.ProjectKeyScopes;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Project;

Client client = new Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>");

Project project = new Project(client);

project.updateKey(
    "<KEY_ID>",                                      // keyId
    "Updated Key",                                   // name
    List.of(ProjectKeyScopes.DATABASES_READ, ProjectKeyScopes.DATABASES_WRITE, ProjectKeyScopes.USERS_READ), // scopes
    "2027-06-30T23:59:59.000+00:00",                // expire
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }
        System.out.println(result);
    })
);
```
```server-swift
import Appwrite
import AppwriteEnums

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let project = Project(client)

let result = try await project.updateKey(
    keyId: "<KEY_ID>",
    name: "Updated Key",
    scopes: [ProjectKeyScopes.databasesRead, ProjectKeyScopes.databasesWrite, ProjectKeyScopes.usersRead],
    expire: "2027-06-30T23:59:59.000+00:00"
)
```
```server-go
package main

import (
    "fmt"
    "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("<YOUR_API_KEY>"),
    )

    project := appwrite.NewProject(client)
    result, err := project.UpdateKey(
        "<KEY_ID>",
        "Updated Key",
        []string{"databases.read", "databases.write", "users.read"},
        project.WithUpdateKeyExpire("2027-06-30T23:59:59.000+00:00"),
    )

    if err != nil {
        panic(err)
    }

    fmt.Println(result)
}
```
```server-rust
use appwrite::Client;
use appwrite::enums::ProjectKeyScopes;
use appwrite::services::project::Project;

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

    let project = Project::new(&client);

    let result = project.update_key(
        "<KEY_ID>",                                    // key_id
        "Updated Key",                                 // name
        vec![ProjectKeyScopes::DatabasesRead, ProjectKeyScopes::DatabasesWrite, ProjectKeyScopes::UsersRead], // scopes
        Some("2027-06-30T23:59:59.000+00:00"),         // expire
    ).await?;

    println!("{:?}", result);
    Ok(())
}
```

#### Delete an API key

```server-nodejs
import { Client, Project } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

await project.deleteKey({
    keyId: '<KEY_ID>'
});
```
```server-deno
import { Client, Project } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

await project.deleteKey({
    keyId: '<KEY_ID>'
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Project;

$client = new Client();

$client
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$project = new Project($client);

$project->deleteKey(
    keyId: '<KEY_ID>'
);
```
```server-python
from appwrite.client import Client
from appwrite.services.project import Project

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

project = Project(client)

project.delete_key(
    key_id='<KEY_ID>'
)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

project = Project.new(client)

project.delete_key(
    key_id: '<KEY_ID>'
)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Project project = new Project(client);

await project.DeleteKey(
    keyId: "<KEY_ID>"
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Project project = Project(client);

await project.deleteKey(
    keyId: '<KEY_ID>',
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Project

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val project = Project(client)

project.deleteKey(
    keyId = "<KEY_ID>"
)
```
```server-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Project;

Client client = new Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>");

Project project = new Project(client);

project.deleteKey(
    "<KEY_ID>",                                      // keyId
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }
        System.out.println(result);
    })
);
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let project = Project(client)

try await project.deleteKey(
    keyId: "<KEY_ID>"
)
```
```server-go
package main

import (
    "fmt"
    "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("<YOUR_API_KEY>"),
    )

    project := appwrite.NewProject(client)
    _, err := project.DeleteKey(
        "<KEY_ID>",
    )

    if err != nil {
        panic(err)
    }

    fmt.Println("API key deleted")
}
```
```server-rust
use appwrite::Client;
use appwrite::services::project::Project;

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

    let project = Project::new(&client);

    project.delete_key(
        "<KEY_ID>",
    ).await?;

    Ok(())
}
```

### Scopes

When adding a new API key, you choose which scopes to grant. Scopes are grouped by service, matching the categories shown in the Appwrite Console.

**Auth**

| 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**

| Name | Description |
|-----------------------|---------------------------------------------------------------------------------|
| `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 columns |
| `columns.write` | Access to create, update, and delete your project's database table columns |
| `indexes.read` | Access to read your project's database table indexes |
| `indexes.write` | Access to create, update, and delete your project's database table indexes |
| `rows.read` | Access to read your project's database rows |
| `rows.write` | Access to create, update, and delete your project's database rows |
| `embeddings.write` | Access to generate vector embeddings |
| `documentsdb.read` | Access to read your project's document databases |
| `documentsdb.write` | Access to create, update, and delete your project's document databases |
| `documentsdb.collections.read` | Access to read your project's document database collections |
| `documentsdb.collections.write` | Access to create, update, and delete your project's document database collections |
| `documentsdb.documents.read` | Access to read your project's document database documents |
| `documentsdb.documents.write` | Access to create, update, and delete your project's document database documents |
| `documentsdb.indexes.read` | Access to read your project's document database collection indexes |
| `documentsdb.indexes.write` | Access to create, update, and delete your project's document database collection indexes |
| `vectorsdb.read` | Access to read your project's vector databases |
| `vectorsdb.write` | Access to create, update, and delete your project's vector databases |
| `vectorsdb.collections.read` | Access to read your project's vector database collections |
| `vectorsdb.collections.write` | Access to create, update, and delete your project's vector database collections |
| `vectorsdb.documents.read` | Access to read your project's vector database documents |
| `vectorsdb.documents.write` | Access to create, update, and delete your project's vector database documents |
| `vectorsdb.indexes.read` | Access to read your project's vector database collection indexes |
| `vectorsdb.indexes.write` | Access to create, update, and delete your project's vector database collection indexes |

**Functions**

| Name | Description |
|-----------------------|---------------------------------------------------------------------------------|
| `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|
| `execution.read` | Access to read your project's execution logs |
| `execution.write` | Access to execute your project's functions |

**Storage**

| Name | Description |
|-----------------------|---------------------------------------------------------------------------------|
| `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 |

**Messaging**

| Name | Description |
|-----------------------|---------------------------------------------------------------------------------|
| `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 |

**Sites**

| Name | Description |
|-----------------------|---------------------------------------------------------------------------------|
| `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 |

**Other**

| Name | Description |
|-----------------------|---------------------------------------------------------------------------------|
| `locale.read` | Access to your project's Locale service |
| `avatars.read` | Access to your project's Avatars service |
| `health.read` | Access to read your project's health status |
| `migrations.read` | Access to read your project's migrations |
| `migrations.write` | Access to create, update, and delete your project's migrations |
| `tokens.read` | Access to read your project's tokens |
| `tokens.write` | Access to create, update, and delete your project's tokens |
| `webhooks.read` | Access to read your project's webhooks |
| `webhooks.write` | Access to create, update, and delete your project's webhooks |
| `keys.read` | Access to read your project's API keys |
| `keys.write` | Access to update and delete your project's API keys |
| `rules.read` | Access to read your project's proxy rules |
| `rules.write` | Access to create, update, and delete your project's proxy rules |
| `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 |

---

## Auth methods

URL: https://appwrite.io/docs/partners/project/auth-methods

Each Appwrite project ships with a configurable set of authentication methods, including email and password, magic URL, email OTP, phone, anonymous sessions, JWT, and team invites. Methods can be toggled on or off from the Appwrite Console under **Auth** > **Settings**, or programmatically through any server SDK using the Project service.

When a method is disabled, the matching account endpoints reject requests for that project until it is re-enabled.

### Toggle from the Console

![Auth methods settings in the Appwrite Console](/images/docs/project/auth-methods.avif)

To toggle auth methods manually:

1. Open your project in the Appwrite Console.
2. Navigate to **Auth** in the sidebar, then open the **Settings** tab.
3. In the **Auth methods** card, toggle individual methods on or off, or use **Enable all** or **Disable all** for bulk changes.
4. Changes take effect immediately. No deploy or restart is required.

OAuth2 providers are configured separately in the **OAuth2 Providers** section on the same page.

### Method IDs

The `methodId` parameter accepts one of the following values:

| Method ID | Description |
| --- | --- |
| `email-password` | Email and password sign-up and login. |
| `magic-url` | Passwordless login using a magic link sent to the user's email. |
| `email-otp` | Time-based one-time password sent to the user's email. |
| `phone` | SMS-based phone authentication. |
| `anonymous` | Guest sessions for unauthenticated visitors. |
| `invites` | Team invitations for collaborative access. |
| `jwt` | JWT-based authentication for delegated access. |

### Enable or disable a method

Use the Project service `updateAuthMethod` endpoint with a method ID and the `enabled` flag.

**Required scope**

The API key used for these calls needs the `project.write` scope.

```server-nodejs
import { Client, Project, ProjectAuthMethodId } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.updateAuthMethod({
    methodId: ProjectAuthMethodId.Emailpassword,
    enabled: false
});
```
```server-deno
import { Client, Project, ProjectAuthMethodId } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.updateAuthMethod({
    methodId: ProjectAuthMethodId.Emailpassword,
    enabled: false
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Project;
use Appwrite\Enums\ProjectAuthMethodId;

$client = new Client();

$client
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$project = new Project($client);

$result = $project->updateAuthMethod(
    methodId: ProjectAuthMethodId::EMAILPASSWORD(),
    enabled: false
);
```
```server-python
from appwrite.client import Client
from appwrite.services.project import Project
from appwrite.enums import ProjectAuthMethodId

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

project = Project(client)

result = project.update_auth_method(
    method_id = ProjectAuthMethodId.EMAIL_PASSWORD,
    enabled = False
)
```
```server-ruby
require 'appwrite'

include Appwrite
include Appwrite::Enums

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

project = Project.new(client)

response = project.update_auth_method(
    method_id: ProjectAuthMethodId::EMAIL_PASSWORD,
    enabled: false
)
```
```server-dotnet
using Appwrite;
using Appwrite.Enums;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Project project = new Project(client);

var result = await project.UpdateAuthMethod(
    methodId: ProjectAuthMethodId.EmailPassword,
    enabled: false
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';
import 'package:dart_appwrite/enums.dart' as enums;

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Project project = Project(client);

final result = await project.updateAuthMethod(
    methodId: enums.ProjectAuthMethodId.emailPassword,
    enabled: false,
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Project
import io.appwrite.enums.ProjectAuthMethodId

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val project = Project(client)

val result = project.updateAuthMethod(
    methodId = ProjectAuthMethodId.EMAIL_PASSWORD,
    enabled = false
)
```
```server-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Project;
import io.appwrite.enums.ProjectAuthMethodId;

Client client = new Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>");

Project project = new Project(client);

project.updateAuthMethod(
    ProjectAuthMethodId.EMAIL_PASSWORD, // methodId
    false, // enabled
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }
        System.out.println(result);
    })
);
```
```server-swift
import Appwrite
import AppwriteEnums

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let project = Project(client)

let result = try await project.updateAuthMethod(
    methodId: .emailPassword,
    enabled: false
)
```
```server-go
package main

import (
    "fmt"
    "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("<YOUR_API_KEY>"),
    )

    project := appwrite.NewProject(client)
    result, err := project.UpdateAuthMethod(
        "email-password",
        false,
    )

    if err != nil {
        panic(err)
    }

    fmt.Println(result)
}
```
```server-rust
use appwrite::Client;
use appwrite::services::project::Project;
use appwrite::enums::ProjectAuthMethodId;

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

    let project = Project::new(&client);

    let result = project.update_auth_method(
        ProjectAuthMethodId::EmailPassword,
        false,
    ).await?;

    println!("{:?}", result);
    Ok(())
}
```
```bash
appwrite project update-auth-method \
    --method-id email-password \
    --enabled false
```

The endpoint returns the updated [Project](/docs/references/cloud/models/project) document with the new method state applied.

---

## White-label transactional emails

URL: https://appwrite.io/docs/partners/project/branded-emails

When a user on your customer's app resets their password, the email that lands in their inbox is part of your customer's brand, or it should be. Out of the box, those messages go through Appwrite's shared sender and carry Appwrite's default templates, which quietly breaks the white-label illusion the moment a user reads the "from" line. The Project API lets you route a project's mail through the customer's own SMTP server and rewrite every template, so verification, recovery, and invitation emails come from the customer's domain and read in their voice.

This guide:

- Points a project at the customer's SMTP server.
- Confirms that mail is reaching the inbox.
- Rebrands a template in the customer's voice.

**Required scopes**

The SMTP calls need the `project.write` scope, and the template calls need the `templates.write` scope.

### Configure custom SMTP

Set up the client, then hand the project the customer's SMTP credentials and the sender identity their recipients should see in the inbox. The password is write-only, so it is stored securely and never comes back in a response.

```server-nodejs
import { Client, Project, ProjectSMTPSecure } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.updateSMTP({
    host: 'smtp.example.com',
    port: 587,
    username: '<USERNAME>', // optional
    password: '<PASSWORD>', // optional
    senderEmail: 'email@example.com',
    senderName: '<SENDER_NAME>', // optional
    replyToEmail: 'email@example.com', // optional
    replyToName: '<REPLY_TO_NAME>', // optional
    secure: ProjectSMTPSecure.Tls, // optional
    enabled: true
});
```
```server-deno
import { Client, Project, ProjectSMTPSecure } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.updateSMTP({
    host: 'smtp.example.com',
    port: 587,
    username: '<USERNAME>', // optional
    password: '<PASSWORD>', // optional
    senderEmail: 'email@example.com',
    senderName: '<SENDER_NAME>', // optional
    replyToEmail: 'email@example.com', // optional
    replyToName: '<REPLY_TO_NAME>', // optional
    secure: ProjectSMTPSecure.Tls, // optional
    enabled: true
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Project;
use Appwrite\Enums\ProjectSMTPSecure;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$project = new Project($client);

$result = $project->updateSMTP(
    host: 'smtp.example.com',
    port: 587,
    username: '<USERNAME>', // optional
    password: '<PASSWORD>', // optional
    senderEmail: 'email@example.com',
    senderName: '<SENDER_NAME>', // optional
    replyToEmail: 'email@example.com', // optional
    replyToName: '<REPLY_TO_NAME>', // optional
    secure: ProjectSMTPSecure::TLS(), // optional
    enabled: true
);
```
```server-python
from appwrite.client import Client
from appwrite.services.project import Project
from appwrite.models import Project as ProjectModel
from appwrite.enums import ProjectSMTPSecure

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

project = Project(client)

result: ProjectModel = project.update_smtp(
    host = 'smtp.example.com',
    port = 587,
    username = '<USERNAME>', # optional
    password = '<PASSWORD>', # optional
    sender_email = 'email@example.com',
    sender_name = '<SENDER_NAME>', # optional
    reply_to_email = 'email@example.com', # optional
    reply_to_name = '<REPLY_TO_NAME>', # optional
    secure = ProjectSMTPSecure.TLS, # optional
    enabled = True
)

print(result.model_dump())
```
```server-ruby
require 'appwrite'

include Appwrite
include Appwrite::Enums

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

project = Project.new(client)

result = project.update_smtp(
    host: 'smtp.example.com',
    port: 587,
    username: '<USERNAME>', # optional
    password: '<PASSWORD>', # optional
    sender_email: 'email@example.com',
    sender_name: '<SENDER_NAME>', # optional
    reply_to_email: 'email@example.com', # optional
    reply_to_name: '<REPLY_TO_NAME>', # optional
    secure: ProjectSMTPSecure::TLS, # optional
    enabled: true
)
```
```server-dotnet
using Appwrite;
using Appwrite.Enums;
using Appwrite.Models;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Project project = new Project(client);

Project result = await project.UpdateSMTP(
    host: "smtp.example.com",
    port: 587,
    username: "<USERNAME>", // optional
    password: "<PASSWORD>", // optional
    senderEmail: "email@example.com",
    senderName: "<SENDER_NAME>", // optional
    replyToEmail: "email@example.com", // optional
    replyToName: "<REPLY_TO_NAME>", // optional
    secure: ProjectSMTPSecure.Tls, // optional
    enabled: true
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';
import 'package:dart_appwrite/enums.dart' as enums;

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Project project = Project(client);

Project result = await project.updateSMTP(
    host: 'smtp.example.com',
    port: 587,
    username: '<USERNAME>', // (optional)
    password: '<PASSWORD>', // (optional)
    senderEmail: 'email@example.com',
    senderName: '<SENDER_NAME>', // (optional)
    replyToEmail: 'email@example.com', // (optional)
    replyToName: '<REPLY_TO_NAME>', // (optional)
    secure: enums.ProjectSMTPSecure.tls, // (optional)
    enabled: true,
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.coroutines.CoroutineCallback
import io.appwrite.enums.ProjectSMTPSecure
import io.appwrite.services.Project

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val project = Project(client)

val response = project.updateSMTP(
    host = "smtp.example.com",
    port = 587,
    username = "<USERNAME>", // optional
    password = "<PASSWORD>", // optional
    senderEmail = "email@example.com",
    senderName = "<SENDER_NAME>", // optional
    replyToEmail = "email@example.com", // optional
    replyToName = "<REPLY_TO_NAME>", // optional
    secure = ProjectSMTPSecure.TLS, // optional
    enabled = true
)
```
```server-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.enums.ProjectSMTPSecure;
import io.appwrite.services.Project;

Client client = new Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>");

Project project = new Project(client);

project.updateSMTP(
    "smtp.example.com", // host
    587, // port
    "<USERNAME>", // username (optional)
    "<PASSWORD>", // password (optional)
    "email@example.com", // senderEmail
    "<SENDER_NAME>", // senderName (optional)
    "email@example.com", // replyToEmail (optional)
    "<REPLY_TO_NAME>", // replyToName (optional)
    ProjectSMTPSecure.TLS, // secure (optional)
    true, // enabled
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        System.out.println(result);
    })
);
```
```server-swift
import Appwrite
import AppwriteEnums

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let project = Project(client)

let result = try await project.updateSMTP(
    host: "smtp.example.com",
    port: 587,
    username: "<USERNAME>", // optional
    password: "<PASSWORD>", // optional
    senderEmail: "email@example.com",
    senderName: "<SENDER_NAME>", // optional
    replyToEmail: "email@example.com", // optional
    replyToName: "<REPLY_TO_NAME>", // optional
    secure: .tls, // optional
    enabled: true
)
```
```server-go
package main

import (
    "fmt"
    "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("<YOUR_API_KEY>"),
    )

    project := appwrite.NewProject(client)
    result, err := project.UpdateSMTP(
        appwrite.WithUpdateSMTPHost("smtp.example.com"),
        appwrite.WithUpdateSMTPPort(587),
        appwrite.WithUpdateSMTPUsername("<USERNAME>"),
        appwrite.WithUpdateSMTPPassword("<PASSWORD>"),
        appwrite.WithUpdateSMTPSenderEmail("email@example.com"),
        appwrite.WithUpdateSMTPSenderName("<SENDER_NAME>"),
        appwrite.WithUpdateSMTPReplyToEmail("email@example.com"),
        appwrite.WithUpdateSMTPReplyToName("<REPLY_TO_NAME>"),
        appwrite.WithUpdateSMTPSecure("tls"),
        appwrite.WithUpdateSMTPEnabled(true),
    )

    if err != nil {
        panic(err)
    }

    fmt.Println(result)
}
```
```server-rust
use appwrite::Client;
use appwrite::services::Project;
use appwrite::enums::ProjectSMTPSecure;

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

    let project = Project::new(&client);

    let result = project.update_smtp(
        Some("smtp.example.com"),
        Some(587),
        Some("<USERNAME>"), // optional
        Some("<PASSWORD>"), // optional
        Some("email@example.com"),
        Some("<SENDER_NAME>"), // optional
        Some("email@example.com"), // optional
        Some("<REPLY_TO_NAME>"), // optional
        Some(ProjectSMTPSecure::Tls), // optional
        Some(true)
    ).await?;

    let _ = result;

    Ok(())
}
```
```bash
appwrite project update-smtp \
    --host smtp.example.com \
    --port 587 \
    --username "<USERNAME>" \
    --password "<PASSWORD>" \
    --sender-email email@example.com \
    --sender-name "<SENDER_NAME>" \
    --reply-to-email email@example.com \
    --reply-to-name "<REPLY_TO_NAME>" \
    --secure tls \
    --enabled true
```
```http
PATCH /v1/project/smtp HTTP/1.1
Content-Type: application/json
X-Appwrite-Project: <PROJECT_ID>
X-Appwrite-Key: <YOUR_API_KEY>

{
  "host": "smtp.example.com",
  "port": 587,
  "username": "<USERNAME>",
  "password": "<PASSWORD>",
  "senderEmail": "email@example.com",
  "senderName": "<SENDER_NAME>",
  "replyToEmail": "email@example.com",
  "replyToName": "<REPLY_TO_NAME>",
  "secure": "tls",
  "enabled": true
}
```

**Enabling SMTP validates the connection**

The moment you enable custom SMTP, Appwrite tries to connect to the host you gave it, so the server has to be reachable with those exact credentials or the call fails. Custom SMTP is also the prerequisite for everything below: you cannot customize email templates until it is enabled.

### Send a test email

Do not wait for a real password reset to find out the configuration is wrong. Send yourself a test message, which goes out through the SMTP settings you just saved, and a message arriving in the inbox confirms the whole path end to end.

```server-nodejs
const result = await project.createSMTPTest({
    emails: ['email@example.com']
});
```
```server-deno
const result = await project.createSMTPTest({
    emails: ['email@example.com']
});
```
```server-php
$result = $project->createSMTPTest(
    emails: ['email@example.com']
);
```
```server-python
result = project.create_smtp_test(
    emails = ['email@example.com']
)
```
```server-ruby
result = project.create_smtp_test(
    emails: ['email@example.com']
)
```
```server-dotnet
await project.CreateSMTPTest(
    emails: new List<string> { "email@example.com" }
);
```
```server-dart
await project.createSMTPTest(
    emails: ['email@example.com'],
);
```
```server-kotlin
val response = project.createSMTPTest(
    emails = listOf("email@example.com")
)
```
```server-java
project.createSMTPTest(
    List.of("email@example.com"), // emails
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        System.out.println(result);
    })
);
```
```server-swift
let result = try await project.createSMTPTest(
    emails: ["email@example.com"]
)
```
```server-go
response, err := project.CreateSMTPTest(
    []interface{}{"email@example.com"},
)

if err != nil {
    panic(err)
}

fmt.Println(response)
```
```server-rust
project.create_smtp_test(
    vec!["email@example.com"]
).await?;
```
```bash
appwrite project create-smtp-test \
    --emails email@example.com
```
```http
POST /v1/project/smtp/tests HTTP/1.1
Content-Type: application/json
X-Appwrite-Project: <PROJECT_ID>
X-Appwrite-Key: <YOUR_API_KEY>

{
  "emails": ["email@example.com"]
}
```

### Customize a template

With mail flowing through the customer's server, rebrand the messages themselves. Set the subject, body, and sender for a template in a given locale, and repeat per locale to localize. This example rewrites the verification email.

```server-nodejs
const result = await project.updateEmailTemplate({
    templateId: ProjectEmailTemplateId.Verification,
    locale: ProjectEmailTemplateLocale.En, // optional
    subject: '<SUBJECT>', // optional
    message: '<MESSAGE>', // optional
    senderName: '<SENDER_NAME>', // optional
    senderEmail: 'email@example.com', // optional
    replyToEmail: 'email@example.com', // optional
    replyToName: '<REPLY_TO_NAME>' // optional
});
```
```server-deno
const result = await project.updateEmailTemplate({
    templateId: ProjectEmailTemplateId.Verification,
    locale: ProjectEmailTemplateLocale.En, // optional
    subject: '<SUBJECT>', // optional
    message: '<MESSAGE>', // optional
    senderName: '<SENDER_NAME>', // optional
    senderEmail: 'email@example.com', // optional
    replyToEmail: 'email@example.com', // optional
    replyToName: '<REPLY_TO_NAME>' // optional
});
```
```server-php
$result = $project->updateEmailTemplate(
    templateId: ProjectEmailTemplateId::VERIFICATION(),
    locale: ProjectEmailTemplateLocale::EN(), // optional
    subject: '<SUBJECT>', // optional
    message: '<MESSAGE>', // optional
    senderName: '<SENDER_NAME>', // optional
    senderEmail: 'email@example.com', // optional
    replyToEmail: 'email@example.com', // optional
    replyToName: '<REPLY_TO_NAME>' // optional
);
```
```server-python
result: EmailTemplate = project.update_email_template(
    template_id = ProjectEmailTemplateId.VERIFICATION,
    locale = ProjectEmailTemplateLocale.EN, # optional
    subject = '<SUBJECT>', # optional
    message = '<MESSAGE>', # optional
    sender_name = '<SENDER_NAME>', # optional
    sender_email = 'email@example.com', # optional
    reply_to_email = 'email@example.com', # optional
    reply_to_name = '<REPLY_TO_NAME>' # optional
)

print(result.model_dump())
```
```server-ruby
result = project.update_email_template(
    template_id: ProjectEmailTemplateId::VERIFICATION,
    locale: ProjectEmailTemplateLocale::EN, # optional
    subject: '<SUBJECT>', # optional
    message: '<MESSAGE>', # optional
    sender_name: '<SENDER_NAME>', # optional
    sender_email: 'email@example.com', # optional
    reply_to_email: 'email@example.com', # optional
    reply_to_name: '<REPLY_TO_NAME>' # optional
)
```
```server-dotnet
EmailTemplate result = await project.UpdateEmailTemplate(
    templateId: ProjectEmailTemplateId.Verification,
    locale: ProjectEmailTemplateLocale.En, // optional
    subject: "<SUBJECT>", // optional
    message: "<MESSAGE>", // optional
    senderName: "<SENDER_NAME>", // optional
    senderEmail: "email@example.com", // optional
    replyToEmail: "email@example.com", // optional
    replyToName: "<REPLY_TO_NAME>" // optional
);
```
```server-dart
EmailTemplate result = await project.updateEmailTemplate(
    templateId: enums.ProjectEmailTemplateId.verification,
    locale: enums.ProjectEmailTemplateLocale.en, // (optional)
    subject: '<SUBJECT>', // (optional)
    message: '<MESSAGE>', // (optional)
    senderName: '<SENDER_NAME>', // (optional)
    senderEmail: 'email@example.com', // (optional)
    replyToEmail: 'email@example.com', // (optional)
    replyToName: '<REPLY_TO_NAME>', // (optional)
);
```
```server-kotlin
val response = project.updateEmailTemplate(
    templateId = ProjectEmailTemplateId.VERIFICATION,
    locale = ProjectEmailTemplateLocale.EN, // optional
    subject = "<SUBJECT>", // optional
    message = "<MESSAGE>", // optional
    senderName = "<SENDER_NAME>", // optional
    senderEmail = "email@example.com", // optional
    replyToEmail = "email@example.com", // optional
    replyToName = "<REPLY_TO_NAME>" // optional
)
```
```server-java
project.updateEmailTemplate(
    ProjectEmailTemplateId.VERIFICATION, // templateId
    ProjectEmailTemplateLocale.EN, // locale (optional)
    "<SUBJECT>", // subject (optional)
    "<MESSAGE>", // message (optional)
    "<SENDER_NAME>", // senderName (optional)
    "email@example.com", // senderEmail (optional)
    "email@example.com", // replyToEmail (optional)
    "<REPLY_TO_NAME>", // replyToName (optional)
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        System.out.println(result);
    })
);
```
```server-swift
let emailTemplate = try await project.updateEmailTemplate(
    templateId: .verification,
    locale: .en, // optional
    subject: "<SUBJECT>", // optional
    message: "<MESSAGE>", // optional
    senderName: "<SENDER_NAME>", // optional
    senderEmail: "email@example.com", // optional
    replyToEmail: "email@example.com", // optional
    replyToName: "<REPLY_TO_NAME>" // optional
)
```
```server-go
result, err := project.UpdateEmailTemplate(
    "verification",
    appwrite.WithUpdateEmailTemplateLocale("en"),
    appwrite.WithUpdateEmailTemplateSubject("<SUBJECT>"),
    appwrite.WithUpdateEmailTemplateMessage("<MESSAGE>"),
    appwrite.WithUpdateEmailTemplateSenderName("<SENDER_NAME>"),
    appwrite.WithUpdateEmailTemplateSenderEmail("email@example.com"),
    appwrite.WithUpdateEmailTemplateReplyToEmail("email@example.com"),
    appwrite.WithUpdateEmailTemplateReplyToName("<REPLY_TO_NAME>"),
)

if err != nil {
    panic(err)
}

fmt.Println(result)
```
```server-rust
let result = project.update_email_template(
    ProjectEmailTemplateId::Verification,
    Some(ProjectEmailTemplateLocale::En), // optional
    Some("<SUBJECT>"), // optional
    Some("<MESSAGE>"), // optional
    Some("<SENDER_NAME>"), // optional
    Some("email@example.com"), // optional
    Some("email@example.com"), // optional
    Some("<REPLY_TO_NAME>") // optional
).await?;

let _ = result;
```
```bash
appwrite project update-email-template \
    --template-id verification \
    --locale en \
    --subject "<SUBJECT>" \
    --message "<MESSAGE>" \
    --sender-name "<SENDER_NAME>" \
    --sender-email email@example.com \
    --reply-to-email email@example.com \
    --reply-to-name "<REPLY_TO_NAME>"
```
```http
PATCH /v1/project/templates/email HTTP/1.1
Content-Type: application/json
X-Appwrite-Project: <PROJECT_ID>
X-Appwrite-Key: <YOUR_API_KEY>

{
  "templateId": "verification",
  "locale": "en",
  "subject": "<SUBJECT>",
  "message": "<MESSAGE>",
  "senderName": "<SENDER_NAME>",
  "senderEmail": "email@example.com",
  "replyToEmail": "email@example.com",
  "replyToName": "<REPLY_TO_NAME>"
}
```

The template ID is one of `verification`, `magicSession`, `recovery`, `invitation`, `mfaChallenge`, `sessionAlert`, or `otpSession`.

### Read templates back

Read a template back to check the copy that is live, which is worth doing whenever you let a customer edit their own templates through your dashboard.

```server-nodejs
const result = await project.getEmailTemplate({
    templateId: ProjectEmailTemplateId.Verification,
    locale: ProjectEmailTemplateLocale.En // optional
});
```
```server-deno
const result = await project.getEmailTemplate({
    templateId: ProjectEmailTemplateId.Verification,
    locale: ProjectEmailTemplateLocale.En // optional
});
```
```server-php
$result = $project->getEmailTemplate(
    templateId: ProjectEmailTemplateId::VERIFICATION(),
    locale: ProjectEmailTemplateLocale::EN() // optional
);
```
```server-python
result: EmailTemplate = project.get_email_template(
    template_id = ProjectEmailTemplateId.VERIFICATION,
    locale = ProjectEmailTemplateLocale.EN # optional
)

print(result.model_dump())
```
```server-ruby
result = project.get_email_template(
    template_id: ProjectEmailTemplateId::VERIFICATION,
    locale: ProjectEmailTemplateLocale::EN # optional
)
```
```server-dotnet
EmailTemplate result = await project.GetEmailTemplate(
    templateId: ProjectEmailTemplateId.Verification,
    locale: ProjectEmailTemplateLocale.En // optional
);
```
```server-dart
EmailTemplate result = await project.getEmailTemplate(
    templateId: enums.ProjectEmailTemplateId.verification,
    locale: enums.ProjectEmailTemplateLocale.en, // (optional)
);
```
```server-kotlin
val response = project.getEmailTemplate(
    templateId = ProjectEmailTemplateId.VERIFICATION,
    locale = ProjectEmailTemplateLocale.EN // optional
)
```
```server-java
project.getEmailTemplate(
    ProjectEmailTemplateId.VERIFICATION, // templateId
    ProjectEmailTemplateLocale.EN, // locale (optional)
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        System.out.println(result);
    })
);
```
```server-swift
let emailTemplate = try await project.getEmailTemplate(
    templateId: .verification,
    locale: .en // optional
)
```
```server-go
result, err := project.GetEmailTemplate(
    "verification",
    appwrite.WithGetEmailTemplateLocale("en"),
)

if err != nil {
    panic(err)
}

fmt.Println(result)
```
```server-rust
let result = project.get_email_template(
    ProjectEmailTemplateId::Verification,
    Some(ProjectEmailTemplateLocale::En) // optional
).await?;

let _ = result;
```
```bash
appwrite project get-email-template \
    --template-id verification \
    --locale en
```
```http
GET /v1/project/templates/email/verification?locale=en HTTP/1.1
X-Appwrite-Project: <PROJECT_ID>
X-Appwrite-Key: <YOUR_API_KEY>
```

Or list the project's templates to see everything you have overridden in one call.

```server-nodejs
const result = await project.listEmailTemplates({
    queries: [], // optional
    total: false // optional
});
```
```server-deno
const result = await project.listEmailTemplates({
    queries: [], // optional
    total: false // optional
});
```
```server-php
$result = $project->listEmailTemplates(
    queries: [], // optional
    total: false // optional
);
```
```server-python
result: EmailTemplateList = project.list_email_templates(
    queries = [], # optional
    total = False # optional
)

print(result.model_dump())
```
```server-ruby
result = project.list_email_templates(
    queries: [], # optional
    total: false # optional
)
```
```server-dotnet
EmailTemplateList result = await project.ListEmailTemplates(
    queries: new List<string>(), // optional
    total: false // optional
);
```
```server-dart
EmailTemplateList result = await project.listEmailTemplates(
    queries: [], // (optional)
    total: false, // (optional)
);
```
```server-kotlin
val response = project.listEmailTemplates(
    queries = listOf(), // optional
    total = false // optional
)
```
```server-java
project.listEmailTemplates(
    List.of(), // queries (optional)
    false, // total (optional)
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        System.out.println(result);
    })
);
```
```server-swift
let emailTemplateList = try await project.listEmailTemplates(
    queries: [], // optional
    total: false // optional
)
```
```server-go
result, err := project.ListEmailTemplates(
    appwrite.WithListEmailTemplatesQueries([]interface{}{}),
    appwrite.WithListEmailTemplatesTotal(false),
)

if err != nil {
    panic(err)
}

fmt.Println(result)
```
```server-rust
let result = project.list_email_templates(
    Some(vec![]), // optional
    Some(false) // optional
).await?;

let _ = result;
```
```bash
appwrite project list-email-templates
```
```http
GET /v1/project/templates/email HTTP/1.1
X-Appwrite-Project: <PROJECT_ID>
X-Appwrite-Key: <YOUR_API_KEY>
```

### Next steps

With SMTP and templates driven from code, every customer's project sends mail that carries their brand rather than yours. From here:

- [Provision a project's baseline](/docs/partners/project/provisioning) so branding the emails is one step in onboarding.
- Read the [SMTP](/docs/partners/project/smtp) and [Email templates](/docs/partners/project/email-templates) references for every field you can set.

---

## Create a project

URL: https://appwrite.io/docs/partners/project/create

Projects belong to an organization, so the Organization service creates them. A platform that gives every customer their own project calls this once per customer, then configures the new project with the pages in the rest of this section.

**Partners key required**

Creating a project needs a [Partners key](/docs/partners/org-api-keys) with the `projects.write` scope, not a project API key. A project API key is scoped to one project and cannot create another. Pass the organization ID with the key and send no project ID.

### Create a project

Pass a project ID, a display name, and the region the project's data lives in. Store the returned ID against your own customer record.

```server-nodejs
import { Client, Organization, ID, Region } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://cloud.appwrite.io/v1')
    .setOrganization('<ORGANIZATION_ID>')
    .setKey('<YOUR_PARTNERS_KEY>');

const organization = new Organization(client);

const result = await organization.createProject({
    projectId: ID.unique(),
    name: 'Customer workspace',
    region: Region.Fra
});
```
```server-deno
import { Client, Organization, ID, Region } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://cloud.appwrite.io/v1')
    .setOrganization('<ORGANIZATION_ID>')
    .setKey('<YOUR_PARTNERS_KEY>');

const organization = new Organization(client);

const result = await organization.createProject({
    projectId: ID.unique(),
    name: 'Customer workspace',
    region: Region.Fra
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\ID;
use Appwrite\Services\Organization;
use Appwrite\Enums\Region;

$client = (new Client())
    ->setEndpoint('https://cloud.appwrite.io/v1')
    ->setOrganization('<ORGANIZATION_ID>')
    ->setKey('<YOUR_PARTNERS_KEY>');

$organization = new Organization($client);

$result = $organization->createProject(
    projectId: ID::unique(),
    name: 'Customer workspace',
    region: Region::FRA()
);
```
```server-python
from appwrite.client import Client
from appwrite.id import ID
from appwrite.services.organization import Organization
from appwrite.enums import Region

client = Client()
client.set_endpoint('https://cloud.appwrite.io/v1')
client.set_organization('<ORGANIZATION_ID>')
client.set_key('<YOUR_PARTNERS_KEY>')

organization = Organization(client)

result = organization.create_project(
    project_id = ID.unique(),
    name = 'Customer workspace',
    region = Region.FRA
)
```
```server-ruby
require 'appwrite'

include Appwrite
include Appwrite::Enums

client = Client.new
    .set_endpoint('https://cloud.appwrite.io/v1')
    .set_organization('<ORGANIZATION_ID>')
    .set_key('<YOUR_PARTNERS_KEY>')

organization = Organization.new(client)

response = organization.create_project(
    project_id: ID.unique(),
    name: 'Customer workspace',
    region: Region::FRA
)
```
```server-dotnet
using Appwrite;
using Appwrite.Enums;
using Appwrite.Models;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://cloud.appwrite.io/v1")
    .SetOrganization("<ORGANIZATION_ID>")
    .SetKey("<YOUR_PARTNERS_KEY>");

Organization organization = new Organization(client);

Project result = await organization.CreateProject(
    projectId: ID.Unique(),
    name: "Customer workspace",
    region: Region.Fra
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';
import 'package:dart_appwrite/enums.dart' as enums;

Client client = Client()
    .setEndpoint('https://cloud.appwrite.io/v1')
    .setOrganization('<ORGANIZATION_ID>')
    .setKey('<YOUR_PARTNERS_KEY>');

Organization organization = Organization(client);

Project result = await organization.createProject(
    projectId: ID.unique(),
    name: 'Customer workspace',
    region: enums.Region.fra,
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.ID
import io.appwrite.services.Organization
import io.appwrite.enums.Region

val client = Client()
    .setEndpoint("https://cloud.appwrite.io/v1")
    .setOrganization("<ORGANIZATION_ID>")
    .setKey("<YOUR_PARTNERS_KEY>")

val organization = Organization(client)

val response = organization.createProject(
    projectId = ID.unique(),
    name = "Customer workspace",
    region = Region.FRA
)
```
```server-java
import io.appwrite.Client;
import io.appwrite.ID;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Organization;
import io.appwrite.enums.Region;

Client client = new Client()
    .setEndpoint("https://cloud.appwrite.io/v1")
    .setOrganization("<ORGANIZATION_ID>")
    .setKey("<YOUR_PARTNERS_KEY>");

Organization organization = new Organization(client);

organization.createProject(
    ID.unique(), // projectId
    "Customer workspace", // name
    Region.FRA, // region
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        System.out.println(result);
    })
);
```
```server-swift
import Appwrite
import AppwriteEnums

let client = Client()
    .setEndpoint("https://cloud.appwrite.io/v1")
    .setOrganization("<ORGANIZATION_ID>")
    .setKey("<YOUR_PARTNERS_KEY>")

let organization = Organization(client)

let project = try await organization.createProject(
    projectId: ID.unique(),
    name: "Customer workspace",
    region: .fra
)
```
```server-go
package main

import (
    "fmt"
    "github.com/appwrite/sdk-for-go/appwrite"
    "github.com/appwrite/sdk-for-go/id"
    "github.com/appwrite/sdk-for-go/organization"
)

func main() {
    client := appwrite.NewClient(
        appwrite.WithEndpoint("https://cloud.appwrite.io/v1"),
        appwrite.WithOrganization("<ORGANIZATION_ID>"),
        appwrite.WithKey("<YOUR_PARTNERS_KEY>"),
    )

    service := appwrite.NewOrganization(client)
    result, err := service.CreateProject(
        id.Unique(),
        "Customer workspace",
        organization.WithCreateProjectRegion("fra"),
    )

    if err != nil {
        panic(err)
    }

    fmt.Println(result)
}
```
```server-rust
use appwrite::client::Client;
use appwrite::enums::Region;
use appwrite::id::ID;
use appwrite::services::Organization;

let client = Client::new()
    .set_endpoint("https://cloud.appwrite.io/v1")
    .set_organization("<ORGANIZATION_ID>")
    .set_key("<YOUR_PARTNERS_KEY>");

let organization = Organization::new(&client);

let result = organization
    .create_project(ID::unique(), "Customer workspace", Some(Region::Fra))
    .await?;
```

### Regions

The `region` fixes where the project's data lives and which regional endpoint its SDK calls use. It cannot be changed after creation, so pick it from the customer's own requirements. See [Regions](/docs/products/network/regions) for the full list.

### Retries

`ID.unique()` returns a new ID on every call, so a retried create makes a second project. To keep provisioning safe to retry, derive the project ID from your own customer ID and pass that instead, or record the returned ID before the provisioning step returns.

### Next steps

Once the project exists, switch to a project API key to configure it. Create that key in the Console, then work through the configuration pages:

- [API keys](/docs/partners/project/api-keys): Create and manage the keys your backend uses inside the project.
- [Provisioning](/docs/partners/project/provisioning): Apply a standard baseline to a new customer's project from a script.

---

## Email templates

URL: https://appwrite.io/docs/partners/project/email-templates

Appwrite sends transactional emails on your behalf for account management flows such as email verification, password recovery, and magic URL sign-in. Email templates let you customize the subject, message body, sender identity, and reply-to address of each of these emails, with a separate version for every locale you support.

**Custom SMTP required to save changes**

You can view the built-in default templates at any time, but saving a customization requires a custom SMTP server enabled on your project. See [Custom SMTP server](/docs/products/auth/message-templates#smtp) to set one up.

### Manage in the Console

![Email templates in the Appwrite Console](/images/docs/project/email-templates.avif)

To edit email templates from the Appwrite Console:

1. Navigate to your project.
2. Open the **Auth** section and select the **Templates** tab.
3. Under **Email templates**, expand the template you want to edit, such as **Verification** or **Reset password**.
4. Choose a **Template language**, then edit the sender name, sender email, reply-to address, subject, and message. Use the variable chips (`{{user}}`, `{{project}}`, `{{redirect}}`) to insert dynamic values into the subject and message.
5. Click **Update** to save the template for the selected language.

### Manage with a Server SDK

You can also manage email templates programmatically using a [Server SDK](/docs/sdks#server).

**Required scopes**

The API key used for these calls needs `templates.read` to list or fetch templates, and `templates.write` to update them.

#### List email templates

You can paginate the result with the `limit` and `offset` queries. See [Queries](/docs/products/databases/queries) for the query syntax.

```server-nodejs
import { Client, Project } from '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 API key

const project = new Project(client);

const result = await project.listEmailTemplates({
    queries: [], // optional
    total: false // optional
});
```
```server-deno
import { Client, Project } 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 API key

const project = new Project(client);

const result = await project.listEmailTemplates({
    queries: [], // optional
    total: false // optional
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Project;

$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 API key

$project = new Project($client);

$result = $project->listEmailTemplates(
    queries: [], // optional
    total: false // optional
);
```
```server-python
from appwrite.client import Client
from appwrite.services.project import Project
from appwrite.models import EmailTemplateList

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 API key

project = Project(client)

result: EmailTemplateList = project.list_email_templates(
    queries = [], # optional
    total = False # optional
)

print(result.model_dump())
```
```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 API key

project = Project.new(client)

result = project.list_email_templates(
    queries: [], # optional
    total: false # 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 API key

Project project = new Project(client);

EmailTemplateList result = await project.ListEmailTemplates(
    queries: new List<string>(), // optional
    total: false // 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 API key

Project project = Project(client);

EmailTemplateList result = await project.listEmailTemplates(
    queries: [], // (optional)
    total: false, // (optional)
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.coroutines.CoroutineCallback
import io.appwrite.services.Project

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 API key

val project = Project(client)

val response = project.listEmailTemplates(
    queries = listOf(), // optional
    total = false // optional
)
```
```server-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Project;

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 API key

Project project = new Project(client);

project.listEmailTemplates(
    List.of(), // queries (optional)
    false, // total (optional)
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        System.out.println(result);
    })
);
```
```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 API key

let project = Project(client)

let emailTemplateList = try await project.listEmailTemplates(
    queries: [], // optional
    total: false // optional
)
```
```server-go
package main

import (
    "fmt"
    "github.com/appwrite/sdk-for-go/appwrite"
)

func main() {
    client := appwrite.NewClient(
        appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"),
        appwrite.WithProject("<YOUR_PROJECT_ID>"),
        appwrite.WithKey("<YOUR_API_KEY>"),
    )

    project := appwrite.NewProject(client)
    result, err := project.ListEmailTemplates(
        appwrite.WithListEmailTemplatesQueries([]interface{}{}),
        appwrite.WithListEmailTemplatesTotal(false),
    )

    if err != nil {
        panic(err)
    }

    fmt.Println(result)
}
```
```server-rust
use appwrite::Client;
use appwrite::services::Project;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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 API key

    let project = Project::new(&client);

    let result = project.list_email_templates(
        Some(vec![]), // optional
        Some(false) // optional
    ).await?;

    let _ = result;

    Ok(())
}
```
```bash
appwrite project list-email-templates
```

#### Get an email template

Retrieve a single template by its type and, optionally, locale. If you omit the locale, the fallback locale `en` is returned.

```server-nodejs
import { Client, Project, ProjectEmailTemplateId, ProjectEmailTemplateLocale } from '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 API key

const project = new Project(client);

const result = await project.getEmailTemplate({
    templateId: ProjectEmailTemplateId.Verification,
    locale: ProjectEmailTemplateLocale.En // optional
});
```
```server-deno
import { Client, Project, ProjectEmailTemplateId, ProjectEmailTemplateLocale } 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 API key

const project = new Project(client);

const result = await project.getEmailTemplate({
    templateId: ProjectEmailTemplateId.Verification,
    locale: ProjectEmailTemplateLocale.En // optional
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Project;
use Appwrite\Enums\ProjectEmailTemplateId;
use Appwrite\Enums\ProjectEmailTemplateLocale;

$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 API key

$project = new Project($client);

$result = $project->getEmailTemplate(
    templateId: ProjectEmailTemplateId::VERIFICATION(),
    locale: ProjectEmailTemplateLocale::EN() // optional
);
```
```server-python
from appwrite.client import Client
from appwrite.services.project import Project
from appwrite.models import EmailTemplate
from appwrite.enums import ProjectEmailTemplateId
from appwrite.enums import ProjectEmailTemplateLocale

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 API key

project = Project(client)

result: EmailTemplate = project.get_email_template(
    template_id = ProjectEmailTemplateId.VERIFICATION,
    locale = ProjectEmailTemplateLocale.EN # optional
)

print(result.model_dump())
```
```server-ruby
require 'appwrite'

include Appwrite
include Appwrite::Enums

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 API key

project = Project.new(client)

result = project.get_email_template(
    template_id: ProjectEmailTemplateId::VERIFICATION,
    locale: ProjectEmailTemplateLocale::EN # optional
)
```
```server-dotnet
using Appwrite;
using Appwrite.Enums;
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 API key

Project project = new Project(client);

EmailTemplate result = await project.GetEmailTemplate(
    templateId: ProjectEmailTemplateId.Verification,
    locale: ProjectEmailTemplateLocale.En // optional
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';
import 'package:dart_appwrite/enums.dart' as enums;

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 API key

Project project = Project(client);

EmailTemplate result = await project.getEmailTemplate(
    templateId: enums.ProjectEmailTemplateId.verification,
    locale: enums.ProjectEmailTemplateLocale.en, // (optional)
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.coroutines.CoroutineCallback
import io.appwrite.enums.ProjectEmailTemplateId
import io.appwrite.enums.ProjectEmailTemplateLocale
import io.appwrite.services.Project

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 API key

val project = Project(client)

val response = project.getEmailTemplate(
    templateId = ProjectEmailTemplateId.VERIFICATION,
    locale = ProjectEmailTemplateLocale.EN // optional
)
```
```server-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.enums.ProjectEmailTemplateId;
import io.appwrite.enums.ProjectEmailTemplateLocale;
import io.appwrite.services.Project;

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 API key

Project project = new Project(client);

project.getEmailTemplate(
    ProjectEmailTemplateId.VERIFICATION, // templateId
    ProjectEmailTemplateLocale.EN, // locale (optional)
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        System.out.println(result);
    })
);
```
```server-swift
import Appwrite
import AppwriteEnums

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 API key

let project = Project(client)

let emailTemplate = try await project.getEmailTemplate(
    templateId: .verification,
    locale: .en // optional
)
```
```server-go
package main

import (
    "fmt"
    "github.com/appwrite/sdk-for-go/appwrite"
)

func main() {
    client := appwrite.NewClient(
        appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"),
        appwrite.WithProject("<YOUR_PROJECT_ID>"),
        appwrite.WithKey("<YOUR_API_KEY>"),
    )

    project := appwrite.NewProject(client)
    result, err := project.GetEmailTemplate(
        "verification",
        appwrite.WithGetEmailTemplateLocale("en"),
    )

    if err != nil {
        panic(err)
    }

    fmt.Println(result)
}
```
```server-rust
use appwrite::Client;
use appwrite::services::Project;
use appwrite::enums::ProjectEmailTemplateId;
use appwrite::enums::ProjectEmailTemplateLocale;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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 API key

    let project = Project::new(&client);

    let result = project.get_email_template(
        ProjectEmailTemplateId::Verification,
        Some(ProjectEmailTemplateLocale::En) // optional
    ).await?;

    let _ = result;

    Ok(())
}
```
```bash
appwrite project get-email-template \
    --template-id verification \
    --locale en
```

#### Update an email template

Customize a template's subject, message, and sender identity for a given type and locale. Every field is optional, so you can change only the values you need. The `subject` and `message` accept the `{{user}}`, `{{project}}`, and `{{redirect}}` variables, which Appwrite replaces when sending the email.

```server-nodejs
import { Client, Project, ProjectEmailTemplateId, ProjectEmailTemplateLocale } from '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 API key

const project = new Project(client);

const result = await project.updateEmailTemplate({
    templateId: ProjectEmailTemplateId.Verification,
    locale: ProjectEmailTemplateLocale.En, // optional
    subject: '<SUBJECT>', // optional
    message: '<MESSAGE>', // optional
    senderName: '<SENDER_NAME>', // optional
    senderEmail: 'email@example.com', // optional
    replyToEmail: 'email@example.com', // optional
    replyToName: '<REPLY_TO_NAME>' // optional
});
```
```server-deno
import { Client, Project, ProjectEmailTemplateId, ProjectEmailTemplateLocale } 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 API key

const project = new Project(client);

const result = await project.updateEmailTemplate({
    templateId: ProjectEmailTemplateId.Verification,
    locale: ProjectEmailTemplateLocale.En, // optional
    subject: '<SUBJECT>', // optional
    message: '<MESSAGE>', // optional
    senderName: '<SENDER_NAME>', // optional
    senderEmail: 'email@example.com', // optional
    replyToEmail: 'email@example.com', // optional
    replyToName: '<REPLY_TO_NAME>' // optional
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Project;
use Appwrite\Enums\ProjectEmailTemplateId;
use Appwrite\Enums\ProjectEmailTemplateLocale;

$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 API key

$project = new Project($client);

$result = $project->updateEmailTemplate(
    templateId: ProjectEmailTemplateId::VERIFICATION(),
    locale: ProjectEmailTemplateLocale::EN(), // optional
    subject: '<SUBJECT>', // optional
    message: '<MESSAGE>', // optional
    senderName: '<SENDER_NAME>', // optional
    senderEmail: 'email@example.com', // optional
    replyToEmail: 'email@example.com', // optional
    replyToName: '<REPLY_TO_NAME>' // optional
);
```
```server-python
from appwrite.client import Client
from appwrite.services.project import Project
from appwrite.models import EmailTemplate
from appwrite.enums import ProjectEmailTemplateId
from appwrite.enums import ProjectEmailTemplateLocale

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 API key

project = Project(client)

result: EmailTemplate = project.update_email_template(
    template_id = ProjectEmailTemplateId.VERIFICATION,
    locale = ProjectEmailTemplateLocale.EN, # optional
    subject = '<SUBJECT>', # optional
    message = '<MESSAGE>', # optional
    sender_name = '<SENDER_NAME>', # optional
    sender_email = 'email@example.com', # optional
    reply_to_email = 'email@example.com', # optional
    reply_to_name = '<REPLY_TO_NAME>' # optional
)

print(result.model_dump())
```
```server-ruby
require 'appwrite'

include Appwrite
include Appwrite::Enums

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 API key

project = Project.new(client)

result = project.update_email_template(
    template_id: ProjectEmailTemplateId::VERIFICATION,
    locale: ProjectEmailTemplateLocale::EN, # optional
    subject: '<SUBJECT>', # optional
    message: '<MESSAGE>', # optional
    sender_name: '<SENDER_NAME>', # optional
    sender_email: 'email@example.com', # optional
    reply_to_email: 'email@example.com', # optional
    reply_to_name: '<REPLY_TO_NAME>' # optional
)
```
```server-dotnet
using Appwrite;
using Appwrite.Enums;
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 API key

Project project = new Project(client);

EmailTemplate result = await project.UpdateEmailTemplate(
    templateId: ProjectEmailTemplateId.Verification,
    locale: ProjectEmailTemplateLocale.En, // optional
    subject: "<SUBJECT>", // optional
    message: "<MESSAGE>", // optional
    senderName: "<SENDER_NAME>", // optional
    senderEmail: "email@example.com", // optional
    replyToEmail: "email@example.com", // optional
    replyToName: "<REPLY_TO_NAME>" // optional
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';
import 'package:dart_appwrite/enums.dart' as enums;

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 API key

Project project = Project(client);

EmailTemplate result = await project.updateEmailTemplate(
    templateId: enums.ProjectEmailTemplateId.verification,
    locale: enums.ProjectEmailTemplateLocale.en, // (optional)
    subject: '<SUBJECT>', // (optional)
    message: '<MESSAGE>', // (optional)
    senderName: '<SENDER_NAME>', // (optional)
    senderEmail: 'email@example.com', // (optional)
    replyToEmail: 'email@example.com', // (optional)
    replyToName: '<REPLY_TO_NAME>', // (optional)
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.coroutines.CoroutineCallback
import io.appwrite.enums.ProjectEmailTemplateId
import io.appwrite.enums.ProjectEmailTemplateLocale
import io.appwrite.services.Project

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 API key

val project = Project(client)

val response = project.updateEmailTemplate(
    templateId = ProjectEmailTemplateId.VERIFICATION,
    locale = ProjectEmailTemplateLocale.EN, // optional
    subject = "<SUBJECT>", // optional
    message = "<MESSAGE>", // optional
    senderName = "<SENDER_NAME>", // optional
    senderEmail = "email@example.com", // optional
    replyToEmail = "email@example.com", // optional
    replyToName = "<REPLY_TO_NAME>" // optional
)
```
```server-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.enums.ProjectEmailTemplateId;
import io.appwrite.enums.ProjectEmailTemplateLocale;
import io.appwrite.services.Project;

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 API key

Project project = new Project(client);

project.updateEmailTemplate(
    ProjectEmailTemplateId.VERIFICATION, // templateId
    ProjectEmailTemplateLocale.EN, // locale (optional)
    "<SUBJECT>", // subject (optional)
    "<MESSAGE>", // message (optional)
    "<SENDER_NAME>", // senderName (optional)
    "email@example.com", // senderEmail (optional)
    "email@example.com", // replyToEmail (optional)
    "<REPLY_TO_NAME>", // replyToName (optional)
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        System.out.println(result);
    })
);
```
```server-swift
import Appwrite
import AppwriteEnums

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 API key

let project = Project(client)

let emailTemplate = try await project.updateEmailTemplate(
    templateId: .verification,
    locale: .en, // optional
    subject: "<SUBJECT>", // optional
    message: "<MESSAGE>", // optional
    senderName: "<SENDER_NAME>", // optional
    senderEmail: "email@example.com", // optional
    replyToEmail: "email@example.com", // optional
    replyToName: "<REPLY_TO_NAME>" // optional
)
```
```server-go
package main

import (
    "fmt"
    "github.com/appwrite/sdk-for-go/appwrite"
)

func main() {
    client := appwrite.NewClient(
        appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"),
        appwrite.WithProject("<YOUR_PROJECT_ID>"),
        appwrite.WithKey("<YOUR_API_KEY>"),
    )

    project := appwrite.NewProject(client)
    result, err := project.UpdateEmailTemplate(
        "verification",
        appwrite.WithUpdateEmailTemplateLocale("en"),
        appwrite.WithUpdateEmailTemplateSubject("<SUBJECT>"),
        appwrite.WithUpdateEmailTemplateMessage("<MESSAGE>"),
        appwrite.WithUpdateEmailTemplateSenderName("<SENDER_NAME>"),
        appwrite.WithUpdateEmailTemplateSenderEmail("email@example.com"),
        appwrite.WithUpdateEmailTemplateReplyToEmail("email@example.com"),
        appwrite.WithUpdateEmailTemplateReplyToName("<REPLY_TO_NAME>"),
    )

    if err != nil {
        panic(err)
    }

    fmt.Println(result)
}
```
```server-rust
use appwrite::Client;
use appwrite::services::Project;
use appwrite::enums::ProjectEmailTemplateId;
use appwrite::enums::ProjectEmailTemplateLocale;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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 API key

    let project = Project::new(&client);

    let result = project.update_email_template(
        ProjectEmailTemplateId::Verification,
        Some(ProjectEmailTemplateLocale::En), // optional
        Some("<SUBJECT>"), // optional
        Some("<MESSAGE>"), // optional
        Some("<SENDER_NAME>"), // optional
        Some("email@example.com"), // optional
        Some("email@example.com"), // optional
        Some("<REPLY_TO_NAME>") // optional
    ).await?;

    let _ = result;

    Ok(())
}
```
```bash
appwrite project update-email-template \
    --template-id verification \
    --locale en \
    --subject "<SUBJECT>" \
    --message "<MESSAGE>" \
    --sender-name "<SENDER_NAME>" \
    --sender-email email@example.com \
    --reply-to-email email@example.com \
    --reply-to-name "<REPLY_TO_NAME>"
```

### Template types

Each email template is identified by a `templateId`. The available types map to the account management emails Appwrite sends:

| Console name | Template ID | Sent when |
|------------------|----------------|------------------------------------------------------------|
| Verification | `verification` | A user requests email address verification. |
| Magic URL | `magicSession` | A user signs in with a magic URL link. |
| OTP session | `otpSession` | A user signs in with an email one-time password. |
| Reset password | `recovery` | A user starts the password recovery flow. |
| Invite user | `invitation` | A user is invited to join a team. |
| 2FA verification | `mfaChallenge` | A user completes an email-based multi-factor challenge. |
| Session alert | `sessionAlert` | A new session is created on a user's account. |

### Locales

Templates are stored per locale, so you can send the right copy to each audience. Pass the `locale` parameter to read or write the version for a specific language. If you omit it, Appwrite uses the fallback locale `en`. Locale codes follow the ISO 639-1 standard with optional region suffixes, such as `en`, `fr`, `de`, or `pt-br`.

### Benefits

- **On-brand emails.** Replace the default sender name, address, subject, and message so account emails match your product instead of generic Appwrite copy.
- **Localized messaging.** Maintain a separate version of each template per locale and reach every user in their own language.
- **Repeatable provisioning.** Script template content as code and apply the same configuration across dev, staging, and production projects without clicking through the Console.

---

## Environment variables

URL: https://appwrite.io/docs/partners/project/environment-variables

Environment variables let you pass constants and secrets such as API keys, connection strings, and feature flags into your Appwrite Functions and Appwrite Sites at build and runtime. Storing values outside your source keeps secrets out of version control and lets you change configuration without code changes.

Appwrite supports three scopes of environment variables:

- **Project variables** are shared across every function and site in the project. Use them for values consumed by more than one resource, such as a shared third-party API key, a database URL, or a feature flag.
- **Function variables** are scoped to a single function. Use them for values only that function needs.
- **Site variables** are scoped to a single site. Use them for values only that site needs.

When the same key is defined in multiple scopes, the more specific scope wins. Function or site variables override project variables, and Appwrite-injected variables (those prefixed with `APPWRITE_`) take final precedence and cannot be overridden.

**Redeployment required**

Variable changes only take effect on the next deployment. Redeploy your functions or sites after creating, updating, or deleting variables.

### Project variables

Project variables are available to every function and site in your project. They are managed at the project level and merged into each function or site's environment automatically at build and runtime.

This page covers how to manage project variables. To manage variables on a single function or site, see the dedicated pages:

- [Function variables](/docs/products/functions/environment-variables): Manage variables for a single function.
- [Site variables](/docs/products/sites/environment-variables): Manage variables for a single site.

### Manage in the Console

You can create and manage project variables from the Appwrite Console. The Console refers to them as **Global variables**:

1. Navigate to your project.
2. Open **Settings** at the bottom of the project sidebar, then select the **Variables** tab. The **Global variables** card is on that tab.
3. Click **Create a global variable** and enter a key and value.
4. Optionally select the **Secret** checkbox to prevent any team member from reading the value after creation.
5. Click **Create**, then redeploy your functions and sites for the change to take effect.

![Project Global variables in the Appwrite Console](/images/docs/platform/env-variables.avif)

### Manage with a Server SDK

You can also manage project variables programmatically using a [Server SDK](/docs/sdks#server).

**Required scopes**

The API key used for these calls needs `project.read` to list or fetch variables, and `project.write` to create, update, or delete them.

#### Create a variable

```server-nodejs
import { Client, Project, ID } from '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 API key

const project = new Project(client);

const result = await project.createVariable({
    variableId: ID.unique(),
    key: '<KEY>',
    value: '<VALUE>',
    secret: false // optional
});
```
```server-deno
import { Client, Project, ID } 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 API key

const project = new Project(client);

const result = await project.createVariable({
    variableId: ID.unique(),
    key: '<KEY>',
    value: '<VALUE>',
    secret: false // optional
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\ID;
use Appwrite\Services\Project;

$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 API key

$project = new Project($client);

$result = $project->createVariable(
    variableId: ID::unique(),
    key: '<KEY>',
    value: '<VALUE>',
    secret: false // optional
);
```
```server-python
from appwrite.client import Client
from appwrite.id import ID
from appwrite.services.project import Project
from appwrite.models import Variable

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 API key

project = Project(client)

result: Variable = project.create_variable(
    variable_id = ID.unique(),
    key = '<KEY>',
    value = '<VALUE>',
    secret = False # optional
)

print(result.model_dump())
```
```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 API key

project = Project.new(client)

result = project.create_variable(
    variable_id: ID.unique(),
    key: '<KEY>',
    value: '<VALUE>',
    secret: false # 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 API key

Project project = new Project(client);

Variable result = await project.CreateVariable(
    variableId: ID.Unique(),
    key: "<KEY>",
    value: "<VALUE>",
    secret: false // 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 API key

Project project = Project(client);

Variable result = await project.createVariable(
    variableId: ID.unique(),
    key: '<KEY>',
    value: '<VALUE>',
    secret: false, // (optional)
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.ID
import io.appwrite.coroutines.CoroutineCallback
import io.appwrite.services.Project

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 API key

val project = Project(client)

val response = project.createVariable(
    variableId = ID.unique(),
    key = "<KEY>",
    value = "<VALUE>",
    secret = false // optional
)
```
```server-java
import io.appwrite.Client;
import io.appwrite.ID;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Project;

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 API key

Project project = new Project(client);

project.createVariable(
    ID.unique(), // variableId
    "<KEY>", // key
    "<VALUE>", // value
    false, // secret (optional)
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        System.out.println(result);
    })
);
```
```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 API key

let project = Project(client)

let variable = try await project.createVariable(
    variableId: ID.unique(),
    key: "<KEY>",
    value: "<VALUE>",
    secret: false // optional
)
```
```server-go
package main

import (
    "fmt"
    "github.com/appwrite/sdk-for-go/appwrite"
    "github.com/appwrite/sdk-for-go/id"
)

client := appwrite.NewClient(
    appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"),
    appwrite.WithProject("<YOUR_PROJECT_ID>"),
    appwrite.WithKey("<YOUR_API_KEY>"),
)

project := appwrite.NewProject(client)

response, error := project.CreateVariable(
    id.Unique(),
    "<KEY>",
    "<VALUE>",
    appwrite.WithCreateVariableSecret(false),
)
```
```server-rust
use appwrite::Client;
use appwrite::id::ID;
use appwrite::services::Project;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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 API key

    let project = Project::new(&client);

    let result = project.create_variable(
        &ID::unique(),
        "<KEY>",
        "<VALUE>",
        Some(false) // optional
    ).await?;

    let _ = result;

    Ok(())
}
```

#### List variables

You can paginate, filter, and sort the result. See [Queries](/docs/products/databases/queries) for the query syntax. The list endpoint accepts queries on the `key`, `value`, and `secret` attributes.

```server-nodejs
import { Client, Project } from '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 API key

const project = new Project(client);

const result = await project.listVariables({
    queries: [], // optional
    total: false // optional
});
```
```server-deno
import { Client, Project } 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 API key

const project = new Project(client);

const result = await project.listVariables({
    queries: [], // optional
    total: false // optional
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Project;

$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 API key

$project = new Project($client);

$result = $project->listVariables(
    queries: [], // optional
    total: false // optional
);
```
```server-python
from appwrite.client import Client
from appwrite.services.project import Project
from appwrite.models import VariableList

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 API key

project = Project(client)

result: VariableList = project.list_variables(
    queries = [], # optional
    total = False # optional
)

print(result.model_dump())
```
```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 API key

project = Project.new(client)

result = project.list_variables(
    queries: [], # optional
    total: false # 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 API key

Project project = new Project(client);

VariableList result = await project.ListVariables(
    queries: new List<string>(), // optional
    total: false // 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 API key

Project project = Project(client);

VariableList result = await project.listVariables(
    queries: [], // (optional)
    total: false, // (optional)
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.coroutines.CoroutineCallback
import io.appwrite.services.Project

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 API key

val project = Project(client)

val response = project.listVariables(
    queries = listOf(), // optional
    total = false // optional
)
```
```server-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Project;

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 API key

Project project = new Project(client);

project.listVariables(
    List.of(), // queries (optional)
    false, // total (optional)
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        System.out.println(result);
    })
);
```
```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 API key

let project = Project(client)

let variableList = try await project.listVariables(
    queries: [], // optional
    total: false // optional
)
```
```server-go
package main

import (
    "fmt"
    "github.com/appwrite/sdk-for-go/appwrite"
)

client := appwrite.NewClient(
    appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"),
    appwrite.WithProject("<YOUR_PROJECT_ID>"),
    appwrite.WithKey("<YOUR_API_KEY>"),
)

project := appwrite.NewProject(client)

response, error := project.ListVariables(
    appwrite.WithListVariablesQueries([]interface{}{}),
    appwrite.WithListVariablesTotal(false),
)
```
```server-rust
use appwrite::Client;
use appwrite::services::Project;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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 API key

    let project = Project::new(&client);

    let result = project.list_variables(
        Some(vec![]), // optional
        Some(false) // optional
    ).await?;

    let _ = result;

    Ok(())
}
```

#### Get a variable

```server-nodejs
import { Client, Project } from '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 API key

const project = new Project(client);

const result = await project.getVariable({
    variableId: '<VARIABLE_ID>'
});
```
```server-deno
import { Client, Project } 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 API key

const project = new Project(client);

const result = await project.getVariable({
    variableId: '<VARIABLE_ID>'
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Project;

$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 API key

$project = new Project($client);

$result = $project->getVariable(
    variableId: '<VARIABLE_ID>'
);
```
```server-python
from appwrite.client import Client
from appwrite.services.project import Project
from appwrite.models import Variable

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 API key

project = Project(client)

result: Variable = project.get_variable(
    variable_id = '<VARIABLE_ID>'
)

print(result.model_dump())
```
```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 API key

project = Project.new(client)

result = project.get_variable(
    variable_id: '<VARIABLE_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 API key

Project project = new Project(client);

Variable result = await project.GetVariable(
    variableId: "<VARIABLE_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 API key

Project project = Project(client);

Variable result = await project.getVariable(
    variableId: '<VARIABLE_ID>',
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.coroutines.CoroutineCallback
import io.appwrite.services.Project

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 API key

val project = Project(client)

val response = project.getVariable(
    variableId = "<VARIABLE_ID>"
)
```
```server-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Project;

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 API key

Project project = new Project(client);

project.getVariable(
    "<VARIABLE_ID>", // variableId
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        System.out.println(result);
    })
);
```
```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 API key

let project = Project(client)

let variable = try await project.getVariable(
    variableId: "<VARIABLE_ID>"
)
```
```server-go
package main

import (
    "fmt"
    "github.com/appwrite/sdk-for-go/appwrite"
)

client := appwrite.NewClient(
    appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"),
    appwrite.WithProject("<YOUR_PROJECT_ID>"),
    appwrite.WithKey("<YOUR_API_KEY>"),
)

project := appwrite.NewProject(client)

response, error := project.GetVariable(
    "<VARIABLE_ID>",
)
```
```server-rust
use appwrite::Client;
use appwrite::services::Project;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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 API key

    let project = Project::new(&client);

    let result = project.get_variable(
        "<VARIABLE_ID>"
    ).await?;

    let _ = result;

    Ok(())
}
```

#### Update a variable

You can change a variable's `key`, `value`, or `secret` flag. Marking a variable as secret is one-way. Once set, the value is no longer readable from the Console or API.

```server-nodejs
import { Client, Project } from '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 API key

const project = new Project(client);

const result = await project.updateVariable({
    variableId: '<VARIABLE_ID>',
    key: '<KEY>', // optional
    value: '<VALUE>', // optional
    secret: false // optional
});
```
```server-deno
import { Client, Project } 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 API key

const project = new Project(client);

const result = await project.updateVariable({
    variableId: '<VARIABLE_ID>',
    key: '<KEY>', // optional
    value: '<VALUE>', // optional
    secret: false // optional
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Project;

$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 API key

$project = new Project($client);

$result = $project->updateVariable(
    variableId: '<VARIABLE_ID>',
    key: '<KEY>', // optional
    value: '<VALUE>', // optional
    secret: false // optional
);
```
```server-python
from appwrite.client import Client
from appwrite.services.project import Project
from appwrite.models import Variable

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 API key

project = Project(client)

result: Variable = project.update_variable(
    variable_id = '<VARIABLE_ID>',
    key = '<KEY>', # optional
    value = '<VALUE>', # optional
    secret = False # optional
)

print(result.model_dump())
```
```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 API key

project = Project.new(client)

result = project.update_variable(
    variable_id: '<VARIABLE_ID>',
    key: '<KEY>', # optional
    value: '<VALUE>', # optional
    secret: false # 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 API key

Project project = new Project(client);

Variable result = await project.UpdateVariable(
    variableId: "<VARIABLE_ID>",
    key: "<KEY>", // optional
    value: "<VALUE>", // optional
    secret: false // 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 API key

Project project = Project(client);

Variable result = await project.updateVariable(
    variableId: '<VARIABLE_ID>',
    key: '<KEY>', // (optional)
    value: '<VALUE>', // (optional)
    secret: false, // (optional)
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.coroutines.CoroutineCallback
import io.appwrite.services.Project

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 API key

val project = Project(client)

val response = project.updateVariable(
    variableId = "<VARIABLE_ID>",
    key = "<KEY>", // optional
    value = "<VALUE>", // optional
    secret = false // optional
)
```
```server-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Project;

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 API key

Project project = new Project(client);

project.updateVariable(
    "<VARIABLE_ID>", // variableId
    "<KEY>", // key (optional)
    "<VALUE>", // value (optional)
    false, // secret (optional)
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        System.out.println(result);
    })
);
```
```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 API key

let project = Project(client)

let variable = try await project.updateVariable(
    variableId: "<VARIABLE_ID>",
    key: "<KEY>", // optional
    value: "<VALUE>", // optional
    secret: false // optional
)
```
```server-go
package main

import (
    "fmt"
    "github.com/appwrite/sdk-for-go/appwrite"
)

client := appwrite.NewClient(
    appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"),
    appwrite.WithProject("<YOUR_PROJECT_ID>"),
    appwrite.WithKey("<YOUR_API_KEY>"),
)

project := appwrite.NewProject(client)

response, error := project.UpdateVariable(
    "<VARIABLE_ID>",
    appwrite.WithUpdateVariableKey("<KEY>"),
    appwrite.WithUpdateVariableValue("<VALUE>"),
    appwrite.WithUpdateVariableSecret(false),
)
```
```server-rust
use appwrite::Client;
use appwrite::services::Project;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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 API key

    let project = Project::new(&client);

    let result = project.update_variable(
        "<VARIABLE_ID>",
        Some("<KEY>"), // optional
        Some("<VALUE>"), // optional
        Some(false) // optional
    ).await?;

    let _ = result;

    Ok(())
}
```

#### Delete a variable

```server-nodejs
import { Client, Project } from '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 API key

const project = new Project(client);

const result = await project.deleteVariable({
    variableId: '<VARIABLE_ID>'
});
```
```server-deno
import { Client, Project } 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 API key

const project = new Project(client);

const result = await project.deleteVariable({
    variableId: '<VARIABLE_ID>'
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Project;

$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 API key

$project = new Project($client);

$result = $project->deleteVariable(
    variableId: '<VARIABLE_ID>'
);
```
```server-python
from appwrite.client import Client
from appwrite.services.project import Project

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 API key

project = Project(client)

result = project.delete_variable(
    variable_id = '<VARIABLE_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 API key

project = Project.new(client)

result = project.delete_variable(
    variable_id: '<VARIABLE_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 API key

Project project = new Project(client);

await project.DeleteVariable(
    variableId: "<VARIABLE_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 API key

Project project = Project(client);

await project.deleteVariable(
    variableId: '<VARIABLE_ID>',
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.coroutines.CoroutineCallback
import io.appwrite.services.Project

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 API key

val project = Project(client)

val response = project.deleteVariable(
    variableId = "<VARIABLE_ID>"
)
```
```server-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Project;

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 API key

Project project = new Project(client);

project.deleteVariable(
    "<VARIABLE_ID>", // variableId
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        System.out.println(result);
    })
);
```
```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 API key

let project = Project(client)

let result = try await project.deleteVariable(
    variableId: "<VARIABLE_ID>"
)
```
```server-go
package main

import (
    "fmt"
    "github.com/appwrite/sdk-for-go/appwrite"
)

client := appwrite.NewClient(
    appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"),
    appwrite.WithProject("<YOUR_PROJECT_ID>"),
    appwrite.WithKey("<YOUR_API_KEY>"),
)

project := appwrite.NewProject(client)

response, error := project.DeleteVariable(
    "<VARIABLE_ID>",
)
```
```server-rust
use appwrite::Client;
use appwrite::services::Project;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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 API key

    let project = Project::new(&client);

    project.delete_variable(
        "<VARIABLE_ID>"
    ).await?;

    Ok(())
}
```

### Secret variables

Mark a variable as **Secret** to hide its value from the Console and API after creation. Only the function or site runtime can read the value at build and runtime. Team members and external integrations cannot retrieve it after creation.

You can mark a variable as secret either when you create it or by updating an existing variable. Marking a variable as secret cannot be reversed. To replace a secret value, delete the variable and create a new one with the same key.

### Override behavior

When the same key is defined in multiple scopes, more specific scopes take precedence:

1. Project variables are merged into the runtime environment first.
2. Function or site variables override matching keys from project scope.
3. Appwrite-injected variables (those prefixed with `APPWRITE_`) override matching keys from both scopes and cannot be customized.

This lets you set a default at the project level (for example, `LOG_LEVEL=info`) and override it for a specific function or site that needs different behavior.

### Limits

| Field | Limit |
|----------------|------------------------------------------------|
| Variable ID | 36 characters, `a-z A-Z 0-9 . - _` |
| Key | 255 characters |
| Value | 8192 characters |

---

## Issue and rotate API keys

URL: https://appwrite.io/docs/partners/project/key-rotation

Every connection your platform makes into a customer's project rides on an API key, and a key is a credential like any other in production. Left alone, a long-lived key with broad scopes is exactly the thing a security review flags: too much access, no expiry, no record of when it was last rotated. The Project API lets you run a key's whole life from your backend so it never becomes that liability:

- Issue it scoped to one integration, with an expiry.
- Audit the keys on the project before each rotation cycle.
- Replace its secret on a schedule, with no downtime.
- Revoke it the moment it is no longer needed.

This guide follows one key from the day you stand up an integration against a customer's project, through the routine audit and rotation that keeps it fresh, to the day you retire it.

**Required scopes**

The API key used for these calls needs the `keys.read` and `keys.write` scopes.

### Issue a key scoped to the integration

When you connect a new integration to a customer's project, give it a key that can reach only the parts of the API that integration touches, and put an expiry on it from the start. Set up the client once, then issue the key. A sync job that reads and writes tables needs the `tables.read` and `tables.write` scopes and nothing more, so that is all its key gets.

```server-nodejs
import { Client, Project, ID, ProjectKeyScopes } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.createKey({
    keyId: ID.unique(),
    name: 'Tenant integration',
    scopes: [ProjectKeyScopes.TablesRead, ProjectKeyScopes.TablesWrite],
    expire: '2026-12-31T23:59:59.000+00:00'
});
```
```server-deno
import { Client, Project, ID, ProjectKeyScopes } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.createKey({
    keyId: ID.unique(),
    name: 'Tenant integration',
    scopes: [ProjectKeyScopes.TablesRead, ProjectKeyScopes.TablesWrite],
    expire: '2026-12-31T23:59:59.000+00:00'
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\ID;
use Appwrite\Services\Project;

$client = new Client();

$client
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$project = new Project($client);

$result = $project->createKey(
    keyId: ID::unique(),
    name: 'Tenant integration',
    scopes: ['tables.read', 'tables.write'],
    expire: '2026-12-31T23:59:59.000+00:00'
);
```
```server-python
from appwrite.client import Client
from appwrite.id import ID
from appwrite.services.project import Project
from appwrite.enums import ProjectKeyScopes

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

project = Project(client)

result = project.create_key(
    key_id=ID.unique(),
    name='Tenant integration',
    scopes=[ProjectKeyScopes.TABLES_READ, ProjectKeyScopes.TABLES_WRITE],
    expire='2026-12-31T23:59:59.000+00:00'
)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

project = Project.new(client)

response = project.create_key(
    key_id: ID.unique(),
    name: 'Tenant integration',
    scopes: ['tables.read', 'tables.write'],
    expire: '2026-12-31T23:59:59.000+00:00'
)
```
```server-dotnet
using Appwrite;
using Appwrite.Enums;
using Appwrite.Services;
using Appwrite.Models;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Project project = new Project(client);

var result = await project.CreateKey(
    keyId: ID.Unique(),
    name: "Tenant integration",
    scopes: new List<ProjectKeyScopes> {ProjectKeyScopes.TablesRead, ProjectKeyScopes.TablesWrite},
    expire: "2026-12-31T23:59:59.000+00:00"
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';
import 'package:dart_appwrite/enums.dart' as enums;

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Project project = Project(client);

final result = await project.createKey(
    keyId: ID.unique(),
    name: 'Tenant integration',
    scopes: [enums.ProjectKeyScopes.tablesRead, enums.ProjectKeyScopes.tablesWrite],
    expire: '2026-12-31T23:59:59.000+00:00',
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.ID
import io.appwrite.enums.ProjectKeyScopes
import io.appwrite.services.Project

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val project = Project(client)

val result = project.createKey(
    keyId = ID.unique(),
    name = "Tenant integration",
    scopes = listOf(ProjectKeyScopes.TABLES_READ, ProjectKeyScopes.TABLES_WRITE),
    expire = "2026-12-31T23:59:59.000+00:00"
)
```
```server-java
import io.appwrite.Client;
import io.appwrite.ID;
import io.appwrite.enums.ProjectKeyScopes;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Project;

Client client = new Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>");

Project project = new Project(client);

project.createKey(
    ID.unique(),                                     // keyId
    "Tenant integration",                                    // name
    List.of(ProjectKeyScopes.TABLES_READ, ProjectKeyScopes.TABLES_WRITE), // scopes
    "2026-12-31T23:59:59.000+00:00",                // expire
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }
        System.out.println(result);
    })
);
```
```server-swift
import Appwrite
import AppwriteEnums

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let project = Project(client)

let result = try await project.createKey(
    keyId: ID.unique(),
    name: "Tenant integration",
    scopes: [ProjectKeyScopes.tablesRead, ProjectKeyScopes.tablesWrite],
    expire: "2026-12-31T23:59:59.000+00:00"
)
```
```server-go
package main

import (
    "fmt"
    "github.com/appwrite/sdk-for-go/appwrite"
    "github.com/appwrite/sdk-for-go/id"
)

func main() {
    client := appwrite.NewClient(
        appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"),
        appwrite.WithProject("<PROJECT_ID>"),
        appwrite.WithKey("<YOUR_API_KEY>"),
    )

    project := appwrite.NewProject(client)
    result, err := project.CreateKey(
        id.Unique(),
        "Tenant integration",
        []string{"tables.read", "tables.write"},
        project.WithCreateKeyExpire("2026-12-31T23:59:59.000+00:00"),
    )

    if err != nil {
        panic(err)
    }

    fmt.Println(result)
}
```
```server-rust
use appwrite::Client;
use appwrite::id::ID;
use appwrite::enums::ProjectKeyScopes;
use appwrite::services::project::Project;

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

    let project = Project::new(&client);

    let result = project.create_key(
        ID::unique(),                                  // key_id
        "Tenant integration",                                  // name
        vec![ProjectKeyScopes::TablesRead, ProjectKeyScopes::TablesWrite], // scopes
        Some("2026-12-31T23:59:59.000+00:00"),         // expire
    ).await?;

    println!("{:?}", result);
    Ok(())
}
```
```bash
appwrite project create-key \
    --key-id 'unique()' \
    --name "Tenant integration" \
    --scopes tables.read tables.write
```
```http
POST /v1/project/keys HTTP/1.1
Content-Type: application/json
X-Appwrite-Project: <PROJECT_ID>
X-Appwrite-Key: <YOUR_API_KEY>

{
  "keyId": "unique()",
  "name": "Tenant integration",
  "scopes": ["tables.read", "tables.write"],
  "expire": "2026-12-31T23:59:59.000+00:00"
}
```

The secret is returned once, on creation, and never again. Write it straight to your secrets manager, because there is no call that reads it back.

### Audit the project's keys

Rotation begins with knowing what is out there. When a rotation window comes due, list the keys on the project to see them all and when each one expires.

```server-nodejs
const result = await project.listKeys();
```
```server-deno
const result = await project.listKeys();
```
```server-php
$result = $project->listKeys();
```
```server-python
result = project.list_keys()
```
```server-ruby
response = project.list_keys()
```
```server-dotnet
var result = await project.ListKeys();
```
```server-dart
final result = await project.listKeys();
```
```server-kotlin
val result = project.listKeys()
```
```server-java
project.listKeys(
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }
        System.out.println(result);
    })
);
```
```server-swift
let result = try await project.listKeys()
```
```server-go
result, err := project.ListKeys()

if err != nil {
    panic(err)
}

fmt.Println(result)
```
```server-rust
let result = project.list_keys(
    None,  // queries
    None,  // total
).await?;

println!("{:?}", result);
```
```bash
appwrite project list-keys
```
```http
GET /v1/project/keys HTTP/1.1
X-Appwrite-Project: <PROJECT_ID>
X-Appwrite-Key: <YOUR_API_KEY>
```

For any key that is close to expiry, read it back to confirm exactly what it can do before you decide how to handle it.

```server-nodejs
const result = await project.getKey({
    keyId: '<KEY_ID>'
});
```
```server-deno
const result = await project.getKey({
    keyId: '<KEY_ID>'
});
```
```server-php
$result = $project->getKey(
    keyId: '<KEY_ID>'
);
```
```server-python
result = project.get_key(
    key_id='<KEY_ID>'
)
```
```server-ruby
response = project.get_key(
    key_id: '<KEY_ID>'
)
```
```server-dotnet
var result = await project.GetKey(
    keyId: "<KEY_ID>"
);
```
```server-dart
final result = await project.getKey(
    keyId: '<KEY_ID>',
);
```
```server-kotlin
val result = project.getKey(
    keyId = "<KEY_ID>"
)
```
```server-java
project.getKey(
    "<KEY_ID>",                                      // keyId
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }
        System.out.println(result);
    })
);
```
```server-swift
let result = try await project.getKey(
    keyId: "<KEY_ID>"
)
```
```server-go
result, err := project.GetKey(
    "<KEY_ID>",
)

if err != nil {
    panic(err)
}

fmt.Println(result)
```
```server-rust
let result = project.get_key(
    "<KEY_ID>",
).await?;

println!("{:?}", result);
```
```bash
appwrite project get-key \
    --key-id '<KEY_ID>'
```
```http
GET /v1/project/keys/<KEY_ID> HTTP/1.1
X-Appwrite-Project: <PROJECT_ID>
X-Appwrite-Key: <YOUR_API_KEY>
```

### Update a key's scopes or expiry

Sometimes a key just needs a smaller scope set or a later expiry, not a new secret. Updating a key changes its name, scopes, and expiry while keeping the same secret, so reach for it when you are adjusting access for an integration that has not changed credentials.

```server-nodejs
const result = await project.updateKey({
    keyId: '<KEY_ID>',
    name: 'Updated Key',
    scopes: [ProjectKeyScopes.TablesRead, ProjectKeyScopes.TablesWrite, ProjectKeyScopes.UsersRead],
    expire: '2027-06-30T23:59:59.000+00:00'
});
```
```server-deno
const result = await project.updateKey({
    keyId: '<KEY_ID>',
    name: 'Updated Key',
    scopes: [ProjectKeyScopes.TablesRead, ProjectKeyScopes.TablesWrite, ProjectKeyScopes.UsersRead],
    expire: '2027-06-30T23:59:59.000+00:00'
});
```
```server-php
$result = $project->updateKey(
    keyId: '<KEY_ID>',
    name: 'Updated Key',
    scopes: ['tables.read', 'tables.write', 'users.read'],
    expire: '2027-06-30T23:59:59.000+00:00'
);
```
```server-python
result = project.update_key(
    key_id='<KEY_ID>',
    name='Updated Key',
    scopes=[ProjectKeyScopes.TABLES_READ, ProjectKeyScopes.TABLES_WRITE, ProjectKeyScopes.USERS_READ],
    expire='2027-06-30T23:59:59.000+00:00'
)
```
```server-ruby
response = project.update_key(
    key_id: '<KEY_ID>',
    name: 'Updated Key',
    scopes: ['tables.read', 'tables.write', 'users.read'],
    expire: '2027-06-30T23:59:59.000+00:00'
)
```
```server-dotnet
var result = await project.UpdateKey(
    keyId: "<KEY_ID>",
    name: "Updated Key",
    scopes: new List<ProjectKeyScopes> {ProjectKeyScopes.TablesRead, ProjectKeyScopes.TablesWrite, ProjectKeyScopes.UsersRead},
    expire: "2027-06-30T23:59:59.000+00:00"
);
```
```server-dart
final result = await project.updateKey(
    keyId: '<KEY_ID>',
    name: 'Updated Key',
    scopes: [enums.ProjectKeyScopes.tablesRead, enums.ProjectKeyScopes.tablesWrite, enums.ProjectKeyScopes.usersRead],
    expire: '2027-06-30T23:59:59.000+00:00',
);
```
```server-kotlin
val result = project.updateKey(
    keyId = "<KEY_ID>",
    name = "Updated Key",
    scopes = listOf(ProjectKeyScopes.TABLES_READ, ProjectKeyScopes.TABLES_WRITE, ProjectKeyScopes.USERS_READ),
    expire = "2027-06-30T23:59:59.000+00:00"
)
```
```server-java
project.updateKey(
    "<KEY_ID>",                                      // keyId
    "Updated Key",                                   // name
    List.of(ProjectKeyScopes.TABLES_READ, ProjectKeyScopes.TABLES_WRITE, ProjectKeyScopes.USERS_READ), // scopes
    "2027-06-30T23:59:59.000+00:00",                // expire
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }
        System.out.println(result);
    })
);
```
```server-swift
let result = try await project.updateKey(
    keyId: "<KEY_ID>",
    name: "Updated Key",
    scopes: [ProjectKeyScopes.tablesRead, ProjectKeyScopes.tablesWrite, ProjectKeyScopes.usersRead],
    expire: "2027-06-30T23:59:59.000+00:00"
)
```
```server-go
result, err := project.UpdateKey(
    "<KEY_ID>",
    "Updated Key",
    []string{"tables.read", "tables.write", "users.read"},
    project.WithUpdateKeyExpire("2027-06-30T23:59:59.000+00:00"),
)

if err != nil {
    panic(err)
}

fmt.Println(result)
```
```server-rust
let result = project.update_key(
    "<KEY_ID>",                                    // key_id
    "Updated Key",                                 // name
    vec![ProjectKeyScopes::TablesRead, ProjectKeyScopes::TablesWrite, ProjectKeyScopes::UsersRead], // scopes
    Some("2027-06-30T23:59:59.000+00:00"),         // expire
).await?;

println!("{:?}", result);
```
```bash
appwrite project update-key \
    --key-id '<KEY_ID>' \
    --name "Updated Key" \
    --scopes tables.read tables.write users.read
```
```http
PUT /v1/project/keys/<KEY_ID> HTTP/1.1
Content-Type: application/json
X-Appwrite-Project: <PROJECT_ID>
X-Appwrite-Key: <YOUR_API_KEY>

{
  "name": "Updated Key",
  "scopes": ["tables.read", "tables.write", "users.read"],
  "expire": "2027-06-30T23:59:59.000+00:00"
}
```

### Rotate a key's secret

Rotating the secret itself, the part a security policy usually requires on a fixed cadence, means retiring one key and standing up another without a gap in service:

1. Issue a fresh key with the same scopes using the create call above.
2. Point the integration at the new secret.
3. Confirm its traffic has moved over.
4. Delete the old key so the retired secret stops working.

```server-nodejs
await project.deleteKey({
    keyId: '<KEY_ID>'
});
```
```server-deno
await project.deleteKey({
    keyId: '<KEY_ID>'
});
```
```server-php
$project->deleteKey(
    keyId: '<KEY_ID>'
);
```
```server-python
project.delete_key(
    key_id='<KEY_ID>'
)
```
```server-ruby
project.delete_key(
    key_id: '<KEY_ID>'
)
```
```server-dotnet
await project.DeleteKey(
    keyId: "<KEY_ID>"
);
```
```server-dart
await project.deleteKey(
    keyId: '<KEY_ID>',
);
```
```server-kotlin
project.deleteKey(
    keyId = "<KEY_ID>"
)
```
```server-java
project.deleteKey(
    "<KEY_ID>",                                      // keyId
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }
        System.out.println(result);
    })
);
```
```server-swift
try await project.deleteKey(
    keyId: "<KEY_ID>"
)
```
```server-go
_, err := project.DeleteKey(
    "<KEY_ID>",
)

if err != nil {
    panic(err)
}

fmt.Println("API key deleted")
```
```server-rust
project.delete_key(
    "<KEY_ID>",
).await?;
```
```bash
appwrite project delete-key \
    --key-id '<KEY_ID>'
```
```http
DELETE /v1/project/keys/<KEY_ID> HTTP/1.1
X-Appwrite-Project: <PROJECT_ID>
X-Appwrite-Key: <YOUR_API_KEY>
```

The delete takes effect immediately, so only run it once you have confirmed nothing is still authenticating with the old secret. The same call revokes a key that has leaked, without waiting for its expiry.

### Issue a short-lived ephemeral key

Not every job deserves a long-lived key. A support engineer running a one-off migration, or a script that needs an hour of access, can take an ephemeral key instead. It carries its scopes and a lifetime of up to an hour, then expires on its own with nothing for you to revoke afterward.

```server-nodejs
const result = await project.createEphemeralKey({
    scopes: [ProjectKeyScopes.TablesRead, ProjectKeyScopes.TablesWrite],
    duration: 600
});
```
```server-deno
const result = await project.createEphemeralKey({
    scopes: [ProjectKeyScopes.TablesRead, ProjectKeyScopes.TablesWrite],
    duration: 600
});
```
```server-php
$result = $project->createEphemeralKey(
    scopes: ['tables.read', 'tables.write'],
    duration: 600
);
```
```server-python
result = project.create_ephemeral_key(
    scopes=[ProjectKeyScopes.TABLES_READ, ProjectKeyScopes.TABLES_WRITE],
    duration=600
)
```
```server-ruby
response = project.create_ephemeral_key(
    scopes: ['tables.read', 'tables.write'],
    duration: 600
)
```
```server-dotnet
var result = await project.CreateEphemeralKey(
    scopes: new List<ProjectKeyScopes> {ProjectKeyScopes.TablesRead, ProjectKeyScopes.TablesWrite},
    duration: 600
);
```
```server-dart
final result = await project.createEphemeralKey(
    scopes: [enums.ProjectKeyScopes.tablesRead, enums.ProjectKeyScopes.tablesWrite],
    duration: 600,
);
```
```server-kotlin
val result = project.createEphemeralKey(
    scopes = listOf(ProjectKeyScopes.TABLES_READ, ProjectKeyScopes.TABLES_WRITE),
    duration = 600
)
```
```server-java
project.createEphemeralKey(
    List.of(ProjectKeyScopes.TABLES_READ, ProjectKeyScopes.TABLES_WRITE), // scopes
    600, // duration
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }
        System.out.println(result);
    })
);
```
```server-swift
let result = try await project.createEphemeralKey(
    scopes: [ProjectKeyScopes.tablesRead, ProjectKeyScopes.tablesWrite],
    duration: 600
)
```
```server-go
result, err := project.CreateEphemeralKey(
    []string{"tables.read", "tables.write"},
    600,
)

if err != nil {
    panic(err)
}

fmt.Println(result)
```
```server-rust
let result = project.create_ephemeral_key(
    vec![ProjectKeyScopes::TablesRead, ProjectKeyScopes::TablesWrite], // scopes
    600, // duration
).await?;

println!("{:?}", result);
```
```bash
appwrite project create-ephemeral-key \
    --scopes tables.read tables.write \
    --duration 600
```
```http
POST /v1/project/keys/ephemeral HTTP/1.1
Content-Type: application/json
X-Appwrite-Project: <PROJECT_ID>
X-Appwrite-Key: <YOUR_API_KEY>

{
  "scopes": ["tables.read", "tables.write"],
  "duration": 600
}
```

### Next steps

Running a key's whole life from code is what lets you treat customer credentials as managed infrastructure instead of secrets someone pasted once. The same Project API covers the rest of a project's life:

- [Provision a project's baseline](/docs/partners/project/provisioning) before you issue its first key.
- Read the [API keys](/docs/partners/project/api-keys) reference for the full list of scopes a key can hold.

---

## Labels

URL: https://appwrite.io/docs/partners/project/labels

Labels are short alphanumeric tags you assign to a project. Use them to categorize your projects and filter them within an organization, for example by environment (`production`, `staging`), team, or region.

### Manage with a Server SDK

Manage project labels with a [Server SDK](/docs/sdks#server). Labels may contain only alphanumeric characters.

**Required scope**

The API key used for this call needs the `project.write` scope.

#### Update labels

The `labels` array replaces the project's existing labels, so include every label you want to keep in each call. A project can have up to 1000 labels, each up to 36 alphanumeric characters long.

```server-nodejs
import { Client, Project } from '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 API key

const project = new Project(client);

const result = await project.updateLabels({
    labels: ['production', 'eu']
});
```
```server-deno
import { Client, Project } 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 API key

const project = new Project(client);

const result = await project.updateLabels({
    labels: ['production', 'eu']
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Project;

$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 API key

$project = new Project($client);

$result = $project->updateLabels(
    labels: ['production', 'eu']
);
```
```server-python
from appwrite.client import Client
from appwrite.services.project import Project
from appwrite.models import Project as ProjectModel

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 API key

project = Project(client)

result: ProjectModel = project.update_labels(
    labels = ['production', 'eu']
)

print(result.model_dump())
```
```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 API key

project = Project.new(client)

result = project.update_labels(
    labels: ['production', 'eu']
)
```
```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 API key

Project project = new Project(client);

Project result = await project.UpdateLabels(
    labels: new List<string> { "production", "eu" }
);
```
```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 API key

Project project = Project(client);

Project result = await project.updateLabels(
    labels: ['production', 'eu'],
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.coroutines.CoroutineCallback
import io.appwrite.services.Project

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 API key

val project = Project(client)

val response = project.updateLabels(
    labels = listOf("production", "eu")
)
```
```server-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Project;

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 API key

Project project = new Project(client);

project.updateLabels(
    List.of("production", "eu"), // labels
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        System.out.println(result);
    })
);
```
```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 API key

let project = Project(client)

let result = try await project.updateLabels(
    labels: ["production", "eu"]
)
```
```server-go
package main

import (
    "fmt"
    "github.com/appwrite/sdk-for-go/appwrite"
)

func main() {
    client := appwrite.NewClient(
        appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"),
        appwrite.WithProject("<YOUR_PROJECT_ID>"),
        appwrite.WithKey("<YOUR_API_KEY>"),
    )

    project := appwrite.NewProject(client)
    result, err := project.UpdateLabels(
        []interface{}{"production", "eu"},
    )

    if err != nil {
        panic(err)
    }

    fmt.Println(result)
}
```
```server-rust
use appwrite::Client;
use appwrite::services::Project;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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 API key

    let project = Project::new(&client);

    let result = project.update_labels(
        vec!["production", "eu"]
    ).await?;

    let _ = result;

    Ok(())
}
```
```bash
appwrite project update-labels \
    --labels production \
    --labels eu
```

**Reading labels**

Labels have no dedicated read endpoint. Call `get` to fetch the project and read its `labels` field.

```server-nodejs
const result = await project.get();
console.log(result.labels); // ['production', 'eu']
```

### Benefits

- **Organize at scale.** Group related projects with shared tags such as environment, team, or region, so large organizations stay navigable.
- **Faster filtering.** Find the projects you need by filtering an organization on its labels instead of scanning the full list.
- **Repeatable provisioning.** Apply a consistent labeling scheme across every project you create from the same provisioning script.

---

## Mock phones

URL: https://appwrite.io/docs/partners/project/mock-phones

Mock phones let you register fictional phone numbers and a fixed verification code at the project level. When a tester signs in with a registered number, the registered code works in place of a real SMS, so phone authentication flows can be exercised in CI, demo accounts, and app store review submissions without sending SMS or paying provider fees.

Each project stores its mock phones on the project document. Numbers must be in [E.164](https://en.wikipedia.org/wiki/E.164) format, and verification codes are exactly six digits.

### Add from the Console

![Mock phone numbers settings in the Appwrite Console](/images/docs/project/mock-phones.avif)

To add a mock phone manually:

1. Open your project in the Appwrite Console.
2. Navigate to **Auth** in the sidebar, then open the **Settings** tab.
3. Scroll to the **Mock phone numbers** card and click **Add a number**.
4. Enter a phone number in E.164 format (for example, `+15555550100`) and a six-digit verification code.
5. Click **Save** on the row, then **Update** at the bottom of the section to persist.

Use fictional ranges (such as North American `555` numbers) to avoid collision with real subscribers.

### Manage with a Server SDK

The Project service exposes endpoints to list, create, get, update, and delete mock phones. All endpoints require an API key with the appropriate scope.

**Required scopes**

Reading mock phones requires the `mocks.read` scope. Creating, updating, or deleting requires `mocks.write`.

#### Create a mock phone

```server-nodejs
import { Client, Project } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.createMockPhone({
    number: '+15555550100',
    otp: '123456'
});
```
```server-deno
import { Client, Project } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.createMockPhone({
    number: '+15555550100',
    otp: '123456'
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Project;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$project = new Project($client);

$result = $project->createMockPhone(
    number: '+15555550100',
    otp: '123456'
);
```
```server-python
from appwrite.client import Client
from appwrite.services.project import Project

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

project = Project(client)

result = project.create_mock_phone(
    number = '+15555550100',
    otp = '123456'
)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

project = Project.new(client)

result = project.create_mock_phone(
    number: '+15555550100',
    otp: '123456'
)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Project project = new Project(client);

var result = await project.CreateMockPhone(
    number: "+15555550100",
    otp: "123456"
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Project project = Project(client);

final result = await project.createMockPhone(
    number: '+15555550100',
    otp: '123456',
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Project

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val project = Project(client)

val result = project.createMockPhone(
    number = "+15555550100",
    otp = "123456"
)
```
```server-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Project;

Client client = new Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>");

Project project = new Project(client);

project.createMockPhone(
    "+15555550100", // number
    "123456", // otp
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }
        System.out.println(result);
    })
);
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let project = Project(client)

let result = try await project.createMockPhone(
    number: "+15555550100",
    otp: "123456"
)
```
```server-go
package main

import (
    "fmt"
    "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("<YOUR_API_KEY>"),
    )

    project := appwrite.NewProject(client)
    result, err := project.CreateMockPhone(
        "+15555550100",
        "123456",
    )

    if err != nil {
        panic(err)
    }

    fmt.Println(result)
}
```
```server-rust
use appwrite::Client;
use appwrite::services::Project;

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

    let project = Project::new(&client);

    let result = project.create_mock_phone(
        "+15555550100",
        "123456",
    ).await?;

    println!("{:?}", result);
    Ok(())
}
```
```bash
appwrite project create-mock-phone \
    --number +15555550100 \
    --otp 123456
```

The endpoint returns the new [MockNumber](/docs/references/cloud/models/mockNumber) document.

#### List mock phones

```server-nodejs
import { Client, Project } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.listMockPhones();
```
```server-deno
import { Client, Project } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.listMockPhones();
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Project;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$project = new Project($client);

$result = $project->listMockPhones();
```
```server-python
from appwrite.client import Client
from appwrite.services.project import Project

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

project = Project(client)

result = project.list_mock_phones()
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

project = Project.new(client)

result = project.list_mock_phones()
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Project project = new Project(client);

var result = await project.ListMockPhones();
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Project project = Project(client);

final result = await project.listMockPhones();
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Project

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val project = Project(client)

val result = project.listMockPhones()
```
```server-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Project;

Client client = new Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>");

Project project = new Project(client);

project.listMockPhones(new CoroutineCallback<>((result, error) -> {
    if (error != null) {
        error.printStackTrace();
        return;
    }
    System.out.println(result);
}));
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let project = Project(client)

let result = try await project.listMockPhones()
```
```server-go
package main

import (
    "fmt"
    "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("<YOUR_API_KEY>"),
    )

    project := appwrite.NewProject(client)
    result, err := project.ListMockPhones()

    if err != nil {
        panic(err)
    }

    fmt.Println(result)
}
```
```server-rust
use appwrite::Client;
use appwrite::services::Project;

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

    let project = Project::new(&client);

    let result = project.list_mock_phones(None, None).await?;

    println!("{:?}", result);
    Ok(())
}
```
```bash
appwrite project list-mock-phones \
    --limit 25
```

#### Get a mock phone

```server-nodejs
import { Client, Project } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.getMockPhone({
    number: '+15555550100'
});
```
```server-deno
import { Client, Project } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.getMockPhone({
    number: '+15555550100'
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Project;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$project = new Project($client);

$result = $project->getMockPhone(
    number: '+15555550100'
);
```
```server-python
from appwrite.client import Client
from appwrite.services.project import Project

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

project = Project(client)

result = project.get_mock_phone(
    number = '+15555550100'
)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

project = Project.new(client)

result = project.get_mock_phone(
    number: '+15555550100'
)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Project project = new Project(client);

var result = await project.GetMockPhone(
    number: "+15555550100"
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Project project = Project(client);

final result = await project.getMockPhone(
    number: '+15555550100',
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Project

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val project = Project(client)

val result = project.getMockPhone(
    number = "+15555550100"
)
```
```server-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Project;

Client client = new Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>");

Project project = new Project(client);

project.getMockPhone(
    "+15555550100", // number
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }
        System.out.println(result);
    })
);
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let project = Project(client)

let result = try await project.getMockPhone(
    number: "+15555550100"
)
```
```server-go
package main

import (
    "fmt"
    "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("<YOUR_API_KEY>"),
    )

    project := appwrite.NewProject(client)
    result, err := project.GetMockPhone("+15555550100")

    if err != nil {
        panic(err)
    }

    fmt.Println(result)
}
```
```server-rust
use appwrite::Client;
use appwrite::services::Project;

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

    let project = Project::new(&client);

    let result = project.get_mock_phone("+15555550100").await?;

    println!("{:?}", result);
    Ok(())
}
```
```bash
appwrite project get-mock-phone --number +15555550100
```

#### Update a mock phone

```server-nodejs
import { Client, Project } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.updateMockPhone({
    number: '+15555550100',
    otp: '654321'
});
```
```server-deno
import { Client, Project } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.updateMockPhone({
    number: '+15555550100',
    otp: '654321'
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Project;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$project = new Project($client);

$result = $project->updateMockPhone(
    number: '+15555550100',
    otp: '654321'
);
```
```server-python
from appwrite.client import Client
from appwrite.services.project import Project

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

project = Project(client)

result = project.update_mock_phone(
    number = '+15555550100',
    otp = '654321'
)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

project = Project.new(client)

result = project.update_mock_phone(
    number: '+15555550100',
    otp: '654321'
)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Project project = new Project(client);

var result = await project.UpdateMockPhone(
    number: "+15555550100",
    otp: "654321"
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Project project = Project(client);

final result = await project.updateMockPhone(
    number: '+15555550100',
    otp: '654321',
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Project

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val project = Project(client)

val result = project.updateMockPhone(
    number = "+15555550100",
    otp = "654321"
)
```
```server-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Project;

Client client = new Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>");

Project project = new Project(client);

project.updateMockPhone(
    "+15555550100", // number
    "654321", // otp
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }
        System.out.println(result);
    })
);
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let project = Project(client)

let result = try await project.updateMockPhone(
    number: "+15555550100",
    otp: "654321"
)
```
```server-go
package main

import (
    "fmt"
    "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("<YOUR_API_KEY>"),
    )

    project := appwrite.NewProject(client)
    result, err := project.UpdateMockPhone("+15555550100", "654321")

    if err != nil {
        panic(err)
    }

    fmt.Println(result)
}
```
```server-rust
use appwrite::Client;
use appwrite::services::Project;

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

    let project = Project::new(&client);

    let result = project.update_mock_phone("+15555550100", "654321").await?;

    println!("{:?}", result);
    Ok(())
}
```
```bash
appwrite project update-mock-phone \
    --number +15555550100 \
    --otp 654321
```

#### Delete a mock phone

```server-nodejs
import { Client, Project } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

await project.deleteMockPhone({
    number: '+15555550100'
});
```
```server-deno
import { Client, Project } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

await project.deleteMockPhone({
    number: '+15555550100'
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Project;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$project = new Project($client);

$project->deleteMockPhone(
    number: '+15555550100'
);
```
```server-python
from appwrite.client import Client
from appwrite.services.project import Project

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

project = Project(client)

project.delete_mock_phone(
    number = '+15555550100'
)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

project = Project.new(client)

project.delete_mock_phone(
    number: '+15555550100'
)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Project project = new Project(client);

await project.DeleteMockPhone(
    number: "+15555550100"
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Project project = Project(client);

await project.deleteMockPhone(
    number: '+15555550100',
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Project

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val project = Project(client)

project.deleteMockPhone(
    number = "+15555550100"
)
```
```server-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Project;

Client client = new Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>");

Project project = new Project(client);

project.deleteMockPhone(
    "+15555550100", // number
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }
        System.out.println(result);
    })
);
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let project = Project(client)

try await project.deleteMockPhone(
    number: "+15555550100"
)
```
```server-go
package main

import (
    "fmt"
    "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("<YOUR_API_KEY>"),
    )

    project := appwrite.NewProject(client)
    _, err := project.DeleteMockPhone("+15555550100")

    if err != nil {
        panic(err)
    }

    fmt.Println("deleted")
}
```
```server-rust
use appwrite::Client;
use appwrite::services::Project;

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

    let project = Project::new(&client);

    project.delete_mock_phone("+15555550100").await?;

    Ok(())
}
```
```bash
appwrite project delete-mock-phone --number +15555550100
```

### Benefits

- **No SMS costs in test flows.** Exercise phone authentication in CI, demo accounts, and app store review submissions without sending real SMS or paying provider fees.
- **Stable credentials for automation.** Registered numbers and codes don't expire or rotate, so end-to-end tests stay deterministic across runs.
- **Repeatable provisioning.** Script the mock phone set a project should expose and apply it from CI when spinning up a new environment.

---

## OAuth providers

URL: https://appwrite.io/docs/partners/project/oauth

OAuth2 providers let your users sign in with accounts they already have, such as GitHub, Google, or Apple. Each provider stores a client ID and client secret at the project level, and can be enabled or disabled independently.

You can configure providers from the Appwrite Console or programmatically with a [Server SDK](/docs/sdks#server). Each provider has its own update method (`updateOAuth2GitHub`, `updateOAuth2Google`, and so on), the read methods `listOAuth2Providers` and `getOAuth2Provider` cover all of them.

### Configure from the Console

![OAuth2 providers in the Appwrite Console](/images/docs/project/oauth-providers.avif)

To configure a provider from the Console:

1. Open your project in the Appwrite Console.
2. Navigate to **Auth** in the sidebar, then open the **Social providers** tab.
3. Find the provider under **Popular** or **All providers**, or search for it, then click it.
4. Enter the credentials from the provider (the field names vary per provider, see [Available providers](#available-providers)).
5. Toggle the provider on and click **Update**.

### Configure a provider

Each provider has a dedicated update method named `updateOAuth2<Provider>`. The example below configures GitHub. Swap the method name and credential fields to configure a different provider, the field names per provider are listed in [Available providers](#available-providers).

**Required scopes**

The API key used for these calls needs `oauth2.write` to configure providers, and `oauth2.read` to list or fetch them.

**Enabling validates credentials**

Setting `enabled` to `true` triggers end-to-end validation of the credentials against the provider, and the request throws if they are invalid. To store credentials without activating the provider yet, send them with `enabled` set to `false`.

```server-nodejs
import { Client, Project } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.updateOAuth2GitHub({
    clientId: '<CLIENT_ID>',
    clientSecret: '<CLIENT_SECRET>',
    enabled: true
});
```
```server-deno
import { Client, Project } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.updateOAuth2GitHub({
    clientId: '<CLIENT_ID>',
    clientSecret: '<CLIENT_SECRET>',
    enabled: true
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Project;

$client = new Client();

$client
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$project = new Project($client);

$result = $project->updateOAuth2GitHub(
    clientId: '<CLIENT_ID>',
    clientSecret: '<CLIENT_SECRET>',
    enabled: true
);
```
```server-python
from appwrite.client import Client
from appwrite.services.project import Project

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

project = Project(client)

result = project.update_o_auth2_git_hub(
    client_id = '<CLIENT_ID>',
    client_secret = '<CLIENT_SECRET>',
    enabled = True
)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

project = Project.new(client)

response = project.update_o_auth2_git_hub(
    client_id: '<CLIENT_ID>',
    client_secret: '<CLIENT_SECRET>',
    enabled: true
)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Project project = new Project(client);

var result = await project.UpdateOAuth2GitHub(
    clientId: "<CLIENT_ID>",
    clientSecret: "<CLIENT_SECRET>",
    enabled: true
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Project project = Project(client);

final result = await project.updateOAuth2GitHub(
    clientId: '<CLIENT_ID>',
    clientSecret: '<CLIENT_SECRET>',
    enabled: true,
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Project

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val project = Project(client)

val response = project.updateOAuth2GitHub(
    clientId = "<CLIENT_ID>",
    clientSecret = "<CLIENT_SECRET>",
    enabled = true
)
```
```server-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Project;

Client client = new Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>");

Project project = new Project(client);

project.updateOAuth2GitHub(
    "<CLIENT_ID>", // clientId
    "<CLIENT_SECRET>", // clientSecret
    true, // enabled
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }
        System.out.println(result);
    })
);
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let project = Project(client)

let result = try await project.updateOAuth2GitHub(
    clientId: "<CLIENT_ID>",
    clientSecret: "<CLIENT_SECRET>",
    enabled: true
)
```
```server-go
package main

import (
    "fmt"
    "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("<YOUR_API_KEY>"),
    )

    service := appwrite.NewProject(client)
    result, err := service.UpdateOAuth2GitHub(
        service.WithUpdateOAuth2GitHubClientId("<CLIENT_ID>"),
        service.WithUpdateOAuth2GitHubClientSecret("<CLIENT_SECRET>"),
        service.WithUpdateOAuth2GitHubEnabled(true),
    )

    if err != nil {
        panic(err)
    }

    fmt.Println(result)
}
```
```server-rust
use appwrite::Client;
use appwrite::services::project::Project;

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

    let project = Project::new(&client);

    let result = project.update_o_auth2_git_hub(
        Some("<CLIENT_ID>"),
        Some("<CLIENT_SECRET>"),
        Some(true),
    ).await?;

    println!("{:?}", result);
    Ok(())
}
```
```bash
appwrite project update-o-auth-2-git-hub \
    --client-id "<CLIENT_ID>" \
    --client-secret "<CLIENT_SECRET>" \
    --enabled true
```

#### Providers with extra fields

Some providers take more than a client ID and secret. Google, for example, accepts an additional `prompt` parameter that controls the consent screen behavior:

```server-nodejs
import { Client, Project, ProjectOAuth2GooglePrompt } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.updateOAuth2Google({
    clientId: '<CLIENT_ID>',
    clientSecret: '<CLIENT_SECRET>',
    prompt: [ProjectOAuth2GooglePrompt.SelectAccount],
    enabled: true
});
```
```server-deno
import { Client, Project, ProjectOAuth2GooglePrompt } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.updateOAuth2Google({
    clientId: '<CLIENT_ID>',
    clientSecret: '<CLIENT_SECRET>',
    prompt: [ProjectOAuth2GooglePrompt.SelectAccount],
    enabled: true
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Enums\ProjectOAuth2GooglePrompt;
use Appwrite\Services\Project;

$client = new Client();

$client
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$project = new Project($client);

$result = $project->updateOAuth2Google(
    clientId: '<CLIENT_ID>',
    clientSecret: '<CLIENT_SECRET>',
    prompt: [ProjectOAuth2GooglePrompt::SELECTACCOUNT()],
    enabled: true
);
```
```server-python
from appwrite.client import Client
from appwrite.enums import ProjectOAuth2GooglePrompt
from appwrite.services.project import Project

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

project = Project(client)

result = project.update_o_auth2_google(
    client_id = '<CLIENT_ID>',
    client_secret = '<CLIENT_SECRET>',
    prompt = [ProjectOAuth2GooglePrompt.SELECT_ACCOUNT],
    enabled = True
)
```
```server-ruby
require 'appwrite'

include Appwrite
include Appwrite::Enums

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

project = Project.new(client)

response = project.update_o_auth2_google(
    client_id: '<CLIENT_ID>',
    client_secret: '<CLIENT_SECRET>',
    prompt: [ProjectOAuth2GooglePrompt::SELECT_ACCOUNT],
    enabled: true
)
```
```server-dotnet
using Appwrite;
using Appwrite.Enums;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Project project = new Project(client);

var result = await project.UpdateOAuth2Google(
    clientId: "<CLIENT_ID>",
    clientSecret: "<CLIENT_SECRET>",
    prompt: new List<ProjectOAuth2GooglePrompt> {ProjectOAuth2GooglePrompt.SelectAccount},
    enabled: true
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';
import 'package:dart_appwrite/enums.dart' as enums;

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Project project = Project(client);

final result = await project.updateOAuth2Google(
    clientId: '<CLIENT_ID>',
    clientSecret: '<CLIENT_SECRET>',
    prompt: [enums.ProjectOAuth2GooglePrompt.selectAccount],
    enabled: true,
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.enums.ProjectOAuth2GooglePrompt
import io.appwrite.services.Project

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val project = Project(client)

val response = project.updateOAuth2Google(
    clientId = "<CLIENT_ID>",
    clientSecret = "<CLIENT_SECRET>",
    prompt = listOf(ProjectOAuth2GooglePrompt.SELECT_ACCOUNT),
    enabled = true
)
```
```server-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.enums.ProjectOAuth2GooglePrompt;
import io.appwrite.services.Project;

Client client = new Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>");

Project project = new Project(client);

project.updateOAuth2Google(
    "<CLIENT_ID>", // clientId
    "<CLIENT_SECRET>", // clientSecret
    listOf(ProjectOAuth2GooglePrompt.SELECT_ACCOUNT), // prompt
    true, // enabled
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }
        System.out.println(result);
    })
);
```
```server-swift
import Appwrite
import AppwriteEnums

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let project = Project(client)

let result = try await project.updateOAuth2Google(
    clientId: "<CLIENT_ID>",
    clientSecret: "<CLIENT_SECRET>",
    prompt: [.selectAccount],
    enabled: true
)
```
```server-go
package main

import (
    "fmt"
    "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("<YOUR_API_KEY>"),
    )

    service := appwrite.NewProject(client)
    result, err := service.UpdateOAuth2Google(
        service.WithUpdateOAuth2GoogleClientId("<CLIENT_ID>"),
        service.WithUpdateOAuth2GoogleClientSecret("<CLIENT_SECRET>"),
        service.WithUpdateOAuth2GooglePrompt([]string{"select_account"}),
        service.WithUpdateOAuth2GoogleEnabled(true),
    )

    if err != nil {
        panic(err)
    }

    fmt.Println(result)
}
```
```server-rust
use appwrite::Client;
use appwrite::enums::ProjectOAuth2GooglePrompt;
use appwrite::services::project::Project;

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

    let project = Project::new(&client);

    let result = project.update_o_auth2_google(
        Some("<CLIENT_ID>"),
        Some("<CLIENT_SECRET>"),
        Some(vec![ProjectOAuth2GooglePrompt::SelectAccount]),
        Some(true),
    ).await?;

    println!("{:?}", result);
    Ok(())
}
```
```bash
appwrite project update-o-auth-2-google \
    --client-id "<CLIENT_ID>" \
    --client-secret "<CLIENT_SECRET>" \
    --prompt "select_account" \
    --enabled true
```

### Available providers

Every provider has its own `updateOAuth2<Provider>` method. The credential field names differ per provider, use the columns below to map the provider's credentials to the method's parameters.

| Provider | SDK method | App ID field | App secret field | Other fields |
| --- | --- | --- | --- | --- |
| Amazon | `updateOAuth2Amazon` | `clientId` | `clientSecret` | |
| Apple | `updateOAuth2Apple` | `serviceId` | `keyId` | `teamId` |
| Auth0 | `updateOAuth2Auth0` | `clientId` | `clientSecret` | `endpoint` |
| Authentik | `updateOAuth2Authentik` | `clientId` | `clientSecret` | `endpoint` |
| Autodesk | `updateOAuth2Autodesk` | `clientId` | `clientSecret` | |
| Bitbucket | `updateOAuth2Bitbucket` | `key` | `secret` | |
| Bitly | `updateOAuth2Bitly` | `clientId` | `clientSecret` | |
| Box | `updateOAuth2Box` | `clientId` | `clientSecret` | |
| Dailymotion | `updateOAuth2Dailymotion` | `apiKey` | `apiSecret` | |
| Discord | `updateOAuth2Discord` | `clientId` | `clientSecret` | |
| Disqus | `updateOAuth2Disqus` | `publicKey` | `secretKey` | |
| Dropbox | `updateOAuth2Dropbox` | `appKey` | `appSecret` | |
| Etsy | `updateOAuth2Etsy` | `keyString` | `sharedSecret` | |
| Facebook | `updateOAuth2Facebook` | `appId` | `appSecret` | |
| Figma | `updateOAuth2Figma` | `clientId` | `clientSecret` | |
| FusionAuth | `updateOAuth2FusionAuth` | `clientId` | `clientSecret` | `endpoint` |
| GitHub | `updateOAuth2GitHub` | `clientId` | `clientSecret` | |
| GitLab | `updateOAuth2Gitlab` | `applicationId` | `secret` | `endpoint` |
| Google | `updateOAuth2Google` | `clientId` | `clientSecret` | `prompt` |
| Keycloak | `updateOAuth2Keycloak` | `clientId` | `clientSecret` | `endpoint`, `realmName` |
| Kick | `updateOAuth2Kick` | `clientId` | `clientSecret` | |
| LinkedIn | `updateOAuth2Linkedin` | `clientId` | `primaryClientSecret` | |
| Microsoft | `updateOAuth2Microsoft` | `applicationId` | `applicationSecret` | `tenant` |
| Notion | `updateOAuth2Notion` | `oauthClientId` | `oauthClientSecret` | |
| OIDC | `updateOAuth2Oidc` | `clientId` | `clientSecret` | `wellKnownURL`, `authorizationURL`, `tokenURL`, `userInfoURL` |
| Okta | `updateOAuth2Okta` | `clientId` | `clientSecret` | `domain`, `authorizationServerId` |
| PayPal | `updateOAuth2Paypal` | `clientId` | `secretKey` | |
| PayPal Sandbox | `updateOAuth2PaypalSandbox` | `clientId` | `secretKey` | |
| Podio | `updateOAuth2Podio` | `clientId` | `clientSecret` | |
| Salesforce | `updateOAuth2Salesforce` | `customerKey` | `customerSecret` | |
| Slack | `updateOAuth2Slack` | `clientId` | `clientSecret` | |
| Spotify | `updateOAuth2Spotify` | `clientId` | `clientSecret` | |
| Stripe | `updateOAuth2Stripe` | `clientId` | `apiSecretKey` | |
| Tradeshift | `updateOAuth2Tradeshift` | | | `enabled` only |
| Tradeshift Sandbox | `updateOAuth2TradeshiftSandbox` | | | `enabled` only |
| Twitch | `updateOAuth2Twitch` | `clientId` | `clientSecret` | |
| WordPress | `updateOAuth2WordPress` | `clientId` | `clientSecret` | |
| X | `updateOAuth2X` | `customerKey` | `secretKey` | |
| Yahoo | `updateOAuth2Yahoo` | `clientId` | `clientSecret` | |
| Yandex | `updateOAuth2Yandex` | `clientId` | `clientSecret` | |
| Zoho | `updateOAuth2Zoho` | `clientId` | `clientSecret` | |
| Zoom | `updateOAuth2Zoom` | `clientId` | `clientSecret` | |

**Apple is in beta**

The Apple provider is currently in beta.

### List providers

List every OAuth2 provider and its current configuration state.

```server-nodejs
import { Client, Project } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.listOAuth2Providers({
    queries: [],
    total: false
});
```
```server-deno
import { Client, Project } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.listOAuth2Providers({
    queries: [],
    total: false
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Project;

$client = new Client();

$client
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$project = new Project($client);

$result = $project->listOAuth2Providers(
    queries: [],
    total: false
);
```
```server-python
from appwrite.client import Client
from appwrite.services.project import Project

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

project = Project(client)

result = project.list_o_auth2_providers(
    queries = [],
    total = False
)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

project = Project.new(client)

response = project.list_o_auth2_providers(
    queries: [],
    total: false
)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Project project = new Project(client);

var result = await project.ListOAuth2Providers(
    queries: new List<string>(),
    total: false
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Project project = Project(client);

final result = await project.listOAuth2Providers(
    queries: [],
    total: false,
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Project

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val project = Project(client)

val response = project.listOAuth2Providers(
    queries = listOf(),
    total = false
)
```
```server-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Project;

Client client = new Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>");

Project project = new Project(client);

project.listOAuth2Providers(
    List.of(), // queries
    false, // total
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }
        System.out.println(result);
    })
);
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let project = Project(client)

let result = try await project.listOAuth2Providers(
    queries: [],
    total: false
)
```
```server-go
package main

import (
    "fmt"
    "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("<YOUR_API_KEY>"),
    )

    service := appwrite.NewProject(client)
    result, err := service.ListOAuth2Providers(
        service.WithListOAuth2ProvidersQueries([]string{}),
        service.WithListOAuth2ProvidersTotal(false),
    )

    if err != nil {
        panic(err)
    }

    fmt.Println(result)
}
```
```server-rust
use appwrite::Client;
use appwrite::services::project::Project;

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

    let project = Project::new(&client);

    let result = project.list_o_auth2_providers(
        Some(vec![]),
        Some(false),
    ).await?;

    println!("{:?}", result);
    Ok(())
}
```
```bash
appwrite project list-o-auth-2-providers \
    --limit 25
```

### Get a provider

Fetch a single provider's configuration by its provider ID.

```server-nodejs
import { Client, Project, ProjectOAuthProviderId } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.getOAuth2Provider({
    providerId: ProjectOAuthProviderId.Github
});
```
```server-deno
import { Client, Project, ProjectOAuthProviderId } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.getOAuth2Provider({
    providerId: ProjectOAuthProviderId.Github
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Enums\ProjectOAuthProviderId;
use Appwrite\Services\Project;

$client = new Client();

$client
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$project = new Project($client);

$result = $project->getOAuth2Provider(
    providerId: ProjectOAuthProviderId::GITHUB()
);
```
```server-python
from appwrite.client import Client
from appwrite.enums import ProjectOAuthProviderId
from appwrite.services.project import Project

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

project = Project(client)

result = project.get_o_auth2_provider(
    provider_id = ProjectOAuthProviderId.GITHUB
)
```
```server-ruby
require 'appwrite'

include Appwrite
include Appwrite::Enums

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

project = Project.new(client)

response = project.get_o_auth2_provider(
    provider_id: ProjectOAuthProviderId::GITHUB
)
```
```server-dotnet
using Appwrite;
using Appwrite.Enums;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Project project = new Project(client);

var result = await project.GetOAuth2Provider(
    providerId: ProjectOAuthProviderId.Github
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';
import 'package:dart_appwrite/enums.dart' as enums;

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Project project = Project(client);

final result = await project.getOAuth2Provider(
    providerId: enums.ProjectOAuthProviderId.github,
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.enums.ProjectOAuthProviderId
import io.appwrite.services.Project

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val project = Project(client)

val response = project.getOAuth2Provider(
    providerId = ProjectOAuthProviderId.GITHUB
)
```
```server-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.enums.ProjectOAuthProviderId;
import io.appwrite.services.Project;

Client client = new Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>");

Project project = new Project(client);

project.getOAuth2Provider(
    ProjectOAuthProviderId.GITHUB, // providerId
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }
        System.out.println(result);
    })
);
```
```server-swift
import Appwrite
import AppwriteEnums

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let project = Project(client)

let result = try await project.getOAuth2Provider(
    providerId: .github
)
```
```server-go
package main

import (
    "fmt"
    "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("<YOUR_API_KEY>"),
    )

    service := appwrite.NewProject(client)
    result, err := service.GetOAuth2Provider(
        "github",
    )

    if err != nil {
        panic(err)
    }

    fmt.Println(result)
}
```
```server-rust
use appwrite::Client;
use appwrite::enums::ProjectOAuthProviderId;
use appwrite::services::project::Project;

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

    let project = Project::new(&client);

    let result = project.get_o_auth2_provider(
        ProjectOAuthProviderId::Github,
    ).await?;

    println!("{:?}", result);
    Ok(())
}
```
```bash
appwrite project get-o-auth-2-provider \
    --provider-id github
```

### Benefits

- **Repeatable provisioning.** Script the full set of OAuth2 providers a project needs and recreate it on demand, without clicking through the Console.
- **Environment parity.** Keep dev, staging, and production projects in sync by running the same configuration script against each one.
- **Server-only secrets.** Client secrets are write-only over the API and never returned in responses, so configuration scripts can run in CI without exposing them.

---

## Platforms

URL: https://appwrite.io/docs/partners/project/platforms

Each Appwrite project has a list of registered platforms. A platform identifies a client application that is allowed to talk to your project's API: a Web platform pins an allowed hostname for CORS, while Apple, Android, Windows, and Linux platforms pin a bundle, package, or application ID for native clients.

Platforms can be added from the Appwrite Console, or programmatically through any server SDK using the Project service.

### Manage from the Console

![Project platforms in the Appwrite Console](/images/docs/project/platforms.avif)

To add a platform manually:

1. Open your project in the Appwrite Console.
2. Open **Apps** in the project sidebar and click **Add app**.
3. Under **Choose platform**, pick the client stack: Web, Android, Apple, Flutter, React Native, Windows, or Linux.
4. Under **App details**, enter a display name and the platform's identifier (hostname for Web, bundle or package ID for native).
5. To rename or remove an app later, open it from the **Apps** list.

### Platform types

The Project service exposes a dedicated create method per platform type. Each method takes a unique `platformId`, a display `name`, and the platform's identifier:

| Type | Method | Identifier param |
| --- | --- | --- |
| Web | `createWebPlatform` | `hostname` (e.g. `app.example.com`) |
| Apple | `createApplePlatform` | `bundleIdentifier` (e.g. `com.example.app`) |
| Android | `createAndroidPlatform` | `applicationId` (e.g. `com.example.app`) |
| Windows | `createWindowsPlatform` | `packageIdentifierName` |
| Linux | `createLinuxPlatform` | `packageName` |

Flutter and React Native apps shown in the Console map to the underlying Apple, Android, or Web type based on the target you pick.

### Register a platform

The example below registers a Web platform. Swap the method name and identifier param to register Apple, Android, Windows, or Linux platforms.

**Required scopes**

The API key used for these calls needs `platforms.write` to create, update, or delete platforms, and `platforms.read` to list or fetch them.

```server-nodejs
import { Client, Project, ID } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.createWebPlatform({
    platformId: ID.unique(),
    name: 'My Web App',
    hostname: 'app.example.com'
});
```
```server-deno
import { Client, Project, ID } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.createWebPlatform({
    platformId: ID.unique(),
    name: 'My Web App',
    hostname: 'app.example.com'
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\ID;
use Appwrite\Services\Project;

$client = new Client();

$client
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$project = new Project($client);

$result = $project->createWebPlatform(
    platformId: ID::unique(),
    name: 'My Web App',
    hostname: 'app.example.com'
);
```
```server-python
from appwrite.client import Client
from appwrite.id import ID
from appwrite.services.project import Project

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

project = Project(client)

result = project.create_web_platform(
    platform_id = ID.unique(),
    name = 'My Web App',
    hostname = 'app.example.com'
)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

project = Project.new(client)

response = project.create_web_platform(
    platform_id: ID.unique(),
    name: 'My Web App',
    hostname: 'app.example.com'
)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Project project = new Project(client);

var result = await project.CreateWebPlatform(
    platformId: ID.Unique(),
    name: "My Web App",
    hostname: "app.example.com"
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Project project = Project(client);

final result = await project.createWebPlatform(
    platformId: ID.unique(),
    name: 'My Web App',
    hostname: 'app.example.com',
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.ID
import io.appwrite.services.Project

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val project = Project(client)

val response = project.createWebPlatform(
    platformId = ID.unique(),
    name = "My Web App",
    hostname = "app.example.com"
)
```
```server-java
import io.appwrite.Client;
import io.appwrite.ID;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Project;

Client client = new Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>");

Project project = new Project(client);

project.createWebPlatform(
    ID.unique(), // platformId
    "My Web App", // name
    "app.example.com", // hostname
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }
        System.out.println(result);
    })
);
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let project = Project(client)

let result = try await project.createWebPlatform(
    platformId: ID.unique(),
    name: "My Web App",
    hostname: "app.example.com"
)
```
```server-go
package main

import (
    "fmt"
    "github.com/appwrite/sdk-for-go/appwrite"
    "github.com/appwrite/sdk-for-go/id"
)

func main() {
    client := appwrite.NewClient(
        appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"),
        appwrite.WithProject("<PROJECT_ID>"),
        appwrite.WithKey("<YOUR_API_KEY>"),
    )

    project := appwrite.NewProject(client)
    result, err := project.CreateWebPlatform(
        id.Unique(),
        "My Web App",
        "app.example.com",
    )

    if err != nil {
        panic(err)
    }

    fmt.Println(result)
}
```
```server-rust
use appwrite::Client;
use appwrite::id::ID;
use appwrite::services::project::Project;

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

    let project = Project::new(&client);

    let result = project.create_web_platform(
        ID::unique(),
        "My Web App",
        "app.example.com",
    ).await?;

    println!("{:?}", result);
    Ok(())
}
```
```bash
appwrite project create-web-platform \
    --platform-id 'unique()' \
    --name "My Web App" \
    --hostname app.example.com
```

### List platforms

```server-nodejs
import { Client, Project } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.listPlatforms({
    queries: [],
    total: false
});
```
```server-deno
import { Client, Project } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.listPlatforms({
    queries: [],
    total: false
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Project;

$client = new Client();

$client
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$project = new Project($client);

$result = $project->listPlatforms(
    queries: [],
    total: false
);
```
```server-python
from appwrite.client import Client
from appwrite.services.project import Project

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

project = Project(client)

result = project.list_platforms(
    queries = [],
    total = False
)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

project = Project.new(client)

response = project.list_platforms(
    queries: [],
    total: false
)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Project project = new Project(client);

var result = await project.ListPlatforms(
    queries: new List<string>(),
    total: false
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Project project = Project(client);

final result = await project.listPlatforms(
    queries: [],
    total: false,
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Project

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val project = Project(client)

val response = project.listPlatforms(
    queries = listOf(),
    total = false
)
```
```server-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Project;

Client client = new Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>");

Project project = new Project(client);

project.listPlatforms(
    List.of(), // queries
    false, // total
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }
        System.out.println(result);
    })
);
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let project = Project(client)

let result = try await project.listPlatforms(
    queries: [],
    total: false
)
```
```server-go
package main

import (
    "fmt"
    "github.com/appwrite/sdk-for-go/appwrite"
    "github.com/appwrite/sdk-for-go/project"
)

func main() {
    client := appwrite.NewClient(
        appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"),
        appwrite.WithProject("<PROJECT_ID>"),
        appwrite.WithKey("<YOUR_API_KEY>"),
    )

    service := appwrite.NewProject(client)
    result, err := service.ListPlatforms(
        project.WithListPlatformsQueries([]interface{}{}),
        project.WithListPlatformsTotal(false),
    )

    if err != nil {
        panic(err)
    }

    fmt.Println(result)
}
```
```server-rust
use appwrite::Client;
use appwrite::services::project::Project;

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

    let project = Project::new(&client);

    let result = project.list_platforms(
        Some(vec![]),
        Some(false),
    ).await?;

    println!("{:?}", result);
    Ok(())
}
```
```bash
appwrite project list-platforms \
    --limit 25
```

### Get a platform

Fetch a single platform by its ID. The response includes the platform's type-specific fields, such as `hostname` for Web or `bundleIdentifier` for Apple.

```server-nodejs
import { Client, Project } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.getPlatform({
    platformId: '<PLATFORM_ID>'
});
```
```server-deno
import { Client, Project } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.getPlatform({
    platformId: '<PLATFORM_ID>'
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Project;

$client = new Client();

$client
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$project = new Project($client);

$result = $project->getPlatform(
    platformId: '<PLATFORM_ID>'
);
```
```server-python
from appwrite.client import Client
from appwrite.services.project import Project

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

project = Project(client)

result = project.get_platform(
    platform_id = '<PLATFORM_ID>'
)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

project = Project.new(client)

result = project.get_platform(
    platform_id: '<PLATFORM_ID>'
)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Project project = new Project(client);

var result = await project.GetPlatform(
    platformId: "<PLATFORM_ID>"
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Project project = Project(client);

final result = await project.getPlatform(
    platformId: '<PLATFORM_ID>',
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Project

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val project = Project(client)

val result = project.getPlatform(
    platformId = "<PLATFORM_ID>"
)
```
```server-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Project;

Client client = new Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>");

Project project = new Project(client);

project.getPlatform(
    "<PLATFORM_ID>", // platformId
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }
        System.out.println(result);
    })
);
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let project = Project(client)

let result = try await project.getPlatform(
    platformId: "<PLATFORM_ID>"
)
```
```server-go
package main

import (
    "fmt"
    "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("<YOUR_API_KEY>"),
    )

    service := appwrite.NewProject(client)
    result, err := service.GetPlatform(
        "<PLATFORM_ID>",
    )

    if err != nil {
        panic(err)
    }

    fmt.Println(result)
}
```
```server-rust
use appwrite::Client;
use appwrite::services::project::Project;

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

    let project = Project::new(&client);

    let result = project.get_platform(
        "<PLATFORM_ID>",
    ).await?;

    println!("{:?}", result);
    Ok(())
}
```
```bash
appwrite project get-platform \
    --platform-id <PLATFORM_ID>
```

### Update a platform

Each platform type has a matching update method (`updateWebPlatform`, `updateApplePlatform`, and so on). The example below updates a Web platform's name and hostname.

```server-nodejs
import { Client, Project } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.updateWebPlatform({
    platformId: '<PLATFORM_ID>',
    name: 'Renamed Web App',
    hostname: 'app.example.com'
});
```
```server-deno
import { Client, Project } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.updateWebPlatform({
    platformId: '<PLATFORM_ID>',
    name: 'Renamed Web App',
    hostname: 'app.example.com'
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Project;

$client = new Client();

$client
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$project = new Project($client);

$result = $project->updateWebPlatform(
    platformId: '<PLATFORM_ID>',
    name: 'Renamed Web App',
    hostname: 'app.example.com'
);
```
```server-python
from appwrite.client import Client
from appwrite.services.project import Project

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

project = Project(client)

result = project.update_web_platform(
    platform_id = '<PLATFORM_ID>',
    name = 'Renamed Web App',
    hostname = 'app.example.com'
)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

project = Project.new(client)

response = project.update_web_platform(
    platform_id: '<PLATFORM_ID>',
    name: 'Renamed Web App',
    hostname: 'app.example.com'
)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Project project = new Project(client);

var result = await project.UpdateWebPlatform(
    platformId: "<PLATFORM_ID>",
    name: "Renamed Web App",
    hostname: "app.example.com"
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Project project = Project(client);

final result = await project.updateWebPlatform(
    platformId: '<PLATFORM_ID>',
    name: 'Renamed Web App',
    hostname: 'app.example.com',
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Project

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val project = Project(client)

val response = project.updateWebPlatform(
    platformId = "<PLATFORM_ID>",
    name = "Renamed Web App",
    hostname = "app.example.com"
)
```
```server-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Project;

Client client = new Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>");

Project project = new Project(client);

project.updateWebPlatform(
    "<PLATFORM_ID>", // platformId
    "Renamed Web App", // name
    "app.example.com", // hostname
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }
        System.out.println(result);
    })
);
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let project = Project(client)

let result = try await project.updateWebPlatform(
    platformId: "<PLATFORM_ID>",
    name: "Renamed Web App",
    hostname: "app.example.com"
)
```
```server-go
package main

import (
    "fmt"
    "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("<YOUR_API_KEY>"),
    )

    service := appwrite.NewProject(client)
    result, err := service.UpdateWebPlatform(
        "<PLATFORM_ID>",
        "Renamed Web App",
        "app.example.com",
    )

    if err != nil {
        panic(err)
    }

    fmt.Println(result)
}
```
```server-rust
use appwrite::Client;
use appwrite::services::project::Project;

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

    let project = Project::new(&client);

    let result = project.update_web_platform(
        "<PLATFORM_ID>",
        "Renamed Web App",
        "app.example.com",
    ).await?;

    println!("{:?}", result);
    Ok(())
}
```
```bash
appwrite project update-web-platform \
    --platform-id <PLATFORM_ID> \
    --name "Renamed Web App" \
    --hostname app.example.com
```

### Delete a platform

```server-nodejs
import { Client, Project } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

await project.deletePlatform({
    platformId: '<PLATFORM_ID>'
});
```
```server-deno
import { Client, Project } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

await project.deletePlatform({
    platformId: '<PLATFORM_ID>'
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Project;

$client = new Client();

$client
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$project = new Project($client);

$project->deletePlatform(
    platformId: '<PLATFORM_ID>'
);
```
```server-python
from appwrite.client import Client
from appwrite.services.project import Project

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

project = Project(client)

project.delete_platform(
    platform_id = '<PLATFORM_ID>'
)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

project = Project.new(client)

project.delete_platform(
    platform_id: '<PLATFORM_ID>'
)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Project project = new Project(client);

await project.DeletePlatform(
    platformId: "<PLATFORM_ID>"
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Project project = Project(client);

await project.deletePlatform(
    platformId: '<PLATFORM_ID>',
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Project

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val project = Project(client)

project.deletePlatform(
    platformId = "<PLATFORM_ID>"
)
```
```server-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Project;

Client client = new Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>");

Project project = new Project(client);

project.deletePlatform(
    "<PLATFORM_ID>", // platformId
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }
        System.out.println(result);
    })
);
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let project = Project(client)

try await project.deletePlatform(
    platformId: "<PLATFORM_ID>"
)
```
```server-go
package main

import (
    "fmt"
    "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("<YOUR_API_KEY>"),
    )

    service := appwrite.NewProject(client)
    result, err := service.DeletePlatform(
        "<PLATFORM_ID>",
    )

    if err != nil {
        panic(err)
    }

    fmt.Println(result)
}
```
```server-rust
use appwrite::Client;
use appwrite::services::project::Project;

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

    let project = Project::new(&client);

    project.delete_platform(
        "<PLATFORM_ID>",
    ).await?;

    Ok(())
}
```
```bash
appwrite project delete-platform \
    --platform-id <PLATFORM_ID>
```

### Benefits

- **Repeatable provisioning.** Script the full set of platforms a project needs and recreate it on demand, without clicking through the Console.
- **Environment parity.** Keep dev, staging, and production projects in sync by running the same script against each one.
- **CI and automation.** Add or rotate platforms from a CI job when a new preview environment spins up or a domain changes.

---

## Policies

URL: https://appwrite.io/docs/partners/project/policies

Project policies control how users authenticate, how long their sessions live, how many users can sign up, and what team members can see about each other. Each policy is an independent toggle on the project.

Policies can be configured from the Appwrite Console, or programmatically through any server SDK using the Project service.

### Manage from the Console

![Project policies in the Appwrite Console](/images/docs/project/policies.avif)

To configure policies manually:

1. Open your project in the Appwrite Console.
2. Navigate to **Auth** in the sidebar, then open the **Policies** tab.
3. Pick the group that holds the policy: **Sessions**, **Users**, **Emails**, **Memberships**, or **Passwords**.
4. Adjust the policy you want, then click **Update** on its card.

### Available policies

| Policy ID | SDK method | Body |
| --- | --- | --- |
| `password-dictionary` | `updatePasswordDictionaryPolicy` | `enabled` |
| `password-history` | `updatePasswordHistoryPolicy` | `total` (1–`APP_LIMIT_COUNT`, or `null`) |
| `password-strength` | `updatePasswordStrengthPolicy` | `min` (8–256, default 8), `uppercase`, `lowercase`, `number`, `symbols` (all optional) |
| `password-personal-data` | `updatePasswordPersonalDataPolicy` | `enabled` |
| `session-alert` | `updateSessionAlertPolicy` | `enabled` |
| `session-duration` | `updateSessionDurationPolicy` | `duration` (5–31536000 seconds) |
| `session-invalidation` | `updateSessionInvalidationPolicy` | `enabled` |
| `session-limit` | `updateSessionLimitPolicy` | `total` (1–`APP_LIMIT_COUNT`, or `null`) |
| `user-limit` | `updateUserLimitPolicy` | `total` (1–`APP_LIMIT_COUNT`, or `null`) |
| `membership-privacy` | `updateMembershipPrivacyPolicy` | `userId`, `userEmail`, `userPhone`, `userName`, `userMFA` (all bool, all optional) |
| `deny-aliased-email` | `updateDenyAliasedEmailPolicy` | `enabled` |
| `deny-disposable-email` | `updateDenyDisposableEmailPolicy` | `enabled` |
| `deny-free-email` | `updateDenyFreeEmailPolicy` | `enabled` |

**Required scopes**

The API key used for these calls needs `project.policies.read` to list or fetch policies, and `project.policies.write` to update them.

### List policies

List all policies configured for the project along with their current state.

```server-nodejs
import { Client, Project } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.listPolicies({
    queries: [],
    total: false
});
```
```server-deno
import { Client, Project } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.listPolicies({
    queries: [],
    total: false
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Project;

$client = new Client();

$client
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$project = new Project($client);

$result = $project->listPolicies(
    queries: [],
    total: false
);
```
```server-python
from appwrite.client import Client
from appwrite.services.project import Project

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

project = Project(client)

result = project.list_policies(
    queries = [],
    total = False
)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

project = Project.new(client)

response = project.list_policies(
    queries: [],
    total: false
)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Project project = new Project(client);

var result = await project.ListPolicies(
    queries: new List<string>(),
    total: false
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Project project = Project(client);

final result = await project.listPolicies(
    queries: [],
    total: false,
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Project

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val project = Project(client)

val response = project.listPolicies(
    queries = listOf(),
    total = false
)
```
```server-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Project;

Client client = new Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>");

Project project = new Project(client);

project.listPolicies(
    List.of(), // queries
    false, // total
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }
        System.out.println(result);
    })
);
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let project = Project(client)

let result = try await project.listPolicies(
    queries: [],
    total: false
)
```
```server-go
package main

import (
    "fmt"
    "github.com/appwrite/sdk-for-go/appwrite"
    "github.com/appwrite/sdk-for-go/project"
)

func main() {
    client := appwrite.NewClient(
        appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"),
        appwrite.WithProject("<PROJECT_ID>"),
        appwrite.WithKey("<YOUR_API_KEY>"),
    )

    service := appwrite.NewProject(client)
    result, err := service.ListPolicies(
        project.WithListPoliciesQueries([]interface{}{}),
        project.WithListPoliciesTotal(false),
    )

    if err != nil {
        panic(err)
    }

    fmt.Println(result)
}
```
```server-rust
use appwrite::Client;
use appwrite::services::project::Project;

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

    let project = Project::new(&client);

    let result = project.list_policies(
        Some(vec![]),
        Some(false),
    ).await?;

    println!("{:?}", result);
    Ok(())
}
```
```bash
appwrite project list-policies \
    --limit 25
```

### Get a policy

Fetch a single policy by its ID. The response fields depend on the policy type, matching the [Available policies](#available-policies) table above.

```server-nodejs
import { Client, Project, ProjectPolicyId } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.getPolicy({
    policyId: ProjectPolicyId.Passworddictionary
});
```
```server-deno
import { Client, Project, ProjectPolicyId } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.getPolicy({
    policyId: ProjectPolicyId.Passworddictionary
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Enums\ProjectPolicyId;
use Appwrite\Services\Project;

$client = new Client();

$client
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$project = new Project($client);

$result = $project->getPolicy(
    policyId: ProjectPolicyId::PASSWORDDICTIONARY()
);
```
```server-python
from appwrite.client import Client
from appwrite.enums import ProjectPolicyId
from appwrite.services.project import Project

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

project = Project(client)

result = project.get_policy(
    policy_id = ProjectPolicyId.PASSWORD_DICTIONARY
)
```
```server-ruby
require 'appwrite'

include Appwrite
include Appwrite::Enums

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

project = Project.new(client)

response = project.get_policy(
    policy_id: ProjectPolicyId::PASSWORD_DICTIONARY
)
```
```server-dotnet
using Appwrite;
using Appwrite.Enums;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Project project = new Project(client);

var result = await project.GetPolicy(
    policyId: ProjectPolicyId.PasswordDictionary
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';
import 'package:dart_appwrite/enums.dart' as enums;

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Project project = Project(client);

final result = await project.getPolicy(
    policyId: enums.ProjectPolicyId.passwordDictionary,
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.enums.ProjectPolicyId
import io.appwrite.services.Project

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val project = Project(client)

val response = project.getPolicy(
    policyId = ProjectPolicyId.PASSWORD_DICTIONARY
)
```
```server-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.enums.ProjectPolicyId;
import io.appwrite.services.Project;

Client client = new Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>");

Project project = new Project(client);

project.getPolicy(
    ProjectPolicyId.PASSWORD_DICTIONARY, // policyId
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }
        System.out.println(result);
    })
);
```
```server-swift
import Appwrite
import AppwriteEnums

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let project = Project(client)

let result = try await project.getPolicy(
    policyId: .passwordDictionary
)
```
```server-go
package main

import (
    "fmt"
    "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("<YOUR_API_KEY>"),
    )

    service := appwrite.NewProject(client)
    result, err := service.GetPolicy(
        "password-dictionary",
    )

    if err != nil {
        panic(err)
    }

    fmt.Println(result)
}
```
```server-rust
use appwrite::Client;
use appwrite::enums::ProjectPolicyId;
use appwrite::services::project::Project;

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

    let project = Project::new(&client);

    let result = project.get_policy(
        ProjectPolicyId::PasswordDictionary,
    ).await?;

    println!("{:?}", result);
    Ok(())
}
```
```bash
appwrite project get-policy \
    --policy-id password-dictionary
```

### Update password dictionary policy

When enabled, new passwords are checked against a dictionary of common passwords and rejected if they match.

```server-nodejs
import { Client, Project } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.updatePasswordDictionaryPolicy({
    enabled: true
});
```
```server-deno
import { Client, Project } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.updatePasswordDictionaryPolicy({
    enabled: true
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Project;

$client = new Client();

$client
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$project = new Project($client);

$result = $project->updatePasswordDictionaryPolicy(
    enabled: true
);
```
```server-python
from appwrite.client import Client
from appwrite.services.project import Project

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

project = Project(client)

result = project.update_password_dictionary_policy(
    enabled = True
)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

project = Project.new(client)

response = project.update_password_dictionary_policy(
    enabled: true
)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Project project = new Project(client);

var result = await project.UpdatePasswordDictionaryPolicy(
    enabled: true
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Project project = Project(client);

final result = await project.updatePasswordDictionaryPolicy(
    enabled: true,
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Project

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val project = Project(client)

val response = project.updatePasswordDictionaryPolicy(
    enabled = true
)
```
```server-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Project;

Client client = new Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>");

Project project = new Project(client);

project.updatePasswordDictionaryPolicy(
    true, // enabled
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }
        System.out.println(result);
    })
);
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let project = Project(client)

let result = try await project.updatePasswordDictionaryPolicy(
    enabled: true
)
```
```server-go
package main

import (
    "fmt"
    "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("<YOUR_API_KEY>"),
    )

    service := appwrite.NewProject(client)
    result, err := service.UpdatePasswordDictionaryPolicy(true)

    if err != nil {
        panic(err)
    }

    fmt.Println(result)
}
```
```server-rust
use appwrite::Client;
use appwrite::services::project::Project;

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

    let project = Project::new(&client);

    let result = project.update_password_dictionary_policy(true).await?;

    println!("{:?}", result);
    Ok(())
}
```
```bash
appwrite project update-password-dictionary-policy \
    --enabled true
```

### Update password history policy

Stores the last `total` password hashes per user and rejects new passwords that match. Pass `null` to disable.

```server-nodejs
import { Client, Project } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.updatePasswordHistoryPolicy({
    total: 5
});
```
```server-deno
import { Client, Project } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.updatePasswordHistoryPolicy({
    total: 5
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Project;

$client = new Client();

$client
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$project = new Project($client);

$result = $project->updatePasswordHistoryPolicy(
    total: 5
);
```
```server-python
from appwrite.client import Client
from appwrite.services.project import Project

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

project = Project(client)

result = project.update_password_history_policy(
    total = 5
)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

project = Project.new(client)

response = project.update_password_history_policy(
    total: 5
)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Project project = new Project(client);

var result = await project.UpdatePasswordHistoryPolicy(
    total: 5
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Project project = Project(client);

final result = await project.updatePasswordHistoryPolicy(
    total: 5,
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Project

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val project = Project(client)

val response = project.updatePasswordHistoryPolicy(
    total = 5
)
```
```server-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Project;

Client client = new Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>");

Project project = new Project(client);

project.updatePasswordHistoryPolicy(
    5, // total
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }
        System.out.println(result);
    })
);
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let project = Project(client)

let result = try await project.updatePasswordHistoryPolicy(
    total: 5
)
```
```server-go
package main

import (
    "fmt"
    "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("<YOUR_API_KEY>"),
    )

    service := appwrite.NewProject(client)
    result, err := service.UpdatePasswordHistoryPolicy(5)

    if err != nil {
        panic(err)
    }

    fmt.Println(result)
}
```
```server-rust
use appwrite::Client;
use appwrite::services::project::Project;

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

    let project = Project::new(&client);

    let result = project.update_password_history_policy(5).await?;

    println!("{:?}", result);
    Ok(())
}
```
```bash
appwrite project update-password-history-policy \
    --total 5
```

### Update password strength policy

Set the minimum password length and which character types new passwords must contain. Each field is optional, and any field you omit keeps its current value.

```server-nodejs
import { Client, Project } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.updatePasswordStrengthPolicy({
    min: 8,
    uppercase: true,
    lowercase: true,
    number: true,
    symbols: true
});
```
```server-deno
import { Client, Project } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.updatePasswordStrengthPolicy({
    min: 8,
    uppercase: true,
    lowercase: true,
    number: true,
    symbols: true
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Project;

$client = new Client();

$client
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$project = new Project($client);

$result = $project->updatePasswordStrengthPolicy(
    min: 8,
    uppercase: true,
    lowercase: true,
    number: true,
    symbols: true
);
```
```server-python
from appwrite.client import Client
from appwrite.services.project import Project

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

project = Project(client)

result = project.update_password_strength_policy(
    min = 8,
    uppercase = True,
    lowercase = True,
    number = True,
    symbols = True
)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

project = Project.new(client)

response = project.update_password_strength_policy(
    min: 8,
    uppercase: true,
    lowercase: true,
    number: true,
    symbols: true
)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Project project = new Project(client);

var result = await project.UpdatePasswordStrengthPolicy(
    min: 8,
    uppercase: true,
    lowercase: true,
    number: true,
    symbols: true
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Project project = Project(client);

final result = await project.updatePasswordStrengthPolicy(
    min: 8,
    uppercase: true,
    lowercase: true,
    number: true,
    symbols: true,
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Project

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val project = Project(client)

val response = project.updatePasswordStrengthPolicy(
    min = 8,
    uppercase = true,
    lowercase = true,
    number = true,
    symbols = true
)
```
```server-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Project;

Client client = new Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>");

Project project = new Project(client);

project.updatePasswordStrengthPolicy(
    8, // min
    true, // uppercase
    true, // lowercase
    true, // number
    true, // symbols
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }
        System.out.println(result);
    })
);
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let project = Project(client)

let result = try await project.updatePasswordStrengthPolicy(
    min: 8,
    uppercase: true,
    lowercase: true,
    number: true,
    symbols: true
)
```
```server-go
package main

import (
    "fmt"
    "github.com/appwrite/sdk-for-go/appwrite"
    "github.com/appwrite/sdk-for-go/project"
)

func main() {
    client := appwrite.NewClient(
        appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"),
        appwrite.WithProject("<PROJECT_ID>"),
        appwrite.WithKey("<YOUR_API_KEY>"),
    )

    service := appwrite.NewProject(client)
    result, err := service.UpdatePasswordStrengthPolicy(
        project.WithUpdatePasswordStrengthPolicyMin(8),
        project.WithUpdatePasswordStrengthPolicyUppercase(true),
        project.WithUpdatePasswordStrengthPolicyLowercase(true),
        project.WithUpdatePasswordStrengthPolicyNumber(true),
        project.WithUpdatePasswordStrengthPolicySymbols(true),
    )

    if err != nil {
        panic(err)
    }

    fmt.Println(result)
}
```
```server-rust
use appwrite::Client;
use appwrite::services::project::Project;

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

    let project = Project::new(&client);

    let result = project.update_password_strength_policy(
        Some(8),
        Some(true),
        Some(true),
        Some(true),
        Some(true),
    ).await?;

    println!("{:?}", result);
    Ok(())
}
```
```bash
appwrite project update-password-strength-policy \
    --min 8 \
    --uppercase true \
    --lowercase true \
    --number true \
    --symbols true
```

### Update password personal data policy

When enabled, new passwords are rejected if they contain the user's ID, name, email, or phone number.

```server-nodejs
import { Client, Project } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.updatePasswordPersonalDataPolicy({
    enabled: true
});
```
```server-deno
import { Client, Project } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.updatePasswordPersonalDataPolicy({
    enabled: true
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Project;

$client = new Client();

$client
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$project = new Project($client);

$result = $project->updatePasswordPersonalDataPolicy(
    enabled: true
);
```
```server-python
from appwrite.client import Client
from appwrite.services.project import Project

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

project = Project(client)

result = project.update_password_personal_data_policy(
    enabled = True
)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

project = Project.new(client)

response = project.update_password_personal_data_policy(
    enabled: true
)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Project project = new Project(client);

var result = await project.UpdatePasswordPersonalDataPolicy(
    enabled: true
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Project project = Project(client);

final result = await project.updatePasswordPersonalDataPolicy(
    enabled: true,
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Project

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val project = Project(client)

val response = project.updatePasswordPersonalDataPolicy(
    enabled = true
)
```
```server-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Project;

Client client = new Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>");

Project project = new Project(client);

project.updatePasswordPersonalDataPolicy(
    true, // enabled
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }
        System.out.println(result);
    })
);
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let project = Project(client)

let result = try await project.updatePasswordPersonalDataPolicy(
    enabled: true
)
```
```server-go
package main

import (
    "fmt"
    "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("<YOUR_API_KEY>"),
    )

    service := appwrite.NewProject(client)
    result, err := service.UpdatePasswordPersonalDataPolicy(true)

    if err != nil {
        panic(err)
    }

    fmt.Println(result)
}
```
```server-rust
use appwrite::Client;
use appwrite::services::project::Project;

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

    let project = Project::new(&client);

    let result = project.update_password_personal_data_policy(true).await?;

    println!("{:?}", result);
    Ok(())
}
```
```bash
appwrite project update-password-personal-data-policy \
    --enabled true
```

### Update session alert policy

When enabled, the user receives an email each time a new session is created. The first session after sign-up does not trigger an alert.

```server-nodejs
import { Client, Project } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.updateSessionAlertPolicy({
    enabled: true
});
```
```server-deno
import { Client, Project } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.updateSessionAlertPolicy({
    enabled: true
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Project;

$client = new Client();

$client
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$project = new Project($client);

$result = $project->updateSessionAlertPolicy(
    enabled: true
);
```
```server-python
from appwrite.client import Client
from appwrite.services.project import Project

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

project = Project(client)

result = project.update_session_alert_policy(
    enabled = True
)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

project = Project.new(client)

response = project.update_session_alert_policy(
    enabled: true
)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Project project = new Project(client);

var result = await project.UpdateSessionAlertPolicy(
    enabled: true
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Project project = Project(client);

final result = await project.updateSessionAlertPolicy(
    enabled: true,
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Project

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val project = Project(client)

val response = project.updateSessionAlertPolicy(
    enabled = true
)
```
```server-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Project;

Client client = new Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>");

Project project = new Project(client);

project.updateSessionAlertPolicy(
    true, // enabled
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }
        System.out.println(result);
    })
);
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let project = Project(client)

let result = try await project.updateSessionAlertPolicy(
    enabled: true
)
```
```server-go
package main

import (
    "fmt"
    "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("<YOUR_API_KEY>"),
    )

    service := appwrite.NewProject(client)
    result, err := service.UpdateSessionAlertPolicy(true)

    if err != nil {
        panic(err)
    }

    fmt.Println(result)
}
```
```server-rust
use appwrite::Client;
use appwrite::services::project::Project;

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

    let project = Project::new(&client);

    let result = project.update_session_alert_policy(true).await?;

    println!("{:?}", result);
    Ok(())
}
```
```bash
appwrite project update-session-alert-policy \
    --enabled true
```

### Update session duration policy

Sets the maximum lifetime of a session in seconds. Valid range is 5 seconds to 31536000 seconds (one year).

```server-nodejs
import { Client, Project } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.updateSessionDurationPolicy({
    duration: 86400
});
```
```server-deno
import { Client, Project } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.updateSessionDurationPolicy({
    duration: 86400
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Project;

$client = new Client();

$client
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$project = new Project($client);

$result = $project->updateSessionDurationPolicy(
    duration: 86400
);
```
```server-python
from appwrite.client import Client
from appwrite.services.project import Project

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

project = Project(client)

result = project.update_session_duration_policy(
    duration = 86400
)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

project = Project.new(client)

response = project.update_session_duration_policy(
    duration: 86400
)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Project project = new Project(client);

var result = await project.UpdateSessionDurationPolicy(
    duration: 86400
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Project project = Project(client);

final result = await project.updateSessionDurationPolicy(
    duration: 86400,
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Project

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val project = Project(client)

val response = project.updateSessionDurationPolicy(
    duration = 86400
)
```
```server-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Project;

Client client = new Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>");

Project project = new Project(client);

project.updateSessionDurationPolicy(
    86400, // duration
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }
        System.out.println(result);
    })
);
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let project = Project(client)

let result = try await project.updateSessionDurationPolicy(
    duration: 86400
)
```
```server-go
package main

import (
    "fmt"
    "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("<YOUR_API_KEY>"),
    )

    service := appwrite.NewProject(client)
    result, err := service.UpdateSessionDurationPolicy(86400)

    if err != nil {
        panic(err)
    }

    fmt.Println(result)
}
```
```server-rust
use appwrite::Client;
use appwrite::services::project::Project;

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

    let project = Project::new(&client);

    let result = project.update_session_duration_policy(86400).await?;

    println!("{:?}", result);
    Ok(())
}
```
```bash
appwrite project update-session-duration-policy \
    --duration 86400
```

### Update session invalidation policy

When enabled, all existing sessions for a user are invalidated when their password is changed.

```server-nodejs
import { Client, Project } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.updateSessionInvalidationPolicy({
    enabled: true
});
```
```server-deno
import { Client, Project } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.updateSessionInvalidationPolicy({
    enabled: true
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Project;

$client = new Client();

$client
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$project = new Project($client);

$result = $project->updateSessionInvalidationPolicy(
    enabled: true
);
```
```server-python
from appwrite.client import Client
from appwrite.services.project import Project

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

project = Project(client)

result = project.update_session_invalidation_policy(
    enabled = True
)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

project = Project.new(client)

response = project.update_session_invalidation_policy(
    enabled: true
)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Project project = new Project(client);

var result = await project.UpdateSessionInvalidationPolicy(
    enabled: true
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Project project = Project(client);

final result = await project.updateSessionInvalidationPolicy(
    enabled: true,
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Project

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val project = Project(client)

val response = project.updateSessionInvalidationPolicy(
    enabled = true
)
```
```server-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Project;

Client client = new Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>");

Project project = new Project(client);

project.updateSessionInvalidationPolicy(
    true, // enabled
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }
        System.out.println(result);
    })
);
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let project = Project(client)

let result = try await project.updateSessionInvalidationPolicy(
    enabled: true
)
```
```server-go
package main

import (
    "fmt"
    "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("<YOUR_API_KEY>"),
    )

    service := appwrite.NewProject(client)
    result, err := service.UpdateSessionInvalidationPolicy(true)

    if err != nil {
        panic(err)
    }

    fmt.Println(result)
}
```
```server-rust
use appwrite::Client;
use appwrite::services::project::Project;

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

    let project = Project::new(&client);

    let result = project.update_session_invalidation_policy(true).await?;

    println!("{:?}", result);
    Ok(())
}
```
```bash
appwrite project update-session-invalidation-policy \
    --enabled true
```

### Update session limit policy

Sets the maximum number of concurrent sessions allowed per user. When the limit is reached, the oldest session is dropped to make room for a new one. Pass `null` to remove the limit.

```server-nodejs
import { Client, Project } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.updateSessionLimitPolicy({
    total: 10
});
```
```server-deno
import { Client, Project } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.updateSessionLimitPolicy({
    total: 10
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Project;

$client = new Client();

$client
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$project = new Project($client);

$result = $project->updateSessionLimitPolicy(
    total: 10
);
```
```server-python
from appwrite.client import Client
from appwrite.services.project import Project

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

project = Project(client)

result = project.update_session_limit_policy(
    total = 10
)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

project = Project.new(client)

response = project.update_session_limit_policy(
    total: 10
)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Project project = new Project(client);

var result = await project.UpdateSessionLimitPolicy(
    total: 10
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Project project = Project(client);

final result = await project.updateSessionLimitPolicy(
    total: 10,
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Project

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val project = Project(client)

val response = project.updateSessionLimitPolicy(
    total = 10
)
```
```server-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Project;

Client client = new Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>");

Project project = new Project(client);

project.updateSessionLimitPolicy(
    10, // total
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }
        System.out.println(result);
    })
);
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let project = Project(client)

let result = try await project.updateSessionLimitPolicy(
    total: 10
)
```
```server-go
package main

import (
    "fmt"
    "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("<YOUR_API_KEY>"),
    )

    service := appwrite.NewProject(client)
    result, err := service.UpdateSessionLimitPolicy(10)

    if err != nil {
        panic(err)
    }

    fmt.Println(result)
}
```
```server-rust
use appwrite::Client;
use appwrite::services::project::Project;

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

    let project = Project::new(&client);

    let result = project.update_session_limit_policy(10).await?;

    println!("{:?}", result);
    Ok(())
}
```
```bash
appwrite project update-session-limit-policy \
    --total 10
```

### Update user limit policy

Sets the maximum number of users in the project. Existing users remain active when the limit is reached or exceeded; new sign-ups are rejected. Pass `null` to remove the limit.

```server-nodejs
import { Client, Project } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.updateUserLimitPolicy({
    total: 1000
});
```
```server-deno
import { Client, Project } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.updateUserLimitPolicy({
    total: 1000
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Project;

$client = new Client();

$client
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$project = new Project($client);

$result = $project->updateUserLimitPolicy(
    total: 1000
);
```
```server-python
from appwrite.client import Client
from appwrite.services.project import Project

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

project = Project(client)

result = project.update_user_limit_policy(
    total = 1000
)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

project = Project.new(client)

response = project.update_user_limit_policy(
    total: 1000
)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Project project = new Project(client);

var result = await project.UpdateUserLimitPolicy(
    total: 1000
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Project project = Project(client);

final result = await project.updateUserLimitPolicy(
    total: 1000,
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Project

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val project = Project(client)

val response = project.updateUserLimitPolicy(
    total = 1000
)
```
```server-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Project;

Client client = new Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>");

Project project = new Project(client);

project.updateUserLimitPolicy(
    1000, // total
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }
        System.out.println(result);
    })
);
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let project = Project(client)

let result = try await project.updateUserLimitPolicy(
    total: 1000
)
```
```server-go
package main

import (
    "fmt"
    "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("<YOUR_API_KEY>"),
    )

    service := appwrite.NewProject(client)
    result, err := service.UpdateUserLimitPolicy(1000)

    if err != nil {
        panic(err)
    }

    fmt.Println(result)
}
```
```server-rust
use appwrite::Client;
use appwrite::services::project::Project;

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

    let project = Project::new(&client);

    let result = project.update_user_limit_policy(1000).await?;

    println!("{:?}", result);
    Ok(())
}
```
```bash
appwrite project update-user-limit-policy \
    --total 1000
```

### Update membership privacy policy

Controls which fields of one team member's profile are visible to other members in the same team. Each field can be toggled independently.

```server-nodejs
import { Client, Project } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.updateMembershipPrivacyPolicy({
    userId: true,
    userEmail: false,
    userPhone: false,
    userName: true,
    userMFA: false
});
```
```server-deno
import { Client, Project } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.updateMembershipPrivacyPolicy({
    userId: true,
    userEmail: false,
    userPhone: false,
    userName: true,
    userMFA: false
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Project;

$client = new Client();

$client
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$project = new Project($client);

$result = $project->updateMembershipPrivacyPolicy(
    userId: true,
    userEmail: false,
    userPhone: false,
    userName: true,
    userMFA: false
);
```
```server-python
from appwrite.client import Client
from appwrite.services.project import Project

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

project = Project(client)

result = project.update_membership_privacy_policy(
    user_id = True,
    user_email = False,
    user_phone = False,
    user_name = True,
    user_mfa = False
)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

project = Project.new(client)

response = project.update_membership_privacy_policy(
    user_id: true,
    user_email: false,
    user_phone: false,
    user_name: true,
    user_mfa: false
)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Project project = new Project(client);

var result = await project.UpdateMembershipPrivacyPolicy(
    userId: true,
    userEmail: false,
    userPhone: false,
    userName: true,
    userMFA: false
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Project project = Project(client);

final result = await project.updateMembershipPrivacyPolicy(
    userId: true,
    userEmail: false,
    userPhone: false,
    userName: true,
    userMFA: false,
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Project

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val project = Project(client)

val response = project.updateMembershipPrivacyPolicy(
    userId = true,
    userEmail = false,
    userPhone = false,
    userName = true,
    userMFA = false
)
```
```server-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Project;

Client client = new Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>");

Project project = new Project(client);

project.updateMembershipPrivacyPolicy(
    true, // userId
    false, // userEmail
    false, // userPhone
    true, // userName
    false, // userMFA
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }
        System.out.println(result);
    })
);
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let project = Project(client)

let result = try await project.updateMembershipPrivacyPolicy(
    userId: true,
    userEmail: false,
    userPhone: false,
    userName: true,
    userMFA: false
)
```
```server-go
package main

import (
    "fmt"
    "github.com/appwrite/sdk-for-go/appwrite"
    "github.com/appwrite/sdk-for-go/project"
)

func main() {
    client := appwrite.NewClient(
        appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"),
        appwrite.WithProject("<PROJECT_ID>"),
        appwrite.WithKey("<YOUR_API_KEY>"),
    )

    service := appwrite.NewProject(client)
    result, err := service.UpdateMembershipPrivacyPolicy(
        project.WithUpdateMembershipPrivacyPolicyUserId(true),
        project.WithUpdateMembershipPrivacyPolicyUserEmail(false),
        project.WithUpdateMembershipPrivacyPolicyUserPhone(false),
        project.WithUpdateMembershipPrivacyPolicyUserName(true),
        project.WithUpdateMembershipPrivacyPolicyUserMFA(false),
    )

    if err != nil {
        panic(err)
    }

    fmt.Println(result)
}
```
```server-rust
use appwrite::Client;
use appwrite::services::project::Project;

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

    let project = Project::new(&client);

    let result = project.update_membership_privacy_policy(
        Some(true),
        Some(false),
        Some(false),
        Some(true),
        Some(false),
    ).await?;

    println!("{:?}", result);
    Ok(())
}
```
```bash
appwrite project update-membership-privacy-policy \
    --user-id true \
    --user-email false \
    --user-phone false \
    --user-name true \
    --user-mfa false
```

### Update deny aliased email policy

When enabled, aliased emails such as subaddresses and emails with a suffix are rejected during new sign-ups and email updates.

```server-nodejs
import { Client, Project } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.updateDenyAliasedEmailPolicy({
    enabled: true
});
```
```server-deno
import { Client, Project } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.updateDenyAliasedEmailPolicy({
    enabled: true
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Project;

$client = new Client();

$client
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$project = new Project($client);

$result = $project->updateDenyAliasedEmailPolicy(
    enabled: true
);
```
```server-python
from appwrite.client import Client
from appwrite.services.project import Project

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

project = Project(client)

result = project.update_deny_aliased_email_policy(
    enabled = True
)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

project = Project.new(client)

response = project.update_deny_aliased_email_policy(
    enabled: true
)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Project project = new Project(client);

var result = await project.UpdateDenyAliasedEmailPolicy(
    enabled: true
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Project project = Project(client);

final result = await project.updateDenyAliasedEmailPolicy(
    enabled: true,
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Project

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val project = Project(client)

val response = project.updateDenyAliasedEmailPolicy(
    enabled = true
)
```
```server-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Project;

Client client = new Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>");

Project project = new Project(client);

project.updateDenyAliasedEmailPolicy(
    true, // enabled
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }
        System.out.println(result);
    })
);
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let project = Project(client)

let result = try await project.updateDenyAliasedEmailPolicy(
    enabled: true
)
```
```server-go
package main

import (
    "fmt"
    "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("<YOUR_API_KEY>"),
    )

    service := appwrite.NewProject(client)
    result, err := service.UpdateDenyAliasedEmailPolicy(true)

    if err != nil {
        panic(err)
    }

    fmt.Println(result)
}
```
```server-rust
use appwrite::Client;
use appwrite::services::project::Project;

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

    let project = Project::new(&client);

    let result = project.update_deny_aliased_email_policy(true).await?;

    println!("{:?}", result);
    Ok(())
}
```
```bash
appwrite project update-deny-aliased-email-policy \
    --enabled true
```

### Update deny disposable email policy

When enabled, disposable emails from known temporary domains are rejected during new sign-ups and email updates.

```server-nodejs
import { Client, Project } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.updateDenyDisposableEmailPolicy({
    enabled: true
});
```
```server-deno
import { Client, Project } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.updateDenyDisposableEmailPolicy({
    enabled: true
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Project;

$client = new Client();

$client
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$project = new Project($client);

$result = $project->updateDenyDisposableEmailPolicy(
    enabled: true
);
```
```server-python
from appwrite.client import Client
from appwrite.services.project import Project

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

project = Project(client)

result = project.update_deny_disposable_email_policy(
    enabled = True
)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

project = Project.new(client)

response = project.update_deny_disposable_email_policy(
    enabled: true
)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Project project = new Project(client);

var result = await project.UpdateDenyDisposableEmailPolicy(
    enabled: true
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Project project = Project(client);

final result = await project.updateDenyDisposableEmailPolicy(
    enabled: true,
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Project

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val project = Project(client)

val response = project.updateDenyDisposableEmailPolicy(
    enabled = true
)
```
```server-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Project;

Client client = new Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>");

Project project = new Project(client);

project.updateDenyDisposableEmailPolicy(
    true, // enabled
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }
        System.out.println(result);
    })
);
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let project = Project(client)

let result = try await project.updateDenyDisposableEmailPolicy(
    enabled: true
)
```
```server-go
package main

import (
    "fmt"
    "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("<YOUR_API_KEY>"),
    )

    service := appwrite.NewProject(client)
    result, err := service.UpdateDenyDisposableEmailPolicy(true)

    if err != nil {
        panic(err)
    }

    fmt.Println(result)
}
```
```server-rust
use appwrite::Client;
use appwrite::services::project::Project;

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

    let project = Project::new(&client);

    let result = project.update_deny_disposable_email_policy(true).await?;

    println!("{:?}", result);
    Ok(())
}
```
```bash
appwrite project update-deny-disposable-email-policy \
    --enabled true
```

### Update deny free email policy

When enabled, emails from free providers such as Gmail or Yahoo are rejected during new sign-ups and email updates.

```server-nodejs
import { Client, Project } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.updateDenyFreeEmailPolicy({
    enabled: true
});
```
```server-deno
import { Client, Project } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.updateDenyFreeEmailPolicy({
    enabled: true
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Project;

$client = new Client();

$client
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$project = new Project($client);

$result = $project->updateDenyFreeEmailPolicy(
    enabled: true
);
```
```server-python
from appwrite.client import Client
from appwrite.services.project import Project

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

project = Project(client)

result = project.update_deny_free_email_policy(
    enabled = True
)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

project = Project.new(client)

response = project.update_deny_free_email_policy(
    enabled: true
)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Project project = new Project(client);

var result = await project.UpdateDenyFreeEmailPolicy(
    enabled: true
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Project project = Project(client);

final result = await project.updateDenyFreeEmailPolicy(
    enabled: true,
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Project

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val project = Project(client)

val response = project.updateDenyFreeEmailPolicy(
    enabled = true
)
```
```server-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Project;

Client client = new Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>");

Project project = new Project(client);

project.updateDenyFreeEmailPolicy(
    true, // enabled
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }
        System.out.println(result);
    })
);
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let project = Project(client)

let result = try await project.updateDenyFreeEmailPolicy(
    enabled: true
)
```
```server-go
package main

import (
    "fmt"
    "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("<YOUR_API_KEY>"),
    )

    service := appwrite.NewProject(client)
    result, err := service.UpdateDenyFreeEmailPolicy(true)

    if err != nil {
        panic(err)
    }

    fmt.Println(result)
}
```
```server-rust
use appwrite::Client;
use appwrite::services::project::Project;

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

    let project = Project::new(&client);

    let result = project.update_deny_free_email_policy(true).await?;

    println!("{:?}", result);
    Ok(())
}
```
```bash
appwrite project update-deny-free-email-policy \
    --enabled true
```

### Benefits

- **Codify auth posture.** Keep password rules, session lifetimes, and user caps in version control alongside the rest of your project configuration.
- **Environment parity.** Apply the same policy script to dev, staging, and production projects to keep them aligned.
- **Faster incident response.** When a policy needs to change in a hurry (e.g. tightening session duration after a breach), update it from a script instead of clicking through the Console.

---

## Protocols

URL: https://appwrite.io/docs/partners/project/protocols

Each Appwrite project exposes its API through three protocols: REST, GraphQL, and WebSocket. You can disable any protocol your clients don't use to shrink the project's surface area, then re-enable it when needed.

Protocols can be toggled from the Appwrite Console, or programmatically through any server SDK using the Project service.

### Manage from the Console

![Project protocols in the Appwrite Console](/images/docs/project/protocols.avif)

To toggle a protocol manually:

1. Open your project in the Appwrite Console.
2. Open **Settings** at the bottom of the project sidebar.
3. On the **Overview** tab, scroll to the **Protocols** card.
4. Flip the switch next to REST, GraphQL, or WebSocket. Use **Disable all** to turn off every protocol at once.

### Available protocols

| Protocol ID | Description |
| --- | --- |
| `rest` | Standard HTTP API requests from client SDKs. |
| `graphql` | GraphQL API access for queries and mutations. |
| `websocket` | Realtime subscriptions over WebSocket connections. |

### Update a protocol

The example below disables the REST protocol. Pass `enabled: true` to re-enable it.

Disabling REST blocks client SDK traffic only. Server SDKs using an API key keep access, so you can always call this endpoint again to re-enable the protocol.

**Required scope**

The API key used for this call needs the `project.write` scope.

```server-nodejs
import { Client, Project, ProjectProtocolId } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.updateProtocol({
    protocolId: ProjectProtocolId.Rest,
    enabled: false
});
```
```server-deno
import { Client, Project, ProjectProtocolId } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.updateProtocol({
    protocolId: ProjectProtocolId.Rest,
    enabled: false
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Enums\ProjectProtocolId;
use Appwrite\Services\Project;

$client = new Client();

$client
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$project = new Project($client);

$result = $project->updateProtocol(
    protocolId: ProjectProtocolId::REST(),
    enabled: false
);
```
```server-python
from appwrite.client import Client
from appwrite.enums import ProjectProtocolId
from appwrite.services.project import Project

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

project = Project(client)

result = project.update_protocol(
    protocol_id = ProjectProtocolId.REST,
    enabled = False
)
```
```server-ruby
require 'appwrite'

include Appwrite
include Appwrite::Enums

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

project = Project.new(client)

response = project.update_protocol(
    protocol_id: ProjectProtocolId::REST,
    enabled: false
)
```
```server-dotnet
using Appwrite;
using Appwrite.Enums;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Project project = new Project(client);

var result = await project.UpdateProtocol(
    protocolId: ProjectProtocolId.Rest,
    enabled: false
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';
import 'package:dart_appwrite/enums.dart' as enums;

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Project project = Project(client);

final result = await project.updateProtocol(
    protocolId: enums.ProjectProtocolId.rest,
    enabled: false,
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.enums.ProjectProtocolId
import io.appwrite.services.Project

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val project = Project(client)

val response = project.updateProtocol(
    protocolId = ProjectProtocolId.REST,
    enabled = false
)
```
```server-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.enums.ProjectProtocolId;
import io.appwrite.services.Project;

Client client = new Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>");

Project project = new Project(client);

project.updateProtocol(
    ProjectProtocolId.REST, // protocolId
    false, // enabled
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }
        System.out.println(result);
    })
);
```
```server-swift
import Appwrite
import AppwriteEnums

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let project = Project(client)

let result = try await project.updateProtocol(
    protocolId: .rest,
    enabled: false
)
```
```server-go
package main

import (
    "fmt"
    "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("<YOUR_API_KEY>"),
    )

    service := appwrite.NewProject(client)
    result, err := service.UpdateProtocol(
        "rest",
        false,
    )

    if err != nil {
        panic(err)
    }

    fmt.Println(result)
}
```
```server-rust
use appwrite::Client;
use appwrite::enums::ProjectProtocolId;
use appwrite::services::project::Project;

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

    let project = Project::new(&client);

    let result = project.update_protocol(
        ProjectProtocolId::Rest,
        false,
    ).await?;

    println!("{:?}", result);
    Ok(())
}
```
```bash
appwrite project update-protocol \
    --protocol-id rest \
    --enabled false
```

### Benefits

- **Shrink the client surface area.** Disabled protocols are blocked for client SDK callers (anonymous, account session, JWT, and OAuth users). Server SDKs using an API key still have access, including to re-enable the protocol.
- **Repeatable provisioning.** Script the protocol set a project should expose and apply it from CI when spinning up a new environment.
- **Environment parity.** Keep dev, staging, and production in sync by running the same enable/disable script against each project.

---

## Provision a project's baseline

URL: https://appwrite.io/docs/partners/project/provisioning

A customer signs up to your platform and a project is created for them. Right now it is wide open: every authentication method is enabled, every service is exposed on the API, and nothing is constrained to the way you run things. Before that customer ever logs in, you want their project to match the baseline that every project on your platform shares.

The Project API lets you encode that baseline once and apply it from your backend or a CI step, so onboarding a customer is a script you run, not a checklist someone works through in the Console. The sections below follow that script in the order it runs:

- Register the customer's app.
- Narrow the sign-in methods to the ones they use.
- Close down the services and protocols they don't.
- Seed the variables their code expects.

Each call uses a [Server SDK](/docs/sdks#server) with an API key.

**Required scopes**

The API key used for these calls needs the `project.write` and `platforms.write` scopes. See [API keys](/docs/partners/project/api-keys) to create one.

### Register the customer's app

Nothing can talk to a project until you register the apps allowed to reach it. Set up the client once, then register the customer's web app by its hostname, so requests from their domain are accepted and everything else is rejected.

```server-nodejs
import { Client, Project, ID } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.createWebPlatform({
    platformId: ID.unique(),
    name: 'Customer web app',
    hostname: 'app.customer.example'
});
```
```server-deno
import { Client, Project, ID } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.createWebPlatform({
    platformId: ID.unique(),
    name: 'Customer web app',
    hostname: 'app.customer.example'
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\ID;
use Appwrite\Services\Project;

$client = new Client();

$client
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$project = new Project($client);

$result = $project->createWebPlatform(
    platformId: ID::unique(),
    name: 'Customer web app',
    hostname: 'app.customer.example'
);
```
```server-python
from appwrite.client import Client
from appwrite.id import ID
from appwrite.services.project import Project

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

project = Project(client)

result = project.create_web_platform(
    platform_id = ID.unique(),
    name = 'Customer web app',
    hostname = 'app.customer.example'
)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

project = Project.new(client)

response = project.create_web_platform(
    platform_id: ID.unique(),
    name: 'Customer web app',
    hostname: 'app.customer.example'
)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Project project = new Project(client);

var result = await project.CreateWebPlatform(
    platformId: ID.Unique(),
    name: "Customer web app",
    hostname: "app.customer.example"
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Project project = Project(client);

final result = await project.createWebPlatform(
    platformId: ID.unique(),
    name: 'Customer web app',
    hostname: 'app.customer.example',
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.ID
import io.appwrite.services.Project

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val project = Project(client)

val response = project.createWebPlatform(
    platformId = ID.unique(),
    name = "Customer web app",
    hostname = "app.customer.example"
)
```
```server-java
import io.appwrite.Client;
import io.appwrite.ID;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Project;

Client client = new Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>");

Project project = new Project(client);

project.createWebPlatform(
    ID.unique(), // platformId
    "Customer web app", // name
    "app.customer.example", // hostname
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }
        System.out.println(result);
    })
);
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let project = Project(client)

let result = try await project.createWebPlatform(
    platformId: ID.unique(),
    name: "Customer web app",
    hostname: "app.customer.example"
)
```
```server-go
package main

import (
    "fmt"
    "github.com/appwrite/sdk-for-go/appwrite"
    "github.com/appwrite/sdk-for-go/id"
)

func main() {
    client := appwrite.NewClient(
        appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"),
        appwrite.WithProject("<PROJECT_ID>"),
        appwrite.WithKey("<YOUR_API_KEY>"),
    )

    project := appwrite.NewProject(client)
    result, err := project.CreateWebPlatform(
        id.Unique(),
        "Customer web app",
        "app.customer.example",
    )

    if err != nil {
        panic(err)
    }

    fmt.Println(result)
}
```
```server-rust
use appwrite::Client;
use appwrite::id::ID;
use appwrite::services::project::Project;

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

    let project = Project::new(&client);

    let result = project.create_web_platform(
        ID::unique(),
        "Customer web app",
        "app.customer.example",
    ).await?;

    println!("{:?}", result);
    Ok(())
}
```
```bash
appwrite project create-web-platform \
    --platform-id 'unique()' \
    --name "Customer web app" \
    --hostname app.customer.example
```
```http
POST /v1/project/platforms/web HTTP/1.1
Content-Type: application/json
X-Appwrite-Project: <PROJECT_ID>
X-Appwrite-Key: <YOUR_API_KEY>

{
  "platformId": "unique()",
  "name": "Customer web app",
  "hostname": "app.customer.example"
}
```

If the customer ships native apps too, register them the same way with `createAndroidPlatform`, `createApplePlatform`, `createLinuxPlatform`, or `createWindowsPlatform`. Each takes the identifier for that target, such as an Android application ID or an Apple bundle identifier.

### Enable the required auth methods

A fresh project leaves every sign-in method enabled. Trim that down to the methods the customer's app offers, so you are not exposing flows they never built a UI for. If they sign their users in over SMS, turn phone authentication on.

```server-nodejs
const result = await project.updateAuthMethod({
    methodId: ProjectAuthMethodId.Phone,
    enabled: true
});
```
```server-deno
const result = await project.updateAuthMethod({
    methodId: ProjectAuthMethodId.Phone,
    enabled: true
});
```
```server-php
$result = $project->updateAuthMethod(
    methodId: ProjectAuthMethodId::PHONE(),
    enabled: true
);
```
```server-python
result = project.update_auth_method(
    method_id = ProjectAuthMethodId.PHONE,
    enabled = True
)
```
```server-ruby
response = project.update_auth_method(
    method_id: ProjectAuthMethodId::PHONE,
    enabled: true
)
```
```server-dotnet
var result = await project.UpdateAuthMethod(
    methodId: ProjectAuthMethodId.Phone,
    enabled: true
);
```
```server-dart
final result = await project.updateAuthMethod(
    methodId: enums.ProjectAuthMethodId.phone,
    enabled: true,
);
```
```server-kotlin
val result = project.updateAuthMethod(
    methodId = ProjectAuthMethodId.PHONE,
    enabled = true
)
```
```server-java
project.updateAuthMethod(
    ProjectAuthMethodId.PHONE, // methodId
    true, // enabled
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }
        System.out.println(result);
    })
);
```
```server-swift
let result = try await project.updateAuthMethod(
    methodId: .phone,
    enabled: true
)
```
```server-go
result, err := project.UpdateAuthMethod(
    "phone",
    true,
)

if err != nil {
    panic(err)
}

fmt.Println(result)
```
```server-rust
let result = project.update_auth_method(
    ProjectAuthMethodId::Phone,
    true,
).await?;

println!("{:?}", result);
```
```bash
appwrite project update-auth-method \
    --method-id phone \
    --enabled true
```
```http
PATCH /v1/project/auth-methods/phone HTTP/1.1
Content-Type: application/json
X-Appwrite-Project: <PROJECT_ID>
X-Appwrite-Key: <YOUR_API_KEY>

{
  "enabled": true
}
```

Pass `enabled: false` to switch a method off. The method ID is one of `email-password`, `magic-url`, `email-otp`, `anonymous`, `invites`, `jwt`, or `phone`.

### Disable unused services and protocols

Every service the customer will never call is attack surface you can remove. If their app has no client-side Functions, take Functions off the client API to shrink that surface.

```server-nodejs
const result = await project.updateService({
    serviceId: ProjectServiceId.Functions,
    enabled: false
});
```
```server-deno
const result = await project.updateService({
    serviceId: ProjectServiceId.Functions,
    enabled: false
});
```
```server-php
$result = $project->updateService(
    serviceId: ProjectServiceId::FUNCTIONS(),
    enabled: false
);
```
```server-python
result = project.update_service(
    service_id = ProjectServiceId.FUNCTIONS,
    enabled = False
)
```
```server-ruby
response = project.update_service(
    service_id: ProjectServiceId::FUNCTIONS,
    enabled: false
)
```
```server-dotnet
var result = await project.UpdateService(
    serviceId: ProjectServiceId.Functions,
    enabled: false
);
```
```server-dart
final result = await project.updateService(
    serviceId: enums.ProjectServiceId.functions,
    enabled: false,
);
```
```server-kotlin
val response = project.updateService(
    serviceId = ProjectServiceId.FUNCTIONS,
    enabled = false
)
```
```server-java
project.updateService(
    ProjectServiceId.FUNCTIONS, // serviceId
    false, // enabled
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }
        System.out.println(result);
    })
);
```
```server-swift
let result = try await project.updateService(
    serviceId: .functions,
    enabled: false
)
```
```server-go
result, err := service.UpdateService(
    "functions",
    false,
)

if err != nil {
    panic(err)
}

fmt.Println(result)
```
```server-rust
let result = project.update_service(
    ProjectServiceId::Functions,
    false,
).await?;

println!("{:?}", result);
```
```bash
appwrite project update-service \
    --service-id functions \
    --enabled false
```
```http
PATCH /v1/project/services/functions HTTP/1.1
Content-Type: application/json
X-Appwrite-Project: <PROJECT_ID>
X-Appwrite-Key: <YOUR_API_KEY>

{
  "enabled": false
}
```

**Server SDKs keep access**

Disabling a service only removes it from the client-facing API. A Server SDK using an API key can still reach the service, including to turn it back on later, so your own backend keeps working while client apps lose the route.

Apply the same reasoning to API protocols. If nothing in the customer's stack speaks GraphQL, disable it and leave REST and Realtime in place.

```server-nodejs
const result = await project.updateProtocol({
    protocolId: ProjectProtocolId.Graphql,
    enabled: false
});
```
```server-deno
const result = await project.updateProtocol({
    protocolId: ProjectProtocolId.Graphql,
    enabled: false
});
```
```server-php
$result = $project->updateProtocol(
    protocolId: ProjectProtocolId::GRAPHQL(),
    enabled: false
);
```
```server-python
result = project.update_protocol(
    protocol_id = ProjectProtocolId.GRAPHQL,
    enabled = False
)
```
```server-ruby
response = project.update_protocol(
    protocol_id: ProjectProtocolId::GRAPHQL,
    enabled: false
)
```
```server-dotnet
var result = await project.UpdateProtocol(
    protocolId: ProjectProtocolId.Graphql,
    enabled: false
);
```
```server-dart
final result = await project.updateProtocol(
    protocolId: enums.ProjectProtocolId.graphql,
    enabled: false,
);
```
```server-kotlin
val response = project.updateProtocol(
    protocolId = ProjectProtocolId.GRAPHQL,
    enabled = false
)
```
```server-java
project.updateProtocol(
    ProjectProtocolId.GRAPHQL, // protocolId
    false, // enabled
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }
        System.out.println(result);
    })
);
```
```server-swift
let result = try await project.updateProtocol(
    protocolId: .graphql,
    enabled: false
)
```
```server-go
result, err := service.UpdateProtocol(
    "graphql",
    false,
)

if err != nil {
    panic(err)
}

fmt.Println(result)
```
```server-rust
let result = project.update_protocol(
    ProjectProtocolId::Graphql,
    false,
).await?;

println!("{:?}", result);
```
```bash
appwrite project update-protocol \
    --protocol-id graphql \
    --enabled false
```
```http
PATCH /v1/project/protocols/graphql HTTP/1.1
Content-Type: application/json
X-Appwrite-Project: <PROJECT_ID>
X-Appwrite-Key: <YOUR_API_KEY>

{
  "enabled": false
}
```

The protocol ID is one of `rest`, `graphql`, or `websocket`.

### Seed project variables

The last step before the project is ready is to drop in the constants and secrets the customer's functions and sites read at build and runtime, so their first deploy finds everything it needs already in place.

```server-nodejs
const result = await project.createVariable({
    variableId: ID.unique(),
    key: 'TENANT_TIER',
    value: 'pro',
    secret: false // optional
});
```
```server-deno
const result = await project.createVariable({
    variableId: ID.unique(),
    key: 'TENANT_TIER',
    value: 'pro',
    secret: false // optional
});
```
```server-php
$result = $project->createVariable(
    variableId: ID::unique(),
    key: 'TENANT_TIER',
    value: 'pro',
    secret: false // optional
);
```
```server-python
result: Variable = project.create_variable(
    variable_id = ID.unique(),
    key = 'TENANT_TIER',
    value = 'pro',
    secret = False # optional
)

print(result.model_dump())
```
```server-ruby
result = project.create_variable(
    variable_id: ID.unique(),
    key: 'TENANT_TIER',
    value: 'pro',
    secret: false # optional
)
```
```server-dotnet
Variable result = await project.CreateVariable(
    variableId: ID.Unique(),
    key: "<KEY>",
    value: "<VALUE>",
    secret: false // optional
);
```
```server-dart
Variable result = await project.createVariable(
    variableId: ID.unique(),
    key: 'TENANT_TIER',
    value: 'pro',
    secret: false, // (optional)
);
```
```server-kotlin
val response = project.createVariable(
    variableId = ID.unique(),
    key = "<KEY>",
    value = "<VALUE>",
    secret = false // optional
)
```
```server-java
project.createVariable(
    ID.unique(), // variableId
    "<KEY>", // key
    "<VALUE>", // value
    false, // secret (optional)
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        System.out.println(result);
    })
);
```
```server-swift
let variable = try await project.createVariable(
    variableId: ID.unique(),
    key: "<KEY>",
    value: "<VALUE>",
    secret: false // optional
)
```
```server-go
response, error := project.CreateVariable(
id.Unique(),
"<KEY>",
"<VALUE>",
appwrite.WithCreateVariableSecret(false),
)
```
```server-rust
let result = project.create_variable(
    &ID::unique(),
    "<KEY>",
    "<VALUE>",
    Some(false) // optional
).await?;

let _ = result;
```
```bash
appwrite project create-variable \
    --variable-id 'unique()' \
    --key TENANT_TIER \
    --value pro
```
```http
POST /v1/project/variables HTTP/1.1
Content-Type: application/json
X-Appwrite-Project: <PROJECT_ID>
X-Appwrite-Key: <YOUR_API_KEY>

{
  "variableId": "unique()",
  "key": "TENANT_TIER",
  "value": "pro"
}
```

### Next steps

Wrap these calls in a single function keyed to the customer and every project you bring online starts identical, which is what keeps the next thousand projects manageable. With the baseline in place, the rest of the project's life uses the same Project API:

- [Issue and rotate API keys](/docs/partners/project/key-rotation) for the integrations that run against the project.
- [Brand its transactional emails](/docs/partners/project/branded-emails) so they come from the customer's domain.

---

## Services

URL: https://appwrite.io/docs/partners/project/services

Each Appwrite project ships with the full set of services enabled by default: Account, TablesDB, Storage, Functions, and so on. You can disable any service your clients don't use to remove it from the client-facing API. Disabled services remain accessible to server SDKs using an API key.

Services can be toggled from the Appwrite Console, or programmatically through any server SDK using the Project service.

### Manage from the Console

![Project services in the Appwrite Console](/images/docs/project/services.avif)

To toggle a service manually:

1. Open your project in the Appwrite Console.
2. Open **Settings** at the bottom of the project sidebar.
3. On the **Overview** tab, scroll to the **Services** card.
4. Flip the switch next to the service you want to enable or disable. Use **Disable all** to turn off every optional service at once.

### Available services

| Service ID | Description |
| --- | --- |
| `account` | User accounts and authentication. |
| `avatars` | App image, icon, and avatar helpers. |
| `tablesdb` | TablesDB tables, columns, and rows. |
| `locale` | Locale and geographic helpers. |
| `health` | Health checks and status. |
| `project` | Project configuration. |
| `storage` | File storage buckets and files. |
| `teams` | Teams and shared resource access. |
| `users` | User administration via server SDKs. |
| `sites` | Sites hosting and deployments. |
| `functions` | Cloud Functions. |
| `proxy` | Custom domain proxy. |
| `graphql` | GraphQL endpoint. |
| `migrations` | Third-party data migrations. |
| `messaging` | Push, SMS, and email messaging. |
| `databases` | Legacy Databases collections and documents. |

### Update a service

The example below disables the Account service. Pass `enabled: true` to re-enable it.

**Required scope**

The API key used for this call needs the `project.write` scope.

```server-nodejs
import { Client, Project, ProjectServiceId } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.updateService({
    serviceId: ProjectServiceId.Account,
    enabled: false
});
```
```server-deno
import { Client, Project, ProjectServiceId } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.updateService({
    serviceId: ProjectServiceId.Account,
    enabled: false
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Enums\ProjectServiceId;
use Appwrite\Services\Project;

$client = new Client();

$client
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$project = new Project($client);

$result = $project->updateService(
    serviceId: ProjectServiceId::ACCOUNT(),
    enabled: false
);
```
```server-python
from appwrite.client import Client
from appwrite.enums import ProjectServiceId
from appwrite.services.project import Project

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

project = Project(client)

result = project.update_service(
    service_id = ProjectServiceId.ACCOUNT,
    enabled = False
)
```
```server-ruby
require 'appwrite'

include Appwrite
include Appwrite::Enums

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

project = Project.new(client)

response = project.update_service(
    service_id: ProjectServiceId::ACCOUNT,
    enabled: false
)
```
```server-dotnet
using Appwrite;
using Appwrite.Enums;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Project project = new Project(client);

var result = await project.UpdateService(
    serviceId: ProjectServiceId.Account,
    enabled: false
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';
import 'package:dart_appwrite/enums.dart' as enums;

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Project project = Project(client);

final result = await project.updateService(
    serviceId: enums.ProjectServiceId.account,
    enabled: false,
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.enums.ProjectServiceId
import io.appwrite.services.Project

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val project = Project(client)

val response = project.updateService(
    serviceId = ProjectServiceId.ACCOUNT,
    enabled = false
)
```
```server-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.enums.ProjectServiceId;
import io.appwrite.services.Project;

Client client = new Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>");

Project project = new Project(client);

project.updateService(
    ProjectServiceId.ACCOUNT, // serviceId
    false, // enabled
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }
        System.out.println(result);
    })
);
```
```server-swift
import Appwrite
import AppwriteEnums

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let project = Project(client)

let result = try await project.updateService(
    serviceId: .account,
    enabled: false
)
```
```server-go
package main

import (
    "fmt"
    "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("<YOUR_API_KEY>"),
    )

    service := appwrite.NewProject(client)
    result, err := service.UpdateService(
        "account",
        false,
    )

    if err != nil {
        panic(err)
    }

    fmt.Println(result)
}
```
```server-rust
use appwrite::Client;
use appwrite::enums::ProjectServiceId;
use appwrite::services::project::Project;

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

    let project = Project::new(&client);

    let result = project.update_service(
        ProjectServiceId::Account,
        false,
    ).await?;

    println!("{:?}", result);
    Ok(())
}
```
```bash
appwrite project update-service \
    --service-id account \
    --enabled false
```

### Benefits

- **Shrink the client surface area.** Disable services your client apps don't use so they're no longer callable from client SDKs.
- **Server-only access.** Disabled services stay reachable from server SDKs using an API key, useful when you want a service available to your backend but hidden from clients.
- **Repeatable provisioning.** Script the service set a project should expose and apply it from CI when spinning up a new environment.

---

## SMTP

URL: https://appwrite.io/docs/partners/project/smtp

By default, Appwrite sends account management emails such as verification, password recovery, and magic URL links from a shared SMTP server. Configuring a custom SMTP server lets you send these emails through your own provider instead. This sends mail from your own domain, improves deliverability, and unlocks [custom email templates](/docs/partners/project/email-templates).

### Configure in the Console

![SMTP settings in the Appwrite Console](/images/docs/project/smtp.avif)

To configure a custom SMTP server from the Appwrite Console:

1. Navigate to your project.
2. Open the **Settings** section and select the **SMTP** tab.
3. Enable **Custom SMTP server**.
4. Enter your provider's **Sender name**, **Sender email**, **Server host**, and **Server port**. Add a **Username** and **Password** if your provider requires authentication, set a **Reply to** address if needed, and choose the encryption protocol (TLS or SSL).
5. Click **Update**. Appwrite validates the connection to your SMTP server before saving, so the credentials must be correct.

### Configure with a Server SDK

You can also manage SMTP programmatically using a [Server SDK](/docs/sdks#server).

**Required scope**

The API key used for these calls needs the `project.write` scope.

#### Configure SMTP

Every field is optional, so you can change only the values you need. Any field you omit keeps its current value. To enable SMTP, the project must have a `host`, `port`, and `senderEmail` set, either in this call or from a previous one. When you enable SMTP, Appwrite validates the connection to your server before saving. The encryption protocol accepts `tls` or `ssl`.

```server-nodejs
import { Client, Project, ProjectSMTPSecure } from '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 API key

const project = new Project(client);

const result = await project.updateSMTP({
    host: 'smtp.example.com',
    port: 587,
    username: '<USERNAME>', // optional
    password: '<PASSWORD>', // optional
    senderEmail: 'email@example.com',
    senderName: '<SENDER_NAME>', // optional
    replyToEmail: 'email@example.com', // optional
    replyToName: '<REPLY_TO_NAME>', // optional
    secure: ProjectSMTPSecure.Tls, // optional
    enabled: true
});
```
```server-deno
import { Client, Project, ProjectSMTPSecure } 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 API key

const project = new Project(client);

const result = await project.updateSMTP({
    host: 'smtp.example.com',
    port: 587,
    username: '<USERNAME>', // optional
    password: '<PASSWORD>', // optional
    senderEmail: 'email@example.com',
    senderName: '<SENDER_NAME>', // optional
    replyToEmail: 'email@example.com', // optional
    replyToName: '<REPLY_TO_NAME>', // optional
    secure: ProjectSMTPSecure.Tls, // optional
    enabled: true
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Project;
use Appwrite\Enums\ProjectSMTPSecure;

$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 API key

$project = new Project($client);

$result = $project->updateSMTP(
    host: 'smtp.example.com',
    port: 587,
    username: '<USERNAME>', // optional
    password: '<PASSWORD>', // optional
    senderEmail: 'email@example.com',
    senderName: '<SENDER_NAME>', // optional
    replyToEmail: 'email@example.com', // optional
    replyToName: '<REPLY_TO_NAME>', // optional
    secure: ProjectSMTPSecure::TLS(), // optional
    enabled: true
);
```
```server-python
from appwrite.client import Client
from appwrite.services.project import Project
from appwrite.models import Project as ProjectModel
from appwrite.enums import ProjectSMTPSecure

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 API key

project = Project(client)

result: ProjectModel = project.update_smtp(
    host = 'smtp.example.com',
    port = 587,
    username = '<USERNAME>', # optional
    password = '<PASSWORD>', # optional
    sender_email = 'email@example.com',
    sender_name = '<SENDER_NAME>', # optional
    reply_to_email = 'email@example.com', # optional
    reply_to_name = '<REPLY_TO_NAME>', # optional
    secure = ProjectSMTPSecure.TLS, # optional
    enabled = True
)

print(result.model_dump())
```
```server-ruby
require 'appwrite'

include Appwrite
include Appwrite::Enums

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 API key

project = Project.new(client)

result = project.update_smtp(
    host: 'smtp.example.com',
    port: 587,
    username: '<USERNAME>', # optional
    password: '<PASSWORD>', # optional
    sender_email: 'email@example.com',
    sender_name: '<SENDER_NAME>', # optional
    reply_to_email: 'email@example.com', # optional
    reply_to_name: '<REPLY_TO_NAME>', # optional
    secure: ProjectSMTPSecure::TLS, # optional
    enabled: true
)
```
```server-dotnet
using Appwrite;
using Appwrite.Enums;
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 API key

Project project = new Project(client);

Project result = await project.UpdateSMTP(
    host: "smtp.example.com",
    port: 587,
    username: "<USERNAME>", // optional
    password: "<PASSWORD>", // optional
    senderEmail: "email@example.com",
    senderName: "<SENDER_NAME>", // optional
    replyToEmail: "email@example.com", // optional
    replyToName: "<REPLY_TO_NAME>", // optional
    secure: ProjectSMTPSecure.Tls, // optional
    enabled: true
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';
import 'package:dart_appwrite/enums.dart' as enums;

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 API key

Project project = Project(client);

Project result = await project.updateSMTP(
    host: 'smtp.example.com',
    port: 587,
    username: '<USERNAME>', // (optional)
    password: '<PASSWORD>', // (optional)
    senderEmail: 'email@example.com',
    senderName: '<SENDER_NAME>', // (optional)
    replyToEmail: 'email@example.com', // (optional)
    replyToName: '<REPLY_TO_NAME>', // (optional)
    secure: enums.ProjectSMTPSecure.tls, // (optional)
    enabled: true,
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.coroutines.CoroutineCallback
import io.appwrite.enums.ProjectSMTPSecure
import io.appwrite.services.Project

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 API key

val project = Project(client)

val response = project.updateSMTP(
    host = "smtp.example.com",
    port = 587,
    username = "<USERNAME>", // optional
    password = "<PASSWORD>", // optional
    senderEmail = "email@example.com",
    senderName = "<SENDER_NAME>", // optional
    replyToEmail = "email@example.com", // optional
    replyToName = "<REPLY_TO_NAME>", // optional
    secure = ProjectSMTPSecure.TLS, // optional
    enabled = true
)
```
```server-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.enums.ProjectSMTPSecure;
import io.appwrite.services.Project;

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 API key

Project project = new Project(client);

project.updateSMTP(
    "smtp.example.com", // host
    587, // port
    "<USERNAME>", // username (optional)
    "<PASSWORD>", // password (optional)
    "email@example.com", // senderEmail
    "<SENDER_NAME>", // senderName (optional)
    "email@example.com", // replyToEmail (optional)
    "<REPLY_TO_NAME>", // replyToName (optional)
    ProjectSMTPSecure.TLS, // secure (optional)
    true, // enabled
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        System.out.println(result);
    })
);
```
```server-swift
import Appwrite
import AppwriteEnums

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 API key

let project = Project(client)

let result = try await project.updateSMTP(
    host: "smtp.example.com",
    port: 587,
    username: "<USERNAME>", // optional
    password: "<PASSWORD>", // optional
    senderEmail: "email@example.com",
    senderName: "<SENDER_NAME>", // optional
    replyToEmail: "email@example.com", // optional
    replyToName: "<REPLY_TO_NAME>", // optional
    secure: .tls, // optional
    enabled: true
)
```
```server-go
package main

import (
    "fmt"
    "github.com/appwrite/sdk-for-go/appwrite"
)

func main() {
    client := appwrite.NewClient(
        appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"),
        appwrite.WithProject("<YOUR_PROJECT_ID>"),
        appwrite.WithKey("<YOUR_API_KEY>"),
    )

    project := appwrite.NewProject(client)
    result, err := project.UpdateSMTP(
        appwrite.WithUpdateSMTPHost("smtp.example.com"),
        appwrite.WithUpdateSMTPPort(587),
        appwrite.WithUpdateSMTPUsername("<USERNAME>"),
        appwrite.WithUpdateSMTPPassword("<PASSWORD>"),
        appwrite.WithUpdateSMTPSenderEmail("email@example.com"),
        appwrite.WithUpdateSMTPSenderName("<SENDER_NAME>"),
        appwrite.WithUpdateSMTPReplyToEmail("email@example.com"),
        appwrite.WithUpdateSMTPReplyToName("<REPLY_TO_NAME>"),
        appwrite.WithUpdateSMTPSecure("tls"),
        appwrite.WithUpdateSMTPEnabled(true),
    )

    if err != nil {
        panic(err)
    }

    fmt.Println(result)
}
```
```server-rust
use appwrite::Client;
use appwrite::services::Project;
use appwrite::enums::ProjectSMTPSecure;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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 API key

    let project = Project::new(&client);

    let result = project.update_smtp(
        Some("smtp.example.com"),
        Some(587),
        Some("<USERNAME>"), // optional
        Some("<PASSWORD>"), // optional
        Some("email@example.com"),
        Some("<SENDER_NAME>"), // optional
        Some("email@example.com"), // optional
        Some("<REPLY_TO_NAME>"), // optional
        Some(ProjectSMTPSecure::Tls), // optional
        Some(true)
    ).await?;

    let _ = result;

    Ok(())
}
```
```bash
appwrite project update-smtp \
    --host smtp.example.com \
    --port 587 \
    --username "<USERNAME>" \
    --password "<PASSWORD>" \
    --sender-email email@example.com \
    --sender-name "<SENDER_NAME>" \
    --reply-to-email email@example.com \
    --reply-to-name "<REPLY_TO_NAME>" \
    --secure tls \
    --enabled true
```

#### Send a test email

Once SMTP is configured, send a test email to confirm that your server delivers mail. Appwrite sends a sample email to each address you provide, up to a maximum of 10 recipients.

```server-nodejs
import { Client, Project } from '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 API key

const project = new Project(client);

const result = await project.createSMTPTest({
    emails: ['email@example.com']
});
```
```server-deno
import { Client, Project } 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 API key

const project = new Project(client);

const result = await project.createSMTPTest({
    emails: ['email@example.com']
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Project;

$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 API key

$project = new Project($client);

$result = $project->createSMTPTest(
    emails: ['email@example.com']
);
```
```server-python
from appwrite.client import Client
from appwrite.services.project import Project

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 API key

project = Project(client)

result = project.create_smtp_test(
    emails = ['email@example.com']
)
```
```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 API key

project = Project.new(client)

result = project.create_smtp_test(
    emails: ['email@example.com']
)
```
```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 API key

Project project = new Project(client);

await project.CreateSMTPTest(
    emails: new List<string> { "email@example.com" }
);
```
```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 API key

Project project = Project(client);

await project.createSMTPTest(
    emails: ['email@example.com'],
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.coroutines.CoroutineCallback
import io.appwrite.services.Project

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 API key

val project = Project(client)

val response = project.createSMTPTest(
    emails = listOf("email@example.com")
)
```
```server-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Project;

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 API key

Project project = new Project(client);

project.createSMTPTest(
    List.of("email@example.com"), // emails
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        System.out.println(result);
    })
);
```
```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 API key

let project = Project(client)

let result = try await project.createSMTPTest(
    emails: ["email@example.com"]
)
```
```server-go
package main

import (
    "fmt"
    "github.com/appwrite/sdk-for-go/appwrite"
)

func main() {
    client := appwrite.NewClient(
        appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"),
        appwrite.WithProject("<YOUR_PROJECT_ID>"),
        appwrite.WithKey("<YOUR_API_KEY>"),
    )

    project := appwrite.NewProject(client)
    response, err := project.CreateSMTPTest(
        []interface{}{"email@example.com"},
    )

    if err != nil {
        panic(err)
    }

    fmt.Println(response)
}
```
```server-rust
use appwrite::Client;
use appwrite::services::Project;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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 API key

    let project = Project::new(&client);

    project.create_smtp_test(
        vec!["email@example.com"]
    ).await?;

    Ok(())
}
```
```bash
appwrite project create-smtp-test \
    --emails email@example.com
```

### Benefits

- **Send from your own domain.** Deliver account emails from your branded sender address instead of Appwrite's shared one, so users recognize and trust them.
- **Better deliverability.** Routing mail through an authenticated provider, with SPF and DKIM set up on your domain, reduces the chance of verification and recovery emails landing in spam.
- **Unlocks custom templates.** A custom SMTP server is required to customize [email templates](/docs/partners/project/email-templates) per locale.

---

## Proxy API

URL: https://appwrite.io/docs/partners/proxy

The Proxy API attaches a custom domain to a resource inside one Appwrite project. A rule maps one hostname to one target, and the target is the project API, a function, a site, or a redirect to another URL.

Partner platforms use the Proxy API to give each customer a branded hostname. When a customer sets `api.customer.com` in your product, your backend creates a rule on that customer's project and reports the verification state back to your UI.

### Authentication

Proxy rules belong to a project, not to an organization. Authenticate with the customer's project ID and a project API key. A Partners key cannot read or write proxy rules.

| Scope | Operations |
| ----- | ---------- |
| `rules.read` | List rules, get a rule |
| `rules.write` | Create a rule, verify a rule, delete a rule |
| `proxy.invalidations.write` | Invalidate cached responses |

```server-nodejs
import { Client, Proxy } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey(process.env.APPWRITE_API_KEY);

const proxy = new Proxy(client);
```
```server-rust
use appwrite::client::Client;
use appwrite::services::Proxy;

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

let proxy = Proxy::new(&client);
```

### Rule types

Each create operation produces a rule with a fixed `type`. The `type` field tells you what the hostname resolves to.

| Operation | `type` | Target |
| --------- | ------ | ------ |
| `createAPIRule` | `api` | The project's Appwrite API |
| `createFunctionRule` | `deployment` | A function in the project |
| `createSiteRule` | `deployment` | A site in the project |
| `createRedirectRule` | `redirect` | Another URL, with a status code |

Appwrite derives the rule ID from the domain. A second rule for the same hostname fails with `409 rule_already_exists`, so your provisioning code can treat that error as "already configured" instead of a fatal failure.

### API rules

An API rule points a hostname at the project's Appwrite API, so customer apps call `api.customer.com` instead of the regional Appwrite endpoint.

```server-nodejs
const rule = await proxy.createAPIRule({
    domain: 'api.customer.com',
});

console.log(rule.$id, rule.status); // unverified
```
```server-rust
let rule = proxy.create_api_rule("api.customer.com").await?;

println!("{} {:?}", rule.id, rule.status); // Unverified
```

### Function and site rules

Function and site rules both take an optional `branch`. With a branch set, Appwrite moves the rule to the newest deployment from that branch.

```server-nodejs
const functionRule = await proxy.createFunctionRule({
    domain: 'hooks.customer.com',
    functionId: '<FUNCTION_ID>',
    branch: 'main',
});

const siteRule = await proxy.createSiteRule({
    domain: 'www.customer.com',
    siteId: '<SITE_ID>',
    branch: 'main',
});
```
```server-rust
let function_rule = proxy
    .create_function_rule("hooks.customer.com", "<FUNCTION_ID>", Some("main"))
    .await?;

let site_rule = proxy
    .create_site_rule("www.customer.com", "<SITE_ID>", Some("main"))
    .await?;
```

Both rules come back with `type` set to `deployment`. `deploymentResourceType` holds `function` or `site` and tells them apart.

### Redirect rules

A redirect rule sends traffic to another URL. `resourceType` accepts `function` or `site`, and `resourceId` names the resource the redirect belongs to. `statusCode` accepts `301`, `302`, `307`, and `308`.

```server-nodejs
const redirect = await proxy.createRedirectRule({
    domain: 'customer.com',
    url: 'https://www.customer.com',
    statusCode: '301',
    resourceId: '<SITE_ID>',
    resourceType: 'site',
});
```
```server-rust
use appwrite::enums::{ProxyResourceType, StatusCode};

let redirect = proxy
    .create_redirect_rule(
        "customer.com",
        "https://www.customer.com",
        StatusCode::MovedPermanently,
        "<SITE_ID>",
        ProxyResourceType::Site,
    )
    .await?;
```

### Verification

A new rule starts at `unverified`. Appwrite checks DNS only when the verify operation runs, so the customer has to publish the record first. The sequence is:

1. The rule is created, and your backend stores its `$id`.
2. The customer adds a CNAME record for the hostname. The record value depends on the rule type and the deployment, and the domain's verification panel in the Console shows it.
3. The verify operation runs. Appwrite resolves the hostname and issues a certificate.

Some DNS providers reject a CNAME record on an apex domain. If the customer's provider rejects one, the customer can delegate the zone to the Appwrite nameservers instead.

```server-nodejs
const verified = await proxy.updateRuleStatus({
    ruleId: rule.$id,
});

console.log(verified.status);
```
```server-rust
let verified = proxy.update_rule_status(&rule.id).await?;

println!("{:?}", verified.status);
```

If the CNAME record is missing, the call fails and the message names the hostname and the resolver that ran the lookup. The rule also keeps the last failure in its `logs` field, so your UI can show the reason without repeating the call.

You can retry verification after the customer fixes DNS, because the check is idempotent.

### Reading rules

```server-nodejs
const rules = await proxy.listRules({ total: true });

for (const rule of rules.rules) {
    console.log(rule.domain, rule.type, rule.status);
}

const one = await proxy.getRule({ ruleId: '<RULE_ID>' });
```
```server-rust
let rules = proxy.list_rules(None, Some(true)).await?;

for rule in &rules.rules {
    println!("{} {} {:?}", rule.domain, rule.r#type, rule.status);
}

let one = proxy.get_rule("<RULE_ID>").await?;
```

### Rule deletion

Deleting a rule stops Appwrite from serving the hostname. The customer's DNS record survives the delete and has to be removed separately.

```server-nodejs
await proxy.deleteRule({ ruleId: '<RULE_ID>' });
```
```server-rust
proxy.delete_rule("<RULE_ID>").await?;
```

### Invalidations

Appwrite caches responses served through a verified domain, and an invalidation clears that cache for one domain. The domain must be verified first, because an unverified domain has nothing cached.

| `type` | `reference` holds |
| ------ | ----------------- |
| `all` | Nothing. Clears every cached response for the domain |
| `path` | A path to clear |
| `tag` | A cache tag name |

```server-nodejs
await proxy.createInvalidation({
    domain: 'www.customer.com',
    type: 'all',
});
```
```server-rust
use appwrite::enums::InvalidationType;

proxy
    .create_invalidation("www.customer.com", InvalidationType::All, None)
    .await?;
```

### Rule fields

| Field | Description |
| ----- | ----------- |
| `$id` | Rule ID, derived from the domain |
| `domain` | The hostname the rule serves |
| `type` | `api`, `deployment`, or `redirect` |
| `trigger` | `manual` for rules you create, `deployment` for rules Appwrite creates |
| `status` | `unverified`, `verifying`, or `verified` |
| `logs` | The last verification message, empty until a check runs |
| `deploymentResourceType` | `function` or `site` on deployment rules, empty otherwise |
| `deploymentResourceId` | The function ID or site ID on deployment rules |
| `deploymentVcsProviderBranch` | The branch whose deployments the rule follows, empty when unset |
| `redirectUrl` | The target URL on redirect rules |
| `redirectStatusCode` | The HTTP status code on redirect rules |
| `renewAt` | When Appwrite renews the certificate |

### Related

[Project API keys](/docs/partners/project/api-keys)

---

## Quick start

URL: https://appwrite.io/docs/partners/quick-start

This quick start helps you pick an integration path and make your first Console API call.

### Choose your integration model

| Model | Best for | Auth |
| ----- | -------- | ---- |
| [OAuth connect](/docs/partners/oauth-connect) | Users link existing Appwrite accounts | User consent via OAuth 2.0 |
| [Partners keys](/docs/partners/org-api-keys) | Your platform provisions resources in your organization | Partners key |

Use **OAuth connect** when your customers already use Appwrite and want to grant your product access to their organizations.

Use a **Partners key** when your platform creates and manages the projects inside an organization you control. Each project's own resources need a project API key, covered in [Project](/docs/partners/project).

### Prerequisites

- An Appwrite Cloud account with an organization
- A server-side environment to store secrets (API keys or OAuth client credentials)
- An [Appwrite server SDK](/docs/sdks#server) for Node.js, Rust, or another server language

### Step 1: Initialize the Organization service

Partner integrations run on your backend. A Partners key belongs to one organization, so pass the organization ID with the key. The client sends no project ID.

```server-nodejs
import { Client, Organization } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://cloud.appwrite.io/v1')
    .setOrganization('<ORGANIZATION_ID>')
    .setKey(process.env.APPWRITE_PARTNERS_KEY);

const organization = new Organization(client);
```
```server-rust
use appwrite::client::Client;
use appwrite::services::Organization;

let client = Client::new()
    .set_endpoint("https://cloud.appwrite.io/v1")
    .set_organization("<ORGANIZATION_ID>")
    .set_key(std::env::var("APPWRITE_PARTNERS_KEY")?);

let organization = Organization::new(&client);
```

**Never expose secrets in client apps**

Partners keys and OAuth client secrets must stay on your server. Use your backend to call Console APIs and project APIs on behalf of users.

### Step 2: List or create a project

A Partners key acts on one organization, so `listProjects` and `createProject` take no organization parameter.

```server-nodejs
import { ID } from 'node-appwrite';

const list = await organization.listProjects();

const created = await organization.createProject({
    projectId: ID.unique(),
    name: 'Customer workspace',
    region: 'fra',
});
```
```server-rust
use appwrite::enums::Region;
use appwrite::id::ID;

let list = organization.list_projects(None, None, None).await?;

let created = organization
    .create_project(ID::unique(), "Customer workspace", Some(Region::Fra))
    .await?;
```

### Step 3: Manage project resources

After you have a project, initialize a second client with `setProject()` and a project API key, then manage databases, storage, functions, and other services inside that project. See [Project](/docs/partners/project).

### Next steps

- [Set up OAuth connect](/docs/partners/oauth-connect/setup): Register an OAuth app and request access to user organizations.
- [Configure Partners key scopes](/docs/partners/org-api-keys#scopes): Grant only the Console scopes your platform needs.
- [Create a project](/docs/partners/project/create): Provision a project per customer from your backend.

---

## Usage

URL: https://appwrite.io/docs/partners/usage

**Unlinked draft**

This page is not in the Partners navigation. Organization usage and billing sit behind `billing.read` and `billing.write`, which a Partners key cannot hold, so no operation below is reachable from a backend today. Relink this page once a key-authenticated usage surface exists.

The Usage API lets partner platforms read **organization-level consumption** and plan context. Use it to show usage dashboards, enforce limits, or align your billing with Appwrite Cloud usage.

Access usage through `sdk.forConsole.organizations` with an organization API key or OAuth-delegated access that includes billing scopes.

### Console SDK access

```server-nodejs
import { Client, Organization } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://cloud.appwrite.io/v1')
    .setOrganization('<ORGANIZATION_ID>')
    .setKey(process.env.APPWRITE_PARTNERS_KEY);

const organization = new Organization(client);
```

### Common operations

| Operation | Use case |
| --------- | -------- |
| `getPlan` | Read plan limits, features, and billing cycle for an organization |
| `listUsage` | Fetch current usage metrics for the organization |
| `getAggregation` | Paginate usage breakdown by project or resource |
| `listInvoices` | Surface billing history in your platform UI |
| `updateSelectedProjects` | Control which projects count toward plan limits |

### Read organization usage

```server-nodejs
const plan = await organizations.getPlan({ organizationId: '<ORGANIZATION_ID>' });

const usage = await organizations.listUsage('<ORGANIZATION_ID>');
```

Use `getPlan` to understand limits before provisioning projects for a customer. Use `listUsage` to display consumption in your control plane or trigger alerts when thresholds are reached.

### Usage aggregation

For per-project breakdowns, call `getAggregation` with the aggregation ID from your organization billing context:

```server-nodejs
const aggregation = await organizations.getAggregation({
    organizationId: '<ORGANIZATION_ID>',
    aggregationId: '<AGGREGATION_ID>',
    limit: 25,
    offset: 0,
});
```

Paginate with `limit` and `offset` when an organization has many projects. Map each project row to a customer in your platform database.

### Plan limits and project selection

When your platform creates multiple projects per organization, use `updateSelectedProjects` to control which projects are active on the current plan:

```server-nodejs
await organizations.updateSelectedProjects(
    '<ORGANIZATION_ID>',
    ['projectId1', 'projectId2'],
);
```

Check plan limits from `getPlan` before creating new customer workspaces. See [Provisioning](/docs/partners/guides/provisioning).

### Partner patterns

- **Reselling**: combine `listUsage` with your billing system to invoice customers based on Appwrite consumption
- **Multi-tenant dashboards**: show each customer their project usage from aggregation breakdowns
- **Quota enforcement**: block provisioning when usage approaches plan limits from `getPlan`

**Appwrite Cloud**

Usage and billing APIs are available on Appwrite Cloud. Self-hosted deployments do not expose organization billing or usage endpoints.

### Related

[Organization API](/docs/partners/organizations)

---

## Agent

URL: https://appwrite.io/docs/products/agent

Appwrite **Agent** is the AI assistant built into the Appwrite Cloud Console. It sits next to the work you are already doing: the organization and project you have open, the page you are on, and the resources you are configuring. You ask in natural language; the Agent answers with Appwrite-aware guidance, asks clarifying questions when a request is ambiguous, and (when authorized) takes actions through [Appwrite MCP](/docs/tooling/ai/mcp-servers).

You can keep the Agent as a right-hand panel while you stay on a Console page, or move to the fullscreen experience at `/agent` for longer sessions. Conversations, models, memory, MCP authorization, and automations are stored on your Console account, not on a single project.

**Appwrite Cloud**

Agent is available on Appwrite Cloud. Self-hosted deployments do not include Appwrite Agent.

### How it works

Each turn is more than a single chat reply. The Console and backend combine several layers so answers stay grounded in where you are and what you allow the Agent to do.

1. **Console context**: Every message can carry the current organization, project, page path, page title, and URL. Stay on the project or screen you mean before you ask, or pick a project in the Agent composer when you start a new thread. New conversations need a resolvable project so the Agent has a default scope.
2. **Conversation state**: You chat in natural language, attach files, use voice input where the browser supports it, stop or retry a run, edit and resend earlier messages, and leave feedback on replies. While a turn is running, further messages wait in a short queue so you can keep typing without losing intent.
3. **Clarification**: When a request is ambiguous or risky, the Agent can pause and ask structured questions (choice, confirm, or free text). Your answers continue the same turn instead of starting a new one.
4. **Tools via MCP**: With [MCP](/docs/products/agent/mcp) connected and enabled, the Agent can list tools and call Appwrite APIs with the scopes you grant as **Appwrite Agent**. Without MCP, it still explains concepts and walks you through the Console; it cannot mutate project data through tools.
5. **Console actions**: Beyond tool results, the Agent can navigate routes, open create flows and system dialogs, refresh lists after changes, and render resource cards or usage charts inline in the chat.

The empty-state suggestions reflect that split. Before MCP is ready, chips lean toward how-to questions. After an enabled MCP connection can run tools, they switch to action-oriented prompts such as listing databases or creating a table.

### When to use the panel vs fullscreen

Use the **panel** when you want help while staying on a specific page: a settings form, a TablesDB spreadsheet, a failed deployment. Context follows the route you leave open.

Use **fullscreen** (`/agent`) when the conversation is the main task: long troubleshooting, reviewing automation runs, managing settings for models, memory, and MCP, or jumping between several threads. The same conversations appear in both places.

### Getting started

Open the Agent, send a first prompt with your current project in scope, then connect Appwrite MCP if you want the Agent to list or change resources for you.

[Quick start](/docs/products/agent/quick-start)

### Concepts

Core ideas behind conversations, tools, models, and scheduled runs.

- [Conversations](/docs/products/agent/conversations): Threads, context, attachments, voice, queueing, pinning, and archive.
- [Actions](/docs/products/agent/actions): Answers, clarification, MCP tools, and Console UI the Agent can drive.
- [MCP connections](/docs/products/agent/mcp): How Appwrite MCP authorization unlocks project tools for the Agent.
- [Models](/docs/products/agent/models): Appwrite default models and bring-your-own provider keys.
- [Memory](/docs/products/agent/memory): Preferences, instructions, and facts reused across chats.
- [Automations](/docs/products/agent/automations): Cron-scheduled prompts that create a new conversation on each run.

### Guides

Step-by-step guides for common Agent tasks in the Console.

- [Chat with the Agent](/docs/products/agent/chat): Open the panel or fullscreen Agent, send prompts, and manage a turn.
- [Connect Appwrite MCP](/docs/products/agent/connect-mcp): Authorize the hosted MCP server so the Agent can take project actions.
- [Add a custom model](/docs/products/agent/add-model): Register a provider API key and pick it for a conversation.
- [Add memory](/docs/products/agent/add-memory): Save a preference, instruction, or fact for future chats.
- [Create an automation](/docs/products/agent/create-automation): Schedule a recurring prompt with a cron schedule and optional model.

### Related AI tooling

Appwrite Agent is the in-Console assistant. The same platform also ships an MCP server and skills for coding agents outside the Console. Use Agent when you are operating a project in the UI; use external MCP and skills when you are writing code in an IDE.

- [MCP server](/docs/tooling/ai/mcp-servers): Hosted MCP for API and docs, for external coding agents and tools.
- [Agent skills](/docs/tooling/ai/skills): Installable Appwrite knowledge packs for coding agents across SDKs.
- [IDE agents](/docs/tooling/ai/agents/cursor): Connect Cursor, Claude Code, Codex, and other IDEs to Appwrite MCP.
- [Appwrite Arena](/docs/tooling/ai/arena): Open-source benchmark of how well models understand Appwrite.

---

## Actions

URL: https://appwrite.io/docs/products/agent/actions

The Agent has two modes of helpfulness. With documentation and Console context alone, it can explain Appwrite and guide you through setup. With [MCP](/docs/products/agent/mcp) connected, it can also call tools against your projects and drive parts of the Console UI so lists, wizards, and charts stay in sync with what just happened.

You stay in control of anything that changes project data. Ambiguous or destructive work should surface as clarification before it proceeds.

### Answer and guide

Without tools, the Agent is still useful as a grounded guide. It can explain Appwrite concepts, walk through how-to steps for Auth, Databases, Storage, Functions, Sites, and other products, and use the page you have open to keep answers relevant.

Empty-state suggestions reflect this mode: how-to chips such as creating a database, until an enabled MCP connection can run tools. Then the empty state shifts toward action prompts and a "what should we do next" framing.

### Clarification

Before ambiguous or risky work, the Agent may pause the turn and ask you to:

| Prompt | When it appears |
|--------|-----------------|
| **Choice** | Pick one or more options from a list (for example which database or which column type) |
| **Confirm** | Approve an action; higher-risk confirms can use stronger visual emphasis |
| **Text** | Supply missing details in a single line or multiline field |

Answers are structured for that turn; they continue the same run instead of starting a new conversation. Required prompts must be answered before the Agent continues, unless a prompt is explicitly optional.

### MCP tools

With **Appwrite MCP** connected and enabled, the Agent can discover available tools, call Appwrite APIs with your OAuth authorization, and summarize create, update, delete, and read operations in the conversation timeline.

Typical work includes listing databases, tables, buckets, and users; creating or updating resources (for example a todos table with columns); searching Appwrite documentation while you stay in context; and inspecting usage or related project data the tools expose.

Action-oriented suggestions appear when **any** enabled MCP connection has valid authorization for tools. See [MCP connections](/docs/products/agent/mcp) and [Connect Appwrite MCP](/docs/products/agent/connect-mcp).

Successful tool mutations often trigger Console [refresh actions](#console-ui-actions) so the page you already have open updates without a full reload. Resource cards and charts can also appear inline so you can jump straight to the thing that changed.

### Console UI actions

Separately from MCP API calls, the Agent can emit Console actions that the UI applies locally. These keep the chat and the Console in the same workflow: navigate to the right screen, open the right create flow, or show a chart next to the explanation.

| Action | Effect |
|--------|--------|
| Navigate | Open a Console route (projects, settings, resource pages) |
| Open create | Start create flows for databases, buckets, users, teams, functions, or sites |
| Open dialog | Open system dialogs such as invite member, create project, connect MCP, shortcuts, docs search, feedback, or support |
| Toast | Show success, error, info, or warning notifications |
| Refresh | Refetch lists for databases, tables, buckets, files, users, teams, functions, sites, messaging, deployments, domains, keys, project, organization, and related Console data |
| Theme | Switch light, dark, or system theme |
| Show pane | Open or close the Agent or docs pane |
| Toggle terminal | Open or close the Console terminal where available |
| Resource cards | Render resource or list cards in chat with deep links |
| Charts | Render usage charts (area or bar; event or gauge series; intervals such as `1m`, `15m`, `30m`, `1h`, `1d`) |

Some of these actions can be replayed from conversation history as CTAs (for example navigate again or reopen a dialog), so you can re-enter the same Console flow without retyping the request.

### TablesDB helpers

In TablesDB spreadsheet and workspace views, when the Agent feature is enabled, you may see **Suggest columns** and **Suggest indexes**. Those helpers use Console AI APIs on the current table to propose schema or indexes. They are related to the same AI capability, but they are not the same as chatting through MCP: you apply changes from the TablesDB UI, and write access to the table is still required.

### Permissions and safety

Agent settings (conversations, models, memory, MCP tokens, automations) are scoped to **your Console user**. Live project mutations go through **MCP OAuth** using the authorization you grant as **Appwrite Agent**, typically with broad project and organization access so tools can list and act across resources you can already manage in the Console.

The Agent should ask for clarification before risky or unclear work. Always review proposed changes when you are unsure, especially deletes and permission updates.

[Connect Appwrite MCP](/docs/products/agent/connect-mcp)

---

## Add memory

URL: https://appwrite.io/docs/products/agent/add-memory

Add memory when you want the Agent to remember standing guidance without repeating it in every chat. Use preferences for reply style, instructions for org conventions, and facts for stable stack details. Do not put one-off incident context or secrets into memory.

### Add memory

1. Sign in to [Appwrite Cloud](https://cloud.appwrite.io).
2. Go to `/agent/settings/memory`, or open **Agent** > **Settings** > **Memory**.
3. Click **Add memory**.
4. Enter a **key** (short label; fixed after create) and **content**.
5. Choose a category: **Preference**, **Instruction**, or **Fact**.
6. Optionally set a **priority** so important memories are kept first when context space is limited.
7. Leave the memory **active**, then save.

Memories you create here are scoped to your Console user. Keep the content specific enough that the Agent will not over-generalize across unrelated projects.

### Update or archive

Open an existing memory to change its content, category, priority, or active state. Archive a memory to stop using it without deleting it (for example when a temporary preference should not apply). Delete it when you no longer need it.

If two memories conflict, raise the priority of the one you want, or archive the outdated note.

### Verify it is used

1. Start a new conversation (or continue an existing one).
2. Ask something that depends on the memory (for example a preference about answer format).
3. Confirm the Agent follows the saved guidance.

If it does not, check that the memory is active, that its content is unambiguous, and that a higher-priority memory is not overriding it. See [Memory](/docs/products/agent/memory) for categories and writing tips.

---

## Add a custom model

URL: https://appwrite.io/docs/products/agent/add-model

Add a custom model when you want conversations or automations to run on a provider and model you control. Leave the picker on Appwrite default when you do not need a specific vendor. Custom models are useful when your team already has OpenAI, Anthropic, Google, OpenRouter, or Azure keys, or when you expose an OpenAI-compatible endpoint.

### Add a model

1. Sign in to [Appwrite Cloud](https://cloud.appwrite.io).
2. Go to `/agent/settings/models`, or open **Agent** > **Settings** > **Models**.
3. Click **Add model**.
4. Choose a provider (OpenAI, Anthropic, Google, OpenRouter, Azure, or Custom).
5. Enter the API key and model id. Adjust the base URL if your provider requires a non-default endpoint.
6. Save the model.

Prefer a least-privilege key and a clear name so you can tell models apart in the picker later. If the model id is not in the preset list, enter it manually.

### Use the model

1. Open a conversation in the Agent panel or at `/agent`.
2. Open the model picker.
3. Select your custom model. The selection is saved on that conversation.
4. Send a prompt as usual.

For automations, choose the model when you [create or update the automation](/docs/products/agent/create-automation). Each run of that automation uses the model you configured.

### Update or remove

From the models list you can enable or disable a model, update its settings, or delete it. Disabling hides it from selection without deleting the saved configuration. That is the safest option while you rotate a key: update the key, confirm a test chat works, then leave the model enabled.

See [Models](/docs/products/agent/models) for supported providers and privacy notes. When you use a custom provider, that provider’s data policies apply to prompts and responses sent through the model.

---

## Automations

URL: https://appwrite.io/docs/products/agent/automations

**Automations** run Agent prompts on a schedule. Each run creates a **new conversation** with your prompt, optional model, and project context so you can review the result later the same way you read an ordinary Agent thread.

Use automations for recurring checks you would otherwise open the Console to do by hand: weekly summaries, deployment hygiene, or "what changed?" reviews for a project you do not visit every day. Manage them from `/agent/automations` or the Agent automations panel.

### What an automation contains

| Field | Purpose |
|-------|---------|
| Name | Label in the automations list |
| Prompt | The message sent to the Agent on each run |
| Schedule | Cron expression (UI helpers cover common weekly and daily patterns; default is weekly on Monday) |
| Title prefix | Optional prefix for conversation titles created by the run |
| Model | Optional custom or default model for the run |
| Enabled | Whether the schedule is active |
| Context project | Project used as context when the automation runs (resolved the same way as chat project context) |

### How runs work

When the schedule fires, Appwrite creates a new conversation linked to the automation, then processes the prompt with the configured model and project context. You open that conversation to read the output, tool activity, and any resource cards or charts.

Automation conversations are **not** mixed into the main Agents list. They stay under the automation so scheduled noise does not bury your ad-hoc threads. Opening a run by URL still works if you have the conversation id.

Disable an automation to pause the schedule without deleting it. Enable it again when you want runs to resume. Delete an automation when you no longer need the schedule or its configuration.

### Writing a good automation prompt

Write the prompt as if you were messaging the Agent once, with enough detail that it does not need you present to clarify. Name the project concerns you care about, the format you want (short bullets, table, checklist), and whether the run should only report or also take actions.

Runs that only need guidance can work without MCP. Runs that must list or change live resources need [Appwrite MCP](/docs/products/agent/mcp) connected and enabled on the account that owns the automation. There is no separate MCP toggle on the automation itself; it uses your account’s Agent MCP connection at run time.

### Example use cases

**Weekly Auth review**: Ask for a short summary of recent signups, verification issues, and anything that looks like abuse, formatted as a checklist you can skim on Monday.

**Deployment hygiene**: Ask whether Functions or Sites have failed deployments, stale builds, or domains that need attention, and link to the resources that need a human.

**Quiet project pulse**: For a project you do not open daily, ask what changed in storage, databases, or messaging since the last check, and keep the answer short unless something looks wrong.

[Create an automation](/docs/products/agent/create-automation)

---

## Chat with the Agent

URL: https://appwrite.io/docs/products/agent/chat

Chat is the primary way you use the Appwrite Agent. The same conversations appear in the side panel and in fullscreen; choose the shell that matches the job. Stay in the panel when the Console page is still your focus. Switch to fullscreen when the thread itself is the work.

### Open the Agent

##### Right-hand panel

1. Sign in to [Appwrite Cloud](https://cloud.appwrite.io).
2. Click the **Agent** control in the Console header.
3. The Agent panel opens on the right. Your panel open state can persist in account preferences.

##### Fullscreen

1. Go to `/agent` (or `/agent/{conversationId}` for a specific thread).
2. Use the conversation list, composer, settings, and automations from the dedicated Agent shell.

**Keyboard shortcuts**

The Console includes shortcuts to create a new agent conversation, create an automation, and focus the composer. Open the shortcuts dialog from the Console for the current key bindings.

### Start or continue a conversation

1. Click **Create agent** for a new thread, or select an existing conversation.
2. Confirm a project is in scope (from the route or the composer project selector). New conversations need a resolvable project.
3. Optionally choose a [model](/docs/products/agent/models) from the model picker. The choice is saved on the conversation.
4. Type a prompt, pick a suggestion chip, or use voice input. Optionally attach files before sending.
5. Send the message and watch the reply and tool timeline update live.

Suggestion chips change with MCP readiness: how-to prompts when tools are unavailable, action prompts when an enabled MCP connection can run tools.

### Work with a turn

While the Agent is working, the turn owns the conversation. **Stop** cancels an in-progress run. **Retry** asks the Agent to try again after a failure or stop. **Edit and resend** changes a prior user message and continues from that point.

If you keep typing while a turn is running, follow-ups wait in a short queue and send when the run finishes. You can adjust or drop queued items before they go out.

When the Agent needs a decision, [clarifying prompts](/docs/products/agent/actions#clarification) may appear mid-turn. Answer them to continue the same run rather than starting a new chat.

After a reply, you can copy text, use text-to-speech where supported, or leave thumbs up / thumbs down feedback.

### Tips for better results

Ground the Agent in the right place before you ask. Stay on the project or page you mean, or set the project in the composer. Be specific: include resource names, error text, and the outcome you want.

Connect [Appwrite MCP](/docs/products/agent/connect-mcp) when you want the Agent to list or change resources. Save standing preferences in [memory](/docs/products/agent/memory) instead of repeating them every chat. For one-off context (this incident, this screenshot), put it in the message or an attachment rather than in memory.

For voice, dictate your prompt, then say **submit now**. A short countdown lets you cancel if the transcript is wrong before the message sends.

[Connect Appwrite MCP](/docs/products/agent/connect-mcp)

---

## Connect Appwrite MCP

URL: https://appwrite.io/docs/products/agent/connect-mcp

Connecting **Appwrite MCP** lets the Agent call Appwrite tools with the authorization you grant as **Appwrite Agent**. Without this connection, the Agent can still answer questions and guide you through the Console. With it enabled, empty-state suggestions become action-oriented, and the conversation timeline can show live tool activity.

On Appwrite Cloud, the Console often connects Appwrite MCP automatically when you open the Agent, using your existing session. Use this guide when the connection is missing, shows **Reconnect**, or you want to disable or disconnect deliberately.

### Connect

1. Sign in to [Appwrite Cloud](https://cloud.appwrite.io).
2. Open the Agent panel, or go to `/agent/settings/mcp`.
3. Find **Appwrite MCP**.
4. Click **Connect**.
5. If the Console can authorize silently, the badge moves to **Connected** without a consent screen. Otherwise complete the OAuth flow in the popup (or full-page redirect if the popup is blocked).
6. Confirm the badge shows **Connected** and the connection is enabled.

You can also start connect from Agent UI entry points that prompt you to connect MCP before running an action (for example an open-dialog Console action).

### Reconnect

Reconnect when the connection shows **Reconnect**, after you revoke the Appwrite Agent authorization from account security, or when the stored MCP URL no longer matches the endpoint the Console expects.

1. Open **Agent** > **Settings** > **MCP**.
2. Choose **Reconnect** for Appwrite MCP.
3. Complete authorization again if prompted.

After a successful reconnect, try a simple read prompt such as listing databases in the current project to confirm tools work.

### Disconnect or disable

**Disable** the connection to stop tool use without removing the saved connection. Action suggestions and tool calls stay off until you enable it again.

**Delete** or disconnect when you want to remove the MCP connection from your Agent settings entirely. You can connect again later with the steps above.

### After connecting

Empty-state suggestions switch toward actions you can take in the current project, for example listing databases and tables, reviewing recent Auth users, inspecting storage buckets, or creating a todos table with columns.

Review tool activity in the conversation timeline. When the Agent asks a clarifying question before a write, answer it in the same turn. Successful mutations may refresh open Console lists so you see the change without reloading the page.

For using the same MCP server from IDEs and other clients, see the [MCP server](/docs/tooling/ai/mcp-servers) docs. Authorizing Appwrite Agent does not configure those external clients; each client has its own OAuth setup.

[MCP connections](/docs/products/agent/mcp)

---

## Conversations

URL: https://appwrite.io/docs/products/agent/conversations

A **conversation** is a chat thread with the Appwrite Agent. Each conversation belongs to your Console account. You can keep many threads, pin the ones you return to often, archive ones you are done with, and open any thread from the right-hand panel or at `/agent`.

Conversations created by [automations](/docs/products/agent/automations) are linked to the automation that produced them. Those runs do not appear in the main Agents list; open them from the automations views so scheduled work stays separate from ad-hoc chats.

### What a conversation contains

| Piece | Purpose |
|-------|---------|
| Messages | User and assistant turns, including edits, retries, and scores |
| Timeline | Tool calls and multi-step activity for a turn |
| Attachments | Files you upload for the Agent to use in context |
| Model | The LLM selected for the thread (Appwrite default or a custom model) |
| Status | Active or archived; archive hides the thread from the main active list |

### Context on every message

Each send (and each edit-and-resend) can include Console context so answers match where you are working:

| Field | Typical source |
|-------|----------------|
| Organization / team | Current org route, or the project's organization |
| Project | Composer project selector, conversation project, or the project route |
| Page path, title, and URL | The Console page open when you send |

Stay on the project or page you care about before asking project-specific questions. If you start a new conversation from a place with no project in the URL, pick a project in the composer. New threads need a resolvable project; without one, the Agent cannot establish a default scope.

Assistant replies may include placeholders such as `{{APPWRITE_PROJECT_ID}}`. The Console resolves those from your current session so copy-paste snippets stay accurate.

### Creating and managing threads

**Create agent** starts a new conversation. In the fullscreen Agent, the left sidebar lists threads in sections: pinned, active, then archived. In the panel, use the conversation switcher in the Agent chrome.

**Rename** updates the title so the list stays scannable. **Pin** keeps a thread near the top. Pins are stored in your account preferences (including pin order), so they travel with you across browsers once prefs sync. **Archive** removes a thread from the active list without deleting history. The Console does not currently offer an unarchive action in the conversation menu; treat archive as a way to clear the active list when you are finished with a thread. **Delete** permanently removes the conversation.

Keyboard shortcuts (exact bindings are listed in the Console shortcuts dialog) cover creating a new agent conversation, creating an automation, and focusing the composer.

### Messaging and the turn lifecycle

You send text from the composer, optionally with attachments. Images preview inline in the thread; other files show as named attachments. There is no hard file-type filter in the uploader; attach what the Agent needs to reason about (screenshots, configs, logs).

While a turn is in progress, the conversation is locked for that run. **Stop** cancels the in-progress run. **Retry** asks the Agent to try again from a failed or incomplete turn. **Edit and resend** changes a prior user message and continues from that point; the superseded original is folded out of the live history as the edit lands.

If you send again while a turn is still running, the Console queues the follow-up and drains the queue when the current run finishes. You can promote, edit, or remove queued items before they send.

After a reply, you can copy text, speak it aloud with text-to-speech where supported, or leave thumbs up / thumbs down feedback.

### Voice input

Where the browser supports speech recognition, you can dictate into the composer. Say **submit now** to send (localized submit phrases exist for supported UI languages; English "submit now" is always accepted). After the trigger phrase, a short cancelable countdown runs so you can abort before the message sends if the transcript is wrong.

### Clarification mid-turn

When the Agent needs a decision before continuing, it can present structured [clarifying prompts](/docs/products/agent/actions#clarification): choose from options, confirm a risky step, or enter missing text. Your answer continues the same turn. That is intentional: clarification is part of the work, not a new chat.

### Realtime updates

While a turn is running, the Console updates the conversation through realtime events so assistant messages and tool activity appear as they complete. You do not need to refresh the page. Edits and retries merge into the same thread view.

[Chat with the Agent](/docs/products/agent/chat)

---

## Create an automation

URL: https://appwrite.io/docs/products/agent/create-automation

Create an automation when you want the Agent to run the same prompt on a schedule and leave each result in a conversation you can review later. Every run creates a new thread linked to the automation, so history stays organized outside the main Agents list.

Write the prompt as if you will not be present to clarify. Name what to inspect, how to format the answer, and whether the run should only report or also take actions.

### Create

1. Sign in to [Appwrite Cloud](https://cloud.appwrite.io).
2. Go to `/agent/automations` (or `/agent/automations/create`).
3. Click the create action if you are on the list view.
4. Enter a **name**.
5. Write the **prompt** the Agent should run each time.
6. Set a **schedule** with the cron editor (for example weekly on Monday; that is also the default).
7. Optionally set a **title prefix**, choose a **model**, and confirm a **context project** if the prompt is project-specific.
8. Leave **Enabled** on, then save.

### Review a run

When the schedule fires, open the automation and its linked conversations. Read the Agent output, tool activity, and any inline resource cards or charts the same way you would in an ordinary chat. Runs do not appear in the main Agents sidebar.

### Update or pause

1. Open the automation from `/agent/automations/{automationId}` or the automations panel.
2. Change the prompt, schedule, model, context project, or enabled state.
3. Save your changes.

Disable the automation to pause future runs without deleting it. Re-enable when you want the schedule to resume.

**MCP for live data**

Automations that inspect or change project resources need [Appwrite MCP](/docs/products/agent/mcp) connected and enabled on the account that owns the automation. Guidance-only prompts can run without tools.

See [Automations](/docs/products/agent/automations) for fields, run behavior, and example prompts.

---

## MCP connections

URL: https://appwrite.io/docs/products/agent/mcp

**MCP connections** tell the Agent which [Model Context Protocol](https://modelcontextprotocol.io) servers it can use for tools. For Appwrite Agent, the primary connection is **Appwrite MCP**, the hosted server that lets the Agent take actions in your Appwrite projects with your account.

Without a connected, enabled server, the Agent still answers questions and guides you through the Console. With Appwrite MCP ready, it can list tools and call Appwrite APIs, and empty-state suggestions shift from how-to chips to action-oriented prompts.

Manage the connection under **Agent** > **Settings** > **MCP**, or from the MCP controls in the Agent UI.

### Appwrite MCP

**Appwrite MCP** is listed by default. Its job is to let the Agent call Appwrite tools after you authorize the **Appwrite Agent** OAuth client. The hosted endpoint is `mcp.appwrite.io` by default (the same surface used by [external IDE agents](/docs/tooling/ai/mcp-servers)).

| State | Meaning |
|-------|---------|
| Not connected | The Agent can answer questions but cannot run Appwrite project tools |
| Connected | Tools are available when the connection is enabled |
| Reconnect | The previous authorization expired, was revoked, or no longer matches the expected MCP URL; connect again |

On Appwrite Cloud, opening the Agent often connects Appwrite MCP **silently** using your existing Console session. If silent authorization is not possible, the Console falls back to an interactive OAuth flow (popup, or full-page redirect if the popup is blocked). See [Connect Appwrite MCP](/docs/products/agent/connect-mcp).

**Same MCP, different clients**

Appwrite MCP is shared infrastructure. Appwrite Agent is a first-party Console client. Cursor, Claude Code, and other IDEs connect to the same hosted MCP with their own OAuth clients. Authorizing the Agent does not replace IDE setup, and IDE setup does not replace the Agent connection.

### What authorization grants

Connecting Appwrite MCP grants the Agent OAuth access so tools can act with your identity. The Console requests standard identity scopes plus project and organization access, and binds that access with authorization details so tokens can actually list and operate on projects (not only appear broadly permitted in a consent screen).

In practice: if tools fail to list projects after a connect that looked successful, reconnect from **Settings** > **MCP** so authorization is refreshed with the current Appwrite MCP URL and client configuration.

### Enabling and disabling

Each connection can be enabled or disabled without deleting it. Disabled connections stay on your account but are not used for tool calls, and they do not unlock action-oriented empty-state suggestions. Use disable when you want to pause tool use temporarily (for example while you only want guidance, not mutations).

### Other MCP connections

The Agent settings list shows Appwrite MCP and any other MCP connections already stored on your account. Connection status, enable/disable, and disconnect apply to those rows. For building with MCP outside Appwrite Agent, see the [MCP server](/docs/tooling/ai/mcp-servers) tooling docs.

### How tools show up in chat

When the Agent calls tools, the conversation timeline shows tool activity for the turn: which tools ran, and summaries of create, update, delete, or read outcomes. Successful mutations may also trigger [Console refresh actions](/docs/products/agent/actions#console-ui-actions) so lists update without a full page reload. Resource cards and charts can appear inline so you can open the affected resource from the chat.

[Connect Appwrite MCP](/docs/products/agent/connect-mcp)

---

## Memory

URL: https://appwrite.io/docs/products/agent/memory

**Memory** stores durable notes the Agent can reuse across chats so you do not restate the same guidance every time. Use it for how you want replies shaped, standing rules for your org’s Appwrite usage, and stable facts about your stack.

Manage memory under **Agent** > **Settings** > **Memory**. Memories you create in the Console belong to your user account and apply when they are active. They are not a substitute for project secrets or team-wide policy documents.

### Categories

Pick a category that matches why you are saving the note. The category helps you (and the Agent) treat the content as style, rule, or fact.

| Category | Use for | Example |
|----------|---------|---------|
| Preference | How you want the Agent to respond | `Prefer short answers with numbered steps` |
| Instruction | Standing rules when helping you | `Always use TablesDB terminology, not collections, for this org` |
| Fact | Stable information about apps or conventions | `Production project region is fra` |

Each memory has a **key** (short label, fixed after create), **content**, optional **priority**, and an active or archived status.

### Priority and status

When many memories exist, the Agent may not be able to include every note in every turn. **Priority** controls which memories are kept first when space is limited: higher values are preferred.

**Active** memories are available to the Agent. **Archived** memories stay saved but are not included until you activate them again. Archive is the right tool when a preference is temporarily wrong (for example during an incident war room) without deleting the text.

### Writing memories that help

Good memories are specific and stable. Prefer one clear rule per memory over a long essay that mixes tone, naming, and infrastructure. Keys should be scannable in the settings list (`reply-style`, `tablesdb-terms`, `prod-region`).

Avoid storing secrets (API keys, passwords, tokens, private connection strings) in memory. Use project settings, environment variables, and secure secret storage instead. Memory is meant for guidance the Agent can safely reuse in prompts, not for credentials.

If two memories conflict (for example one says "be brief" and another says "always explain in detail"), raise the priority of the one you want to win, or archive the outdated note.

### How memory relates to a single chat

Memory is for standing guidance. For one-off context (this bug, this screenshot, this deploy), put the details in the conversation or attach a file. Mixing ephemeral incident detail into memory makes future chats noisier.

[Add memory](/docs/products/agent/add-memory)

---

## Models

URL: https://appwrite.io/docs/products/agent/models

**Models** control which large language model the Agent uses for a conversation. Most of the time you can leave the picker on **Appwrite default** and focus on the prompt. When you need a specific provider, latency profile, or model family, register your own credentials under **Agent** > **Settings** > **Models** and select that model on the thread.

The choice is stored on the conversation (and optionally on an [automation](/docs/products/agent/automations)), so different threads can use different models without changing a global default.

### Default vs custom

**Appwrite default** uses the models Appwrite provides in the Agent model picker. You do not add API keys. This is the right starting point for everyday Console work.

**Custom** models use provider credentials you register. The Agent calls that provider with your key for conversations (or automation runs) that select the model. Use custom models when you want a particular OpenAI, Anthropic, Google, OpenRouter, Azure, or OpenAI-compatible endpoint, or when your organization already standardizes on a vendor.

Temperature is managed for you: everyday chat uses a low default for steadier Console guidance, and certain reasoning-oriented model families are normalized to a fixed higher temperature so you do not have to tune knobs per send.

### Supported providers

When adding a custom model, choose a provider and fill in the API key, model id, and optional base URL.

| Provider | Examples |
|----------|----------|
| OpenAI | GPT-4o, GPT-4.1, o3, o4-mini |
| Anthropic | Claude Sonnet, Opus, Haiku |
| Google | Gemini 2.5 Pro, Gemini 2.5 Flash |
| OpenRouter | Routed models across providers |
| Azure | Azure OpenAI deployments |
| Custom | Any OpenAI-compatible endpoint |

Exact preset lists in the Console change as providers ship new versions. If a model id is not in the preset list, you can usually enter it manually.

### Managing models

From **Settings** > **Models** you add a model with provider, credentials, and model id; enable or disable a saved model; and update or delete an existing one.

Disabled models remain saved but are hidden from the conversation picker until you enable them again. That is useful when you rotate keys or temporarily stop sending traffic to a provider without losing the configuration.

The model picker searches and pages through your enabled models so long lists stay usable. Selecting a model updates the current conversation immediately; the next send uses that choice.

### Privacy and keys

Custom API keys are stored for your Console account so the Agent can call the provider you chose. Prefer least-privilege keys, restrict them by provider project where possible, and rotate them if they are exposed.

When you use a custom provider, that provider’s data policies apply to the prompts, context, tool summaries, and responses sent through that model. Appwrite default models keep you on Appwrite-managed routing; custom models move generation to the vendor behind your key.

[Add a custom model](/docs/products/agent/add-model)

---

## Start with Agent

URL: https://appwrite.io/docs/products/agent/quick-start

This quick start gets you from a signed-in Console session to a useful first reply, then optionally unlocks live project actions. You do not need MCP to ask how-to questions. You do need it if you want the Agent to list or change resources for you.

### Open the Agent

1. Sign in to [Appwrite Cloud](https://cloud.appwrite.io).
2. Open any project you can access (or stay on an organization page and pick a project in the Agent composer).
3. Click the **Agent** control in the Console header to open the right-hand panel.

For longer sessions, go to `/agent` for the fullscreen Agent. Guests see a sign-in prompt before chat is available.

**Feature availability**

If you do not see Agent, confirm you are on Appwrite Cloud and that the AI agent feature is enabled for your Console profile.

New conversations need a project in scope. If the Agent cannot resolve one from the route, the composer selector, or your accessible projects, it will ask you to pick one before the first message sends.

### Ask a question

1. Click a suggestion chip, or type a question in the composer (for example `How do I create a new database?`).
2. Send the message.
3. Read the reply. The Agent uses your current organization, project, and page as context, so answers tend to match the screen you left open.

A good first prompt is concrete: name the resource type, the outcome you want, and any error text you already have. Vague prompts still get a reply, but they produce more clarification turns.

You can attach files, use voice input where supported, stop a run in progress, retry a failed turn, or edit and resend a previous message. See [Chat with the Agent](/docs/products/agent/chat) for the full turn lifecycle.

### Connect Appwrite MCP (optional)

Connecting [Appwrite MCP](/docs/tooling/ai/mcp-servers) lets the Agent call Appwrite tools with scopes you grant as **Appwrite Agent**. On Appwrite Cloud, the Console often establishes this connection automatically when you open the Agent. If the connection is missing or shows **Reconnect**, connect manually:

1. In the Agent panel or at **Agent** > **Settings** > **MCP**, find **Appwrite MCP**.
2. Click **Connect** and complete authorization if the Console prompts you.
3. Confirm the connection shows **Connected**.

With MCP enabled, try an action prompt such as `List the databases and tables in this project` or `Create a todos table with title and done columns`. Review tool activity in the conversation timeline, and answer any clarifying prompts before changes apply.

See [Connect Appwrite MCP](/docs/products/agent/connect-mcp) for reconnect, disable, and disconnect.

### Next steps

Once the basic loop works, deepen the Agent for how you work day to day.

- [Add memory](/docs/products/agent/add-memory): Save preferences and standing instructions so you do not repeat them every chat.
- [Add a custom model](/docs/products/agent/add-model): Use your own provider keys when you want a specific model for a thread.
- [Create an automation](/docs/products/agent/create-automation): Schedule a recurring prompt and review each run as its own conversation.

---

## Artificial intelligence

URL: 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

Detailed explanations and deep dives into how you can implement different machine techniques in your Appwrite projects.

- [Computer vision](/docs/products/ai/computer-vision): Label and understand the contents of images
- [Natural language processing](/docs/products/ai/natural-language): Understand and generate human language
- [Audio processing](/docs/products/ai/audio-processing): Process and generate audio data

### Show me some code
If you learn best from code examples, follow one of our tutorials.

##### Computer vision

- [Image classification](/docs/products/ai/tutorials/image-classification): Understand and label the contents of images
- [Object detection](/docs/products/ai/tutorials/object-detection): Detect and label objects in images

##### Natural language

- [Text generation](/docs/products/ai/tutorials/text-generation): Generate human-like text
- [Language translation](/docs/products/ai/tutorials/language-translation): Translate text between languages

##### Audio processing

- [Speech recognition](/docs/products/ai/tutorials/speech-recognition): Process speech audio into text
- [Text to speech](/docs/products/ai/tutorials/text-to-speech): Convert text into speech
- [Music generation](/docs/products/ai/tutorials/music-generation): Generate music from a text prompt

---

## Audio processing

URL: 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

- [Speech recognition](/docs/products/ai/tutorials/speech-recognition): Recognize and transcribe spoken language into text
- [Text to speech](/docs/products/ai/tutorials/text-to-speech): Convert written text into spoken language

---

## Computer vision

URL: 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

- [Image classification](/docs/products/ai/tutorials/image-classification): Understand and label the contents of images
- [Object detection](/docs/products/ai/tutorials/object-detection): Detect and label objects in images

---

## Integrating Anyscale

URL: 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

- An Appwrite Project
- An [Anyscale API Key](https://app.endpoints.anyscale.com/credentials)

#### 1. 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.

![Create function screen](/images/docs/functions/template.avif)

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.

#### 2. 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
```

#### 3. 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();
}
```

#### 4. 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.

#### 5. 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
<!doctype html>
  <html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Anyscale Demo</title>

    <script>
      async function onSubmit(prompt) {
        const response = await fetch('/', {
          method: 'POST',
          body: JSON.stringify({ prompt }),
          headers: {
            'Content-Type': 'application/json',
          },
        });

        const json = await response.json();

        if (!json.ok || json.error) {
          alert(json.error);
        }

        return json.completion;
      }
    </script>

    <script src="//unpkg.com/alpinejs" defer></script>

    <link rel="stylesheet" href="https://unpkg.com/@appwrite.io/pink" />
    <link rel="stylesheet" href="https://unpkg.com/@appwrite.io/pink-icons" />
  </head>
</html>
```

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 `</head>` tag add a `<body>` containing the visible form:

```html
<body>
  <main class="main-content">
    <div class="top-cover u-padding-block-end-56">
      <div class="container">
        <div
          class="u-flex u-gap-16 u-flex-justify-center u-margin-block-start-16"
        >
          <h1 class="heading-level-1">Prompt Anyscale Demo</h1>
          <code class="u-un-break-text"></code>
        </div>
        <p
          class="body-text-1 u-normal u-margin-block-start-8"
          style="max-width: 50rem"
        >
          Use this page to test your implementation with Anyscale using Mixtral 8x7B. Enter
          text and receive the model output as a response.
        </p>
      </div>
    </div>
    <div
      class="container u-margin-block-start-negative-56"
      x-data="{ prompt: '', answer: '', loading: false }"
    >
      <div class="card u-flex u-gap-24 u-flex-vertical">
        <div class="u-flex u-cross-center u-gap-8">
          <div
            class="input-text-wrapper is-with-end-button u-width-full-line"
          >
            <input x-model="prompt" type="search" placeholder="Question" />
            <div class="icon-search" aria-hidden="true"></div>
          </div>

          <button
            class="button"
            x-bind:disabled="loading"
            x-on:click="async () => { loading = true; answer = ''; try { answer = await onSubmit(prompt) } catch(err) { console.error(err); } finally { loading = false; } }"
          >
            <span class="text">Submit</span>
          </button>
        </div>
        <template x-if="answer">
          <div class="u-flex u-flex-vertical u-gap-12">
            <div class="u-flex u-flex-vertical u-gap-12 card">
              <div class="u-flex u-gap-12">
                <h5 class="eyebrow-heading-2">Anyscale:</h5>
              </div>

              <div style="overflow-x: hidden; line-break: anywhere">
                <p class="u-color-text-gray" x-text="answer"></p>
              </div>
            </div>
          </div>
        </template>
      </div>
    </div>
  </main>
</body>
```

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.

#### 6. 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);
}
```

#### 8. 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.avif)

---

## Integrating ElevenLabs

URL: 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

- An Appwrite Project
- An [ElevenLabs API Key](https://elevenlabs.io/)

#### 1. 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.

![Create function screen](/images/docs/functions/template.avif)

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.

#### 2. 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
```

#### 3. 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();
}
```

#### 4. 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.

#### 5. 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
<!doctype html>
<html lang="en">
</html>
```

Within the `<html>` tag, Add a `<head>` tag that will define the style and scripts.

```html
<head>
  <meta charset="UTF-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>ElevenLabs Demo</title>

  <script>
    async function onSubmit(prompt) {
      const response = await fetch('/', {
        method: 'POST',
        body: JSON.stringify({ text: prompt }),
        headers: {
          'Content-Type': 'application/json',
        },
      });

      const json = await response.json();

      if (!json.ok || json.error) {
        alert(json.error);
      }

      return json.response;
    }
  </script>

  <script src="//unpkg.com/alpinejs" defer></script>

  <link rel="stylesheet" href="https://unpkg.com/@appwrite.io/pink" />
  <link rel="stylesheet" href="https://unpkg.com/@appwrite.io/pink-icons" />
</head>
```

And after the `</head>` tag add this `<body>` which will contain the actual form:

```html
<body>
  <main class="main-content">
    <div class="top-cover u-padding-block-end-56">
      <div class="container">
        <div
          class="u-flex u-gap-16 u-flex-justify-center u-margin-block-start-16"
        >
          <h1 class="heading-level-1">ElevenLabs Demo</h1>
          <code class="u-un-break-text"></code>
        </div>
        <p
          class="body-text-1 u-normal u-margin-block-start-8"
          style="max-width: 50rem"
        >
          Use this page to test your implementation with ElevenLabs. Enter
          text and receive an audio response.
        </p>
      </div>
    </div>
    <div
      class="container u-margin-block-start-negative-56"
      x-data="{ prompt: '', response: '', loading: false }"
    >
      <div class="card u-flex u-gap-24 u-flex-vertical">
        <div class="u-flex u-cross-center u-gap-8">
          <div
            class="input-text-wrapper is-with-end-button u-width-full-line"
          >
            <input x-model="prompt" type="search" placeholder="Enter text" />
            <div class="icon-search" aria-hidden="true"></div>
          </div>

          <button
            class="button"
            x-bind:disabled="loading"
            x-on:click="async () => { loading = true; response = ''; try { response = await onSubmit(prompt) } catch(err) { console.error(err); } finally { loading = false; } }"
          >
            <span class="text">Generate</span>
          </button>
        </div>
        <template x-if="response">
          <div class="u-flex u-flex-vertical u-gap-12">
            <div class="u-flex u-flex-vertical u-gap-12 card">
              <div class="u-flex u-gap-12">
                <h5 class="eyebrow-heading-2">Output:</h5>
              </div>

              <div>
                <audio x-bind:src="response" controls></audio>
              </div>
            </div>
          </div>
        </template>
      </div>
    </div>
  </main>
</body>
```

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.

#### 6. 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://<REGION>.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.

#### 7. 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.

#### 8. 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.avif)

---

## Integrating fal.ai

URL: 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

- An Appwrite Project
- A [fal.ai API Key](https://fal.ai/docs/authentication/key-based)

#### 1. 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.

![Create function screen](/images/docs/functions/template.avif)

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.

#### 2. 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
```

#### 3. 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();
}
```

#### 4. 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',
    });
  }
};
```

#### 5. 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
<!doctype html>
<html lang="en">
</html>
```

Within the `<html>` tag, add a `<head>` tag with the necessary meta tags, stylesheets, and scripts:

```html
<head>
  <meta charset="UTF-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>fal.ai Demo</title>

  <script>
    async function onSubmit(prompt, type) {
      const response = await fetch('/', {
        method: 'POST',
        body: JSON.stringify({ prompt, type }),
        headers: {
          'Content-Type': 'application/json',
        },
      });

      const json = await response.json();

      if (!json.ok || json.error) {
        alert(json.error);
      }

      return json;
    }
  </script>

  <script src="//unpkg.com/alpinejs" defer></script>

  <link rel="stylesheet" href="https://unpkg.com/@appwrite.io/pink" />
  <link rel="stylesheet" href="https://unpkg.com/@appwrite.io/pink-icons" />
</head>
```

And after the `</head>` tag, add our `<body>` tag with the following content:

```html
<body class="theme-dark">
  <main class="main-content">
    <div class="top-cover u-padding-block-end-56">
      <div class="container">
        <div class="u-flex u-gap-16 u-flex-justify-center u-margin-block-start-16">
          <h1 class="heading-level-1">fal.ai demo</h1>
          <code class="u-un-break-text"></code>
        </div>
        <p class="body-text-1 u-normal u-margin-block-start-8" style="max-width: 50rem">
          Use this page to test your implementation with fal.ai. Enter
          text and receive the model output as a response.
        </p>
      </div>
    </div>
    <div class="container u-margin-block-start-negative-56"
      x-data="{ prompt: '', result: { src: '' }, loading: false }">
      <div class="card u-flex u-gap-24 u-flex-vertical">
        <div class="u-flex u-cross-center u-gap-8">
          <div class="input-text-wrapper is-with-end-button u-width-full-line">
            <input x-model="prompt" type="search" placeholder="Prompt" />
            <div class="icon-search" aria-hidden="true"></div>
          </div>
          <button class="button" x-bind:disabled="loading"
            x-on:click="async () => { loading = true; result = { src: '' }; try { result = await onSubmit(prompt) } catch(err) { console.error(err); } finally { loading = false; } }">
            <span class="text">Generate</span>
          </button>
        </div>
        <template x-if="answer.type">
          <div class="u-flex u-flex-vertical u-gap-12">
            <div class="u-flex u-flex-vertical u-gap-12 card">
              <div class="u-flex u-gap-12">
                <h5 class="eyebrow-heading-2">Result:</h5>
              </div>
              <img class="u-max-width-400" x-bind:src="result.src" alt="fal.ai output" />
            </div>
          </div>
        </template>
      </div>
    </div>
  </main>
</body>
```

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.

#### 6. 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).

#### 7. 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.avif)

---

## Integrating LangChain

URL: 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

#### 1. 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.

![Create function screen](/images/docs/functions/template.avif)

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.

#### 2. 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
```

#### 3. 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();
}
```

#### 4. 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.

#### 5. 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
<!doctype html>
<html lang="en">
</html>
```

Within the `<html>` tag, Add a `<head>` tag that will define the style and scripts.

```html
<head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Pinecone Demo</title>

    <script src="https://unpkg.com/meilisearch@0.34.1"></script>
    <script src="https://unpkg.com/alpinejs" defer></script>

    <link rel="stylesheet" href="https://unpkg.com/@appwrite.io/pink" />
    <link rel="stylesheet" href="https://unpkg.com/@appwrite.io/pink-icons" />
</head>
  
```

And after the `</head>` tag add this `<body>` which will contain the actual form:

```html
<body>
    <main class="main-content">
        <div class="top-cover u-padding-block-end-56">
        <div class="container">
            <div
            class="u-flex u-gap-16 u-flex-justify-center u-margin-block-start-16"
            >
            <h1 class="heading-level-1">Pinecone Demo</h1>
            <code class="u-un-break-text"></code>
            </div>
            <p
            class="body-text-1 u-normal u-margin-block-start-8"
            style="max-width: 50rem"
            >
            Use this demo to verify that the sync between Appwrite Databases and
            Pinecone was successful. Search your Pinecone vector database using
            the input below.
            </p>
        </div>
        </div>
        <div
            class="container u-margin-block-start-negative-56"
            x-data="{ search: '', results: [ ] }"
            x-init="$watch('search', async (value) => { results = await onSearch(value) })"
        >
        <div class="card u-flex u-gap-24 u-flex-vertical">
            <div id="searchbox">
            <div
                class="input-text-wrapper is-with-end-button u-width-full-line"
            >
                <input x-model="search" type="search" placeholder="Search" />
                <div class="icon-search" aria-hidden="true"></div>
            </div>
            </div>
            <div id="hits" class="u-flex u-flex-vertical u-gap-12">
            <template x-for="result in results">
                <div class="card">
                <pre x-text="JSON.stringify(result, null, '\t')"></pre>
                </div>
            </template>
            </div>
        </div>
        </div>
    </main>
    <script>
        window.onSearch = async function (prompt) {
            const response = await fetch('/search', {
                method: 'POST',
                body: JSON.stringify({ prompt }),
                headers: {
                'Content-Type': 'application/json',
                },
            });
            return response.matches;
        };
    </script>
</body>
```

This will render a form that will submit your search query to the function and display the results.

#### 6. 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.

#### 7. 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.

#### 8. 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://<REGION>.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.

#### 9. 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.

---

## Integrating LMNT

URL: 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

- An Appwrite Project
- An Appwrite Bucket
- An [LMNT API Key](https://app.lmnt.com/account)

#### 1. 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.

![Create function screen](/images/docs/functions/template.avif)

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.

#### 2. 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
```

#### 3. 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();
}
```

#### 4. 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.

#### 5. 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
<!doctype html>
<html lang="en">
</html>
```

Within the `<html>` tag, Add a `<head>` tag that will define the style and scripts.

```html
<head>
  <meta charset="UTF-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>LMNT Demo</title>

  <script>
    async function onSubmit(prompt) {
      const response = await fetch('/', {
        method: 'POST',
        body: JSON.stringify({ text: prompt }),
        headers: {
          'Content-Type': 'application/json',
        },
      });

      const json = await response.json();

      if (!json.ok || json.error) {
        alert(json.error);
      }

      return json.response;
    }
  </script>

  <script src="//unpkg.com/alpinejs" defer></script>

  <link rel="stylesheet" href="https://unpkg.com/@appwrite.io/pink" />
  <link rel="stylesheet" href="https://unpkg.com/@appwrite.io/pink-icons" />
</head>
```

And after the `</head>` tag add this `<body>` which will contain the actual form:

```html
<body>
  <main class="main-content">
    <div class="top-cover u-padding-block-end-56">
      <div class="container">
        <div
          class="u-flex u-gap-16 u-flex-justify-center u-margin-block-start-16"
        >
          <h1 class="heading-level-1">LMNT Demo</h1>
          <code class="u-un-break-text"></code>
        </div>
        <p
          class="body-text-1 u-normal u-margin-block-start-8"
          style="max-width: 50rem"
        >
          Use this page to test your implementation with LMNT. Enter
          text and receive an audio response.
        </p>
      </div>
    </div>
    <div
      class="container u-margin-block-start-negative-56"
      x-data="{ prompt: '', response: '', loading: false }"
    >
      <div class="card u-flex u-gap-24 u-flex-vertical">
        <div class="u-flex u-cross-center u-gap-8">
          <div
            class="input-text-wrapper is-with-end-button u-width-full-line"
          >
            <input x-model="prompt" type="search" placeholder="Enter text" />
            <div class="icon-search" aria-hidden="true"></div>
          </div>

          <button
            class="button"
            x-bind:disabled="loading"
            x-on:click="async () => { loading = true; response = ''; try { response = await onSubmit(prompt) } catch(err) { console.error(err); } finally { loading = false; } }"
          >
            <span class="text">Generate</span>
          </button>
        </div>
        <template x-if="response">
          <div class="u-flex u-flex-vertical u-gap-12">
            <div class="u-flex u-flex-vertical u-gap-12 card">
              <div class="u-flex u-gap-12">
                <h5 class="eyebrow-heading-2">Output:</h5>
              </div>

              <div>
                <audio x-bind:src="response" controls></audio>
              </div>
            </div>
          </div>
        </template>
      </div>
    </div>
  </main>
</body>
```

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.

#### 6. 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://<REGION>.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.

#### 7. 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.

#### 8. 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.avif)

---

## Integrating OpenAI

URL: 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

- An Appwrite Project
- An [OpenAI API Key](https://platform.openai.com/account/api-keys)

#### 1. 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.

![Create function screen](/images/docs/functions/template.avif)

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.

#### 2. 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
```

#### 3. 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();
}
```

#### 4. 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.

#### 5. 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
<!doctype html>
  <html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>OpenAI Demo</title>

    <script>
      async function onSubmit(prompt) {
        const response = await fetch('/', {
          method: 'POST',
          body: JSON.stringify({ prompt }),
          headers: {
            'Content-Type': 'application/json',
          },
        });

        const json = await response.json();

        if (!json.ok || json.error) {
          alert(json.error);
        }

        return json.completion;
      }
    </script>

    <script src="//unpkg.com/alpinejs" defer></script>

    <link rel="stylesheet" href="https://unpkg.com/@appwrite.io/pink" />
    <link rel="stylesheet" href="https://unpkg.com/@appwrite.io/pink-icons" />
  </head>
</html>
```

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 `</head>` tag add a `<body>` containing the visible form:

```html
<body>
  <main class="main-content">
    <div class="top-cover u-padding-block-end-56">
      <div class="container">
        <div
          class="u-flex u-gap-16 u-flex-justify-center u-margin-block-start-16"
        >
          <h1 class="heading-level-1">Prompt ChatGPT Demo</h1>
          <code class="u-un-break-text"></code>
        </div>
        <p
          class="body-text-1 u-normal u-margin-block-start-8"
          style="max-width: 50rem"
        >
          Use this page to test your implementation with OpenAI ChatGPT. Enter
          text and receive the model output as a response.
        </p>
      </div>
    </div>
    <div
      class="container u-margin-block-start-negative-56"
      x-data="{ prompt: '', answer: '', loading: false }"
    >
      <div class="card u-flex u-gap-24 u-flex-vertical">
        <div class="u-flex u-cross-center u-gap-8">
          <div
            class="input-text-wrapper is-with-end-button u-width-full-line"
          >
            <input x-model="prompt" type="search" placeholder="Question" />
            <div class="icon-search" aria-hidden="true"></div>
          </div>

          <button
            class="button"
            x-bind:disabled="loading"
            x-on:click="async () => { loading = true; answer = ''; try { answer = await onSubmit(prompt) } catch(err) { console.error(err); } finally { loading = false; } }"
          >
            <span class="text">Submit</span>
          </button>
        </div>
        <template x-if="answer">
          <div class="u-flex u-flex-vertical u-gap-12">
            <div class="u-flex u-flex-vertical u-gap-12 card">
              <div class="u-flex u-gap-12">
                <h5 class="eyebrow-heading-2">ChatGPT:</h5>
              </div>

              <div style="overflow-x: hidden; line-break: anywhere">
                <p class="u-color-text-gray" x-text="answer"></p>
              </div>
            </div>
          </div>
        </template>
      </div>
    </div>
  </main>
</body>
```

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.

#### 6. 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://<REGION>.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);
}
```

#### 8. 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.

---

## Integrating Perplexity

URL: 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

- An Appwrite Project
- A [Perplexity API Key](https://docs.perplexity.ai/docs/getting-started)

#### 1. 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.

![Create function screen](/images/docs/functions/template.avif)

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.

#### 2. 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.

#### 3. 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();
}
```

#### 4. 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',
    });
  }
};
```

#### 5. 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
<!doctype html>
<html lang="en">
</html>
```

Within the `<html>` tag, we're going to add a `<head>` tag that will define our style and scripts.

```html
<head>
  <meta charset="UTF-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Perplexity AI Demo</title>

  <script>
    async function onSubmit(prompt) {
      const response = await fetch('/', {
        method: 'POST',
        body: JSON.stringify({ prompt }),
        headers: {
          'Content-Type': 'application/json',
        },
      });

      const json = await response.json();

      if (!json.ok || json.error) {
        alert(json.error);
      }

      return json.completion;
    }
  </script>

  <script src="//unpkg.com/alpinejs" defer></script>

  <link rel="stylesheet" href="https://unpkg.com/@appwrite.io/pink" />
  <link rel="stylesheet" href="https://unpkg.com/@appwrite.io/pink-icons" />
</head>
```

And after the `</head>` tag we're going to add our `<body>` which will contain the actual form:

```html
<body>
  <main class="main-content">
    <div class="top-cover u-padding-block-end-56">
      <div class="container">
        <div
          class="u-flex u-gap-16 u-flex-justify-center u-margin-block-start-16"
        >
          <h1 class="heading-level-1">Perplexity AI Demo</h1>
          <code class="u-un-break-text"></code>
        </div>
        <p
          class="body-text-1 u-normal u-margin-block-start-8"
          style="max-width: 50rem"
        >
          Use this page to test your implementation with Perplexity AI. Enter
          text and receive the model output as a response.
        </p>
      </div>
    </div>
    <div
      class="container u-margin-block-start-negative-56"
      x-data="{ prompt: '', answer: '', loading: false }"
    >
      <div class="card u-flex u-gap-24 u-flex-vertical">
        <div class="u-flex u-cross-center u-gap-8">
          <div
            class="input-text-wrapper is-with-end-button u-width-full-line"
          >
            <input x-model="prompt" type="search" placeholder="Question" />
            <div class="icon-search" aria-hidden="true"></div>
          </div>

          <button
            class="button"
            x-bind:disabled="loading"
            x-on:click="async () => { loading = true; answer = ''; try { answer = await onSubmit(prompt) } catch(err) { console.error(err); } finally { loading = false; } }"
          >
            <span class="text">Submit</span>
          </button>
        </div>
        <template x-if="answer">
          <div class="u-flex u-flex-vertical u-gap-12">
            <div class="u-flex u-flex-vertical u-gap-12 card">
              <div class="u-flex u-gap-12">
                <h5 class="eyebrow-heading-2">Perplexity AI:</h5>
              </div>

              <div style="overflow-x: hidden; line-break: anywhere">
                <p class="u-color-text-gray" x-text="answer"></p>
              </div>
            </div>
          </div>
        </template>
      </div>
    </div>
  </main>
</body>
```

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.

#### 6. 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.

#### 7. 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.avif)

---

## Integrating Pinecone

URL: 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

- 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

#### 1. 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.

![Create function screen](/images/docs/functions/template.avif)

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.

#### 2. 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
```

#### 3. 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();
}
```

#### 4. 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.

#### 5. 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
<!doctype html>
<html lang="en">
</html>
```

Within the `<html>` tag, Add a `<head>` tag that will define the style and scripts.

```html
<head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Pinecone Demo</title>

    <script src="https://unpkg.com/meilisearch@0.34.1"></script>
    <script src="https://unpkg.com/alpinejs" defer></script>

    <link rel="stylesheet" href="https://unpkg.com/@appwrite.io/pink" />
    <link rel="stylesheet" href="https://unpkg.com/@appwrite.io/pink-icons" />
</head>
  
```

And after the `</head>` tag add this `<body>` which will contain the actual form:

```html
<body>
    <main class="main-content">
        <div class="top-cover u-padding-block-end-56">
        <div class="container">
            <div
            class="u-flex u-gap-16 u-flex-justify-center u-margin-block-start-16"
            >
            <h1 class="heading-level-1">Pinecone Demo</h1>
            <code class="u-un-break-text"></code>
            </div>
            <p
            class="body-text-1 u-normal u-margin-block-start-8"
            style="max-width: 50rem"
            >
            Use this demo to verify that the sync between Appwrite Databases and
            Pinecone was successful. Search your Pinecone vector database using
            the input below.
            </p>
        </div>
        </div>
        <div
            class="container u-margin-block-start-negative-56"
            x-data="{ search: '', results: [ ] }"
            x-init="$watch('search', async (value) => { results = await onSearch(value) })"
        >
        <div class="card u-flex u-gap-24 u-flex-vertical">
            <div id="searchbox">
            <div
                class="input-text-wrapper is-with-end-button u-width-full-line"
            >
                <input x-model="search" type="search" placeholder="Search" />
                <div class="icon-search" aria-hidden="true"></div>
            </div>
            </div>
            <div id="hits" class="u-flex u-flex-vertical u-gap-12">
            <template x-for="result in results">
                <div class="card">
                <pre x-text="JSON.stringify(result, null, '\t')"></pre>
                </div>
            </template>
            </div>
        </div>
        </div>
    </main>
    <script>
        window.onSearch = async function (prompt) {
            const response = await fetch('/search', {
                method: 'POST',
                body: JSON.stringify({ prompt }),
                headers: {
                'Content-Type': 'application/json',
                },
            });
            return response.matches;
        };
    </script>
</body>
```

This will render a form that will submit your search query to the function and display the results.

#### 6. 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.

#### 7. 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.

#### 8. 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://<REGION>.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.

#### 9. 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.

---

## Integrating Replicate

URL: 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

- An Appwrite Project
- A [Replicate API Key](https://replicate.com/docs/reference/http#authentication)

#### 1. 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.

![Create function screen](/images/docs/functions/template.avif)

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.

#### 2. 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
```

#### 3. 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();
}
```

#### 4. 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',
    });
  }
};
```

#### 5. 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
<!doctype html>
<html lang="en">
</html>
```

Within the `<html>` tag, we're going to add a `<head>` tag that will define our style and scripts.

```html
<head>
  <meta charset="UTF-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Replicate Demo</title>

  <script>
    async function onSubmit(prompt, type) {
      const response = await fetch('/', {
        method: 'POST',
        body: JSON.stringify({ prompt, type }),
        headers: {
          'Content-Type': 'application/json',
        },
      });

      const json = await response.json();

      if (!json.ok || json.error) {
        alert(json.error);
      }

      return json;
    }
  </script>

  <script src="//unpkg.com/alpinejs" defer></script>

  <link rel="stylesheet" href="https://unpkg.com/@appwrite.io/pink" />
  <link rel="stylesheet" href="https://unpkg.com/@appwrite.io/pink-icons" />
</head>
```

And after the `</head>` tag we're going to add our `<body>` which will contain the actual form:

```html
<body>
  <main class="main-content">
    <div class="top-cover u-padding-block-end-56">
      <div class="container">
        <div class="u-flex u-gap-16 u-flex-justify-center u-margin-block-start-16">
          <h1 class="heading-level-1">Replicate Demo</h1>
          <code class="u-un-break-text"></code>
        </div>
        <p class="body-text-1 u-normal u-margin-block-start-8" style="max-width: 50rem">
          Use this page to test your implementation with Replicate. Enter
          text and receive the model output as a response.
        </p>
      </div>
    </div>
    <div class="container u-margin-block-start-negative-56"
      x-data="{ type: 'text', prompt: '', answer: {type: '', answer: ''}, loading: false }">
      <div class="card u-flex u-gap-24 u-flex-vertical">
        <div class="u-flex u-cross-center u-gap-8">
          <div class="input-text-wrapper is-with-end-button u-width-full-line">
            <input x-model="prompt" type="search" placeholder="Prompt" />
            <div class="icon-search" aria-hidden="true"></div>
          </div>
          <div class="select u-width-140">
            <select x-model="type">
              <option value="text">Text</option>
              <option value="image">Image</option>
              <option value="audio">Audio</option>
            </select>
            <span class="icon-cheveron-down" aria-hidden="true"></span>
          </div>

          <button class="button" x-bind:disabled="loading"
            x-on:click="async () => { loading = true; answer = {type: '', answer: ''}; try { answer = await onSubmit(prompt, type) } catch(err) { console.error(err); } finally { loading = false; } }">
            <span class="text">Generate</span>
          </button>
        </div>
        <template x-if="answer.type">
          <div class="u-flex u-flex-vertical u-gap-12">
            <div class="u-flex u-flex-vertical u-gap-12 card">
              <div class="u-flex u-gap-12">
                <h5 class="eyebrow-heading-2">Result:</h5>
              </div>
              <template x-if="answer.type === 'image'" class="u-flex u-gap-12">
                <img class="u-max-width-400" x-bind:src="answer.response" alt="Replicate output" />
              </template>
              <template x-if="answer.type === 'audio'" class="u-flex u-gap-12">
                <audio x-bind:src="answer.response" controls></audio>
              </template>
              <template x-if="answer.type === 'text'" class="u-flex u-gap-12">
                <p class="u-color-text-gray" x-text="answer.response"></p>
              </template>
            </div>
          </div>
        </template>
      </div>
    </div>
  </main>
</body>
```

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.

#### 6. 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.

#### 7. 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.avif)

---

## Integrating TensorFlow with Appwrite

URL: 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

- An Appwrite Project
- Basic knowledge of Python and TensorFlow

#### 1. 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.

![Create function screen](/images/docs/functions/template.avif)

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.

#### 2. 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
```

#### 3. 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()
```

#### 4. 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)}")
```

#### 5. 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"},
        )
```

#### 6. 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)
```

#### 7. 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)
```

#### 8. 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
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Generate with TensorFlow demo</title>

    <script>
      async function onSubmit(prompt) {
        const response = await fetch("/", {
          method: "POST",
          body: JSON.stringify({ prompt }),
          headers: {
            "Content-Type": "application/json",
          },
        });

        const json = await response.json();

        if (!json.ok || json.error) {
          alert(json.error);
        }

        return json.completion;
      }
    </script>

    <script src="//unpkg.com/alpinejs" defer></script>

    <link rel="stylesheet" href="https://unpkg.com/@appwrite.io/pink" />
    <link rel="stylesheet" href="https://unpkg.com/@appwrite.io/pink-icons" />
  </head>
  <body class="theme-dark">
    <main class="main-content">
      <div class="top-cover u-padding-block-end-56">
        <div class="container">
          <div
            class="u-flex u-gap-16 u-flex-justify-center u-margin-block-start-16"
          >
            <h1 class="heading-level-1">Generate with TensorFlow demo</h1>
            <code class="u-un-break-text"></code>
          </div>
          <p
            class="body-text-1 u-normal u-margin-block-start-8"
            style="max-width: 50rem"
          >
            Use this page to test your implementation with TensorFlow. Enter
            text and receive the model output as a response.
          </p>
        </div>
      </div>
      <div
        class="container u-margin-block-start-negative-56"
        x-data="{ prompt: '', answer: '', loading: false }"
      >
        <div class="card u-flex u-gap-24 u-flex-vertical">
          <div class="u-flex u-cross-center u-gap-8">
            <div
              class="input-text-wrapper is-with-end-button u-width-full-line"
            >
              <input x-model="prompt" type="search" placeholder="Question" />
              <div class="icon-search" aria-hidden="true"></div>
            </div>

            <button
              class="button"
              x-bind:disabled="loading"
              x-on:click="async () => { loading = true; answer = ''; try { answer = await onSubmit(prompt) } catch(err) { console.error(err); } finally { loading = false; } }"
            >
              <span class="text">Submit</span>
            </button>
          </div>
          <template x-if="answer">
            <div class="u-flex u-flex-vertical u-gap-12">
              <div class="u-flex u-flex-vertical u-gap-12 card">
                <div class="u-flex u-gap-12">
                  <h5 class="eyebrow-heading-2">TensorFlow Model:</h5>
                </div>

                <div style="overflow-x: hidden; line-break: anywhere">
                  <p class="u-color-text-gray" x-text="answer"></p>
                </div>
              </div>
            </div>
          </template>
        </div>
      </div>
    </main>
  </body>
</html>
```

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.

#### 9. 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.

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

URL: 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

- An Appwrite Project
- An Appwrite Bucket
- A [Together AI API Key](https://docs.together.ai/reference/authentication-1)

#### 1. 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.

![Create function screen](/images/docs/functions/template.avif)

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.

#### 2. 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
```

#### 3. 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();
}
```

#### 4. 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',
    });
  }
};
```

#### 5. 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
<!doctype html>
<html lang="en">
</html>
```

Within the `<html>` tag, add a `<head>` tag that will define the style and scripts.

```html
<head>
  <meta charset="UTF-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Together AI Demo</title>

  <script>
    async function onSubmit(prompt, type) {
      const response = await fetch('/', {
        method: 'POST',
        body: JSON.stringify({ prompt, type }),
        headers: {
          'Content-Type': 'application/json',
        },
      });

      const json = await response.json();

      if (!json.ok || json.error) {
        alert(json.error);
      }

      return JSON;
    }
  </script>

  <script src="//unpkg.com/alpinejs" defer></script>

  <link rel="stylesheet" href="https://unpkg.com/@appwrite.io/pink" />
  <link rel="stylesheet" href="https://unpkg.com/@appwrite.io/pink-icons" />
</head>
```

And after the `</head>` tag add a `<body>` which will contain the actual form:

```html
<body>
  <main class="main-content">
    <div class="top-cover u-padding-block-end-56">
      <div class="container">
        <div class="u-flex u-gap-16 u-flex-justify-center u-margin-block-start-16">
          <h1 class="heading-level-1">Together AI Demo</h1>
          <code class="u-un-break-text"></code>
        </div>
        <p class="body-text-1 u-normal u-margin-block-start-8" style="max-width: 50rem">
          Use this page to test your implementation with Together AI. Enter
          text and receive the model output as a response.
        </p>
      </div>
    </div>
    <div class="container u-margin-block-start-negative-56"
      x-data="{ type: 'text', prompt: '', answer: {type: '', answer: ''}, loading: false }">
      <div class="card u-flex u-gap-24 u-flex-vertical">
        <div class="u-flex u-cross-center u-gap-8">
          <div class="input-text-wrapper is-with-end-button u-width-full-line">
            <input x-model="prompt" type="search" placeholder="Prompt" />
            <div class="icon-search" aria-hidden="true"></div>
          </div>
          <div class="select u-width-140">
            <select x-model="type">
              <option value="text">Text</option>
              <option value="image">Image</option>
            </select>
            <span class="icon-cheveron-down" aria-hidden="true"></span>
          </div>

          <button class="button" x-bind:disabled="loading"
            x-on:click="async () => { loading = true; answer = {type: '', answer: ''}; try { answer = await onSubmit(prompt, type) } catch(err) { console.error(err); } finally { loading = false; } }">
            <span class="text">Generate</span>
          </button>
        </div>
        <template x-if="answer.type">
          <div class="u-flex u-flex-vertical u-gap-12">
            <div class="u-flex u-flex-vertical u-gap-12 card">
              <div class="u-flex u-gap-12">
                <h5 class="eyebrow-heading-2">Result:</h5>
              </div>
              <template x-if="answer.type === 'image'" class="u-flex u-gap-12">
                <img class="u-max-width-400" x-bind:src="answer.response" alt="Together output" />
              </template>
              <template x-if="answer.type === 'text'" class="u-flex u-gap-12">
                <p class="u-color-text-gray" x-text="answer.response"></p>
              </template>
            </div>
          </div>
        </template>
      </div>
    </div>
  </main>
</body>
```

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.

#### 6. 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://<REGION>.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.

#### 7. 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.avif)

---

## Natural language processing

URL: 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

- [Text generation](/docs/products/ai/tutorials/text-generation): Generate text from a prompt
- [Language translation](/docs/products/ai/tutorials/language-translation): Translate text from one language to another

---

## Image classification with Hugging Face

URL: https://appwrite.io/docs/products/ai/tutorials/image-classification

Learn to setup an Appwrite Function utilizing image classification with Hugging Face.

### Prerequisites

- An Appwrite project
- A [Hugging Face API key](https://huggingface.co/docs/api-inference/en/quicktour#get-your-api-token)

#### 1. 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.

![Create function screen](/images/docs/functions/template.avif)

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.

#### 2. 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
```

#### 3. 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);
  }
}
```

#### 4. 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.

![Create bucket on console](/images/docs/storage/create-bucket.avif)

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.

#### 6. 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 |

![Image Classification Database](/images/docs/ai/tutorials/image-classification/database.avif)

#### 7. 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://<REGION>.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.

#### 8. 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',
});
```

#### 9. 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);
```

#### 10. 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.

![Image Classification Event](/images/docs/ai/tutorials/image-classification/event.avif)

#### 10. 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.

![Image Classification Test](/images/docs/ai/tutorials/image-classification/result.avif)

---

## Language translation with Hugging Face

URL: https://appwrite.io/docs/products/ai/tutorials/language-translation

Learn to setup an Appwrite Function utilizing language translation with Hugging Face.

### Prerequisites

- An Appwrite project
- A [Hugging Face API key](https://huggingface.co/docs/api-inference/en/quicktour#get-your-api-token)

#### 1. 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.

![Create function screen](/images/docs/functions/template.avif)

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.

#### 2. 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
```

#### 3. 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);
  }
}
```

#### 4. 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.

#### 5. 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?"
}
```

---

## Music generation with Hugging Face

URL: 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

- An Appwrite project
- A [Hugging Face API keys](https://huggingface.co/docs/api-inference/en/quicktour#get-your-api-token)

#### 1. 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.

![Create function screen](/images/docs/functions/template.avif)

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.

#### 2. 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
```

#### 3. 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://<REGION>.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;
```

#### 4. 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.

![Create bucket on console](/images/docs/storage/create-bucket.avif)

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.

#### 5. 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.

#### 6. 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)

---

## Object detection with Hugging Face

URL: https://appwrite.io/docs/products/ai/tutorials/object-detection

Learn to setup an Appwrite Function utilizing object detection with Hugging Face.

### Prerequisites

- An Appwrite project
- A [Hugging Face API key](https://huggingface.co/docs/api-inference/en/quicktour#get-your-api-token)

#### 1. 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.

![Create function screen](/images/docs/functions/template.avif)

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.

#### 2. 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
```

#### 3. 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.

#### 4. 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.

![Create bucket on console](/images/docs/storage/create-bucket.avif)

Note down the bucket ID so we can add it as an environment variable later.

#### 5. 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://<REGION>.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.

#### 6. 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',
});
```

#### 7. 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 text 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`.

![Object detection database](/images/docs/ai/tutorials/object-detection/database.avif)

#### 8. 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 function createImageLabels(databaseId, tableId, imageId, labels) {
  await 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);
```

#### 9. 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.

![Object detection event](/images/docs/ai/tutorials/object-detection/event.avif)

#### 10. 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.

![Object detection test](/images/docs/ai/tutorials/object-detection/result.avif)

---

## Speech recognition with Hugging Face

URL: 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

- An Appwrite project
- A [Hugging Face API key](https://huggingface.co/docs/api-inference/en/quicktour#get-your-api-token)

#### 1. 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.

![Create function screen](/images/docs/functions/template.avif)

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.

#### 2. 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
```

#### 3. 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://<REGION>.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.

#### 4. 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.

![Create bucket on console](/images/docs/storage/create-bucket.avif)

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.

#### 5. 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 |

#### 6. 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.

#### 7. 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.

![Speech recognition test](/images/docs/ai/tutorials/speech-recognition/result.avif)

---

## Text generation with Hugging Face

URL: https://appwrite.io/docs/products/ai/tutorials/text-generation

Learn to setup an Appwrite Function utilizing text generation with Hugging Face.

### Prerequisites

- An Appwrite project
- A [Hugging Face API keys](https://huggingface.co/docs/api-inference/en/quicktour#get-your-api-token)

#### 1. 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.

![Create function screen](/images/docs/functions/template.avif)

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.

#### 2. 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
```

#### 3. 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);
  }
}
```

#### 4. 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.

#### 5. 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]"
}
```

---

## Text to Speech with Hugging Face

URL: 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

- An Appwrite project
- A [Hugging Face API keys](https://huggingface.co/docs/api-inference/en/quicktour#get-your-api-token)

#### 1. 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.

![Create function screen](/images/docs/functions/template.avif)

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.

#### 2. 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
```

#### 3. 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://<REGION>.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;
```

#### 4. 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.

![Create bucket on console](/images/docs/storage/create-bucket.avif)

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.

#### 5. 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.

#### 6. 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.

---

## Video processing

URL: https://appwrite.io/docs/products/ai/video-processing



---

## Authentication

URL: 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.

[Add authentication to your app in 5 minutes](/docs/products/auth/quick-start)

### Authentication methods

Appwrite supports a variety of authentication methods to fit every app and every niche. Explore Appwrite's authentication flows.

- [Email and password](/docs/products/auth/email-password): Email and password login with just a few lines of code secured with state of the art Argon2 hashing.
- [Phone (SMS)](/docs/products/auth/phone-sms): Log in users without a password using their phone number and SMS verification.
- [Magic URL](/docs/products/auth/magic-url): Passwordless login with a magic link sent to the user's email.
- [Email OTP](/docs/products/auth/email-otp): Generate a time-based single-use password sent to the user's email.
- [OAuth 2](/docs/products/auth/oauth2): Authenticate users with existing accounts from GitHub, Google, Facebook, and 30+ other providers.
- [Anonymous](/docs/products/auth/anonymous): Create guest sessions for visitors and convert to full accounts when they're ready.
- [JWT](/docs/products/auth/jwt): Deligate access for a user through passing JWT tokens.
- [Server-side rendering (SSR)](/docs/products/auth/server-side-rendering): Authenticate users in server-side rendered applications.
- [Custom token](/docs/products/auth/custom-token): Implement custom authentication methods like biometric and passkey login by generating custom tokens.
- [Multifactor authentication (MFA)](/docs/products/auth/mfa): Implementing MFA to add extra layers of security to your app.
- [Presences](/docs/products/auth/presences): Track which signed-in users are active right now and broadcast online, typing, and viewing status in realtime.

### 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

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

URL: 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.

**Account vs Users API**

The Account API is the API you should use in your **client applications** with [Client SDKs](/docs/sdks#client) like web, Flutter, mobile, and native apps.
Account API creates sessions, which represent an authenticated user and is attached to a user's [account](/docs/products/auth/accounts).
Sessions respect [permissions](/docs/advanced/security/permissions), which means users can only access resources if they have been granted the correct permissions.

The Users API is a dedicated API for managing users from an admin's perspective.
It should be used with backend or server-side applications with [Server SDKs](/docs/sdks#server). Users API uses API keys instead of sessions.
This means they're not restricted by permissions, but by the scopes granted to the API key used.

### Signup and 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.

To control which email addresses can sign up, enable [email policies](/docs/products/auth/email-policies) to block free, aliased, or disposable email providers.

### Permissions

You can grant permissions to all users using the `Role.users(<STATUS>)` role or
individual users using the `Role.user(<USER_ID>, <STATUS>)` role.
| Description | Role |
| ------------------------------------------- | ------------------------------------------- |
| Verified users | `Role.users('verified')`|
| Unverified users | `Role.users('unverified')` |
| Verified user | `Role.user(<USER_ID>, 'verified')`|
| Unverified user | `Role.user(<USER_ID>, 'unverified')` |

[Learn more about permissions](/docs/advanced/security/permissions)

---

## Anonymous login

URL: 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

Create an anonymous session with [Create Anonymous Session](/docs/references/cloud/client-web/account#createAnonymousSession) method.

```client-web
import { Client, Account } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<PROJECT_ID>');                 // 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://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<PROJECT_ID>');                 // Your project ID

final account = Account(client);

final user = await account.createAnonymousSession();
```
```client-apple
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<PROJECT_ID>")                  // 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://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<PROJECT_ID>")                  // Your project ID

val account = Account(client)

val user = account.createAnonymousSession()
```
```graphql
mutation {
    accountCreateAnonymousSession {
        _id
        userId
        provider
        expire
    }
}
```

### Attaching an 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.

[Email and password](/docs/products/auth/email-password)

[Phone (SMS)](/docs/products/auth/phone-sms)

[Magic URL](/docs/products/auth/magic-url)

[OAuth2](/docs/products/auth/oauth2)

---

## Checking auth status

URL: 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:

```client-web
import { Client, Account } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

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://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

  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://<REGION>.cloud.appwrite.io/v1")
        .setProject("<PROJECT_ID>")

    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://<REGION>.cloud.appwrite.io/v1")
        .setProject("<PROJECT_ID>")

    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
```

### 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

**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

### 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

URL: 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

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.

```server-nodejs
import { Client, Users } from "node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')    // Your API Endpoint
    .setProject('<PROJECT_ID>')                     // Your project ID
    .setKey('<API_KEY>');                           // Your project API key

const users = new Users(client);

const token = await users.createToken({
    userId: '<USER_ID>'
});
const secret = token.secret;
```

```php
use Appwrite\Client;
use Appwrite\Users;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')   // Your API Endpoint
    ->setProject('<PROJECT_ID>')                    // Your project ID
    ->setKey('<API_KEY>');                          // Your project API key

$users = new Users($client);

$token = $users->createToken('<USER_ID>');
$secret = $token['secret'];
```

```python
from appwrite.client import Client
from appwrite.users import Users

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 project API key
)

users = Users(client)

token = users.create_token(user_id='<USER_ID>')
secret = token.secret
```

```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 project API key

users = Users.new(client)

token = users.create_token(user_id: '<USER_ID>')
secret = token['secret']
```

```deno
import { Client, Users } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')    // Your API Endpoint
    .setProject('<PROJECT_ID>')                     // Your project ID
    .setKey('<API_KEY>');                           // 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://<REGION>.cloud.appwrite.io/v1')    // Your API Endpoint
    .setProject('<PROJECT_ID>')                     // Your project ID
    .setKey('<API_KEY>');                           // Your project API key

final users = Users(client);

final token = await users.createToken('<USER_ID>');
final secret = token.secret;
```

```kotlin
import io.appwrite.Client
import io.appwrite.Users

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")    // Your API Endpoint
    .setProject("<PROJECT_ID>")                     // Your project ID
    .setKey("<API_KEY>")                            // Your project API key

val users = Users(client)

val token = users.createToken("<USER_ID>")
val secret = token.secret
```

```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 project API key

let users = Users(client)

let token = try await users.createToken("<USER_ID>")
let secret = token.secret
```

```csharp
using Appwrite;

var client = new Client()
    .SetEndpoint("https://<REGION>.cloud.appwrite.io/v1")    // Your API Endpoint
    .SetProject("<PROJECT_ID>")                     // Your project ID
    .SetKey("<API_KEY>");                           // Your project API key

var users = new Users(client);

var token = await users.CreateToken("<USER_ID>");
var secret = token.secret;
```

```server-rust
use appwrite::Client;
use appwrite::services::users::Users;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let 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 project API key

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

    let token = users.create_token(
        "<USER_ID>",
        None, // length (optional)
        None, // expire (optional)
    ).await?;

    let secret = token.secret;
    println!("{}", secret);
    Ok(())
}
```

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

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.

```client-web
import { Client, Account } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

const account = new Account(client);

const session = await account.createSession({
    userId: '<USER_ID>',
    secret: '<SECRET>'
});
```

```client-flutter
import 'package:appwrite/appwrite.dart';

final client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

final account = Account(client);

final session = await account.createSession(
    userId: '<USER_ID>',
    secret: '<SECRET>'
);
```
```client-apple
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>");

let account = Account(client);

let session = try await account.createSession(
    userId: "<USER_ID>",
    secret: "<SECRET>"
);
```

```client-android-kotlin
import io.appwrite.Client
import io.appwrite.services.Account

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>");

val account = Account(client);

val session = account.createSession(
    userId = "<USER_ID>",
    secret = "<SECRET>"
);
```

```graphql
mutation {
    accountcreateSession(userId: "<USER_ID>", secret: "<SECRET>") {
        _id
        userId
        provider
        expire
    }
}
```

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

URL: 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.

**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 | |

### 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.

```client-web
import { Client, Account, ID } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

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://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

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://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>");

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://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>");

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
    }
}
```

### 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.

```client-web
import { Client, Account, ID } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

const account = new Account(client);

const session = await account.createSession({
    userId: userId,
    secret: '<SECRET>'
});
```

```client-flutter
import 'package:appwrite/appwrite.dart';

final client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

final account = Account(client);

final session = await account.createSession(
    userId: userId,
    secret: '<SECRET>'
);
```
```client-apple
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>");

let account = Account(client);

let session = try await account.createSession(
    userId: userId,
    secret: "<SECRET>"
);
```
```client-android-kotlin
import io.appwrite.Client
import io.appwrite.services.Account
import io.appwrite.ID

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>");

val account = Account(client);

val session = account.createSession(
    userId = userId,
    secret = "<SECRET>"
);
```

```graphql
mutation {
    accountcreateSession(userId: "<USER_ID>", secret: "<SECRET>") {
        _id
        userId
        provider
        expire
    }
}
```

After the secret is verified, a session will be created.

### 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.

```client-web
import { Client, Account, ID } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<PROJECT_ID>');                 // 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
    }
}
```

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

URL: 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 password strength requirements, blocking personal info in passwords, password dictionary, and password history to help users choose good passwords. You can also restrict which addresses can sign up by enabling [email policies](/docs/products/auth/email-policies) to block free, aliased, or disposable email providers.

### Signup

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('<PROJECT_ID>') // Your project ID
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1');

const account = new Account(client);

try {
    const user = await account.create({
        userId: '[USER_ID]',
        email: 'email@example.com',
        password: '<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

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://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

const account = new Account(client);

const result = await account.createEmailPasswordSession({
    email: 'email@example.com',
    password: 'password'
});

console.log(result);

```

### 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('<PROJECT_ID>') // 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('<PROJECT_ID>'); // 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

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('<PROJECT_ID>'); // 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('<PROJECT_ID>'); // Your project ID

const promise = account.updateRecovery({
    userId: '<USER_ID>',
    secret: '<SECRET>',
    password: 'password'
});

promise.then(function (response) {
    console.log(response); // Success
}, function (error) {
    console.log(error); // Failure
});
```

### Security

Appwrite's security first mindset goes beyond a securely implemented authentication API. You can enable features like password strength requirements, 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.

---

## Email policies

URL: https://appwrite.io/docs/products/auth/email-policies

Email policies let you restrict which email addresses can be used for user creation and email updates on a project. Each policy is an independent toggle that runs at sign-up time and when an existing user changes their email. Policies do not affect session creation, so existing users can still sign in if their address would not pass the current policy.

Three policies are available:

| Policy | Blocks | Example |
| --- | --- | --- |
| Deny free emails | Addresses from free email providers | `user@gmail.com` |
| Deny aliased emails | Addresses with aliases, tags, subaddresses, or any provider-specific variation | `user+folder1@gmail.com` |
| Deny disposable emails | Temporary and disposable email providers | `alex9734@mailinator.com` |

Policies can be configured from the Appwrite Console or programmatically through any server SDK using the Project service.

### Manage from the Console

![Email policies card in the Appwrite Console](/images/docs/auth/email-policies/policies.avif)

To configure email policies manually:

1. Open your project in the Appwrite Console.
2. Navigate to **Auth** in the sidebar.
3. Open the **Policies** tab.
4. Select **Emails**.
5. Toggle the policies you want to enable.
6. Click **Update** to apply the changes.

### Manage from the SDK

Each policy has its own method on the Project service. The body is always an `enabled` boolean.

**Required scope**

The API key used for these calls needs the `policies.write` scope.

#### Deny free emails

When enabled, sign-ups and email updates using addresses from free email providers such as Gmail or Yahoo are rejected.

```server-nodejs
import { Client, Project } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.updateDenyFreeEmailPolicy({
    enabled: true
});
```
```server-deno
import { Client, Project } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.updateDenyFreeEmailPolicy({
    enabled: true
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Project;

$client = new Client();

$client
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$project = new Project($client);

$result = $project->updateDenyFreeEmailPolicy(
    enabled: true
);
```
```server-python
from appwrite.client import Client
from appwrite.services.project import Project

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

project = Project(client)

result = project.update_deny_free_email_policy(
    enabled = True
)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

project = Project.new(client)

response = project.update_deny_free_email_policy(
    enabled: true
)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Project project = new Project(client);

var result = await project.UpdateDenyFreeEmailPolicy(
    enabled: true
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Project project = Project(client);

final result = await project.updateDenyFreeEmailPolicy(
    enabled: true,
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Project

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val project = Project(client)

val response = project.updateDenyFreeEmailPolicy(
    enabled = true
)
```
```server-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Project;

Client client = new Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>");

Project project = new Project(client);

project.updateDenyFreeEmailPolicy(
    true, // enabled
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }
        System.out.println(result);
    })
);
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let project = Project(client)

let result = try await project.updateDenyFreeEmailPolicy(
    enabled: true
)
```
```server-go
package main

import (
    "fmt"
    "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("<YOUR_API_KEY>"),
    )

    service := appwrite.NewProject(client)
    result, err := service.UpdateDenyFreeEmailPolicy(true)

    if err != nil {
        panic(err)
    }

    fmt.Println(result)
}
```
```server-rust
use appwrite::Client;
use appwrite::services::project::Project;

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

    let project = Project::new(&client);

    let result = project.update_deny_free_email_policy(true).await?;

    println!("{:?}", result);
    Ok(())
}
```
```bash
appwrite project update-deny-free-email-policy --enabled true
```

#### Deny aliased emails

When enabled, sign-ups and email updates using addresses with aliases, tags, subaddresses, or any other provider-specific variation are rejected.

```server-nodejs
import { Client, Project } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.updateDenyAliasedEmailPolicy({
    enabled: true
});
```
```server-deno
import { Client, Project } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.updateDenyAliasedEmailPolicy({
    enabled: true
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Project;

$client = new Client();

$client
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$project = new Project($client);

$result = $project->updateDenyAliasedEmailPolicy(
    enabled: true
);
```
```server-python
from appwrite.client import Client
from appwrite.services.project import Project

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

project = Project(client)

result = project.update_deny_aliased_email_policy(
    enabled = True
)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

project = Project.new(client)

response = project.update_deny_aliased_email_policy(
    enabled: true
)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Project project = new Project(client);

var result = await project.UpdateDenyAliasedEmailPolicy(
    enabled: true
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Project project = Project(client);

final result = await project.updateDenyAliasedEmailPolicy(
    enabled: true,
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Project

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val project = Project(client)

val response = project.updateDenyAliasedEmailPolicy(
    enabled = true
)
```
```server-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Project;

Client client = new Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>");

Project project = new Project(client);

project.updateDenyAliasedEmailPolicy(
    true, // enabled
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }
        System.out.println(result);
    })
);
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let project = Project(client)

let result = try await project.updateDenyAliasedEmailPolicy(
    enabled: true
)
```
```server-go
package main

import (
    "fmt"
    "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("<YOUR_API_KEY>"),
    )

    service := appwrite.NewProject(client)
    result, err := service.UpdateDenyAliasedEmailPolicy(true)

    if err != nil {
        panic(err)
    }

    fmt.Println(result)
}
```
```server-rust
use appwrite::Client;
use appwrite::services::project::Project;

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

    let project = Project::new(&client);

    let result = project.update_deny_aliased_email_policy(true).await?;

    println!("{:?}", result);
    Ok(())
}
```
```bash
appwrite project update-deny-canonical-email-policy --enabled true
```

#### Deny disposable emails

When enabled, sign-ups and email updates using addresses from known temporary or disposable providers are rejected.

```server-nodejs
import { Client, Project } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.updateDenyDisposableEmailPolicy({
    enabled: true
});
```
```server-deno
import { Client, Project } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.updateDenyDisposableEmailPolicy({
    enabled: true
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Project;

$client = new Client();

$client
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$project = new Project($client);

$result = $project->updateDenyDisposableEmailPolicy(
    enabled: true
);
```
```server-python
from appwrite.client import Client
from appwrite.services.project import Project

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

project = Project(client)

result = project.update_deny_disposable_email_policy(
    enabled = True
)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

project = Project.new(client)

response = project.update_deny_disposable_email_policy(
    enabled: true
)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Project project = new Project(client);

var result = await project.UpdateDenyDisposableEmailPolicy(
    enabled: true
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Project project = Project(client);

final result = await project.updateDenyDisposableEmailPolicy(
    enabled: true,
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Project

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val project = Project(client)

val response = project.updateDenyDisposableEmailPolicy(
    enabled = true
)
```
```server-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Project;

Client client = new Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>");

Project project = new Project(client);

project.updateDenyDisposableEmailPolicy(
    true, // enabled
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }
        System.out.println(result);
    })
);
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let project = Project(client)

let result = try await project.updateDenyDisposableEmailPolicy(
    enabled: true
)
```
```server-go
package main

import (
    "fmt"
    "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("<YOUR_API_KEY>"),
    )

    service := appwrite.NewProject(client)
    result, err := service.UpdateDenyDisposableEmailPolicy(true)

    if err != nil {
        panic(err)
    }

    fmt.Println(result)
}
```
```server-rust
use appwrite::Client;
use appwrite::services::project::Project;

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

    let project = Project::new(&client);

    let result = project.update_deny_disposable_email_policy(true).await?;

    println!("{:?}", result);
    Ok(())
}
```
```bash
appwrite project update-deny-disposable-email-policy --enabled true
```

### Benefits

Enabling email policies on your project provides:

- **Higher quality user data**: Block low-effort or throw-away addresses so the accounts that do sign up represent real users you can reach later
- **Lower spam and abuse**: Cut down on bot signups, trial abuse, and duplicate accounts created with subaddresses of the same inbox
- **Stronger business rules**: Enforce work-email-only access on products that aren't meant for personal Gmail or Yahoo accounts
- **Less downstream cleanup**: Reduce the bounced emails, unreachable users, and support tickets that come from invalid addresses making it into your database

---

## Identities

URL: 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

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

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

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

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

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

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.

---

## User impersonation

URL: https://appwrite.io/docs/products/auth/impersonation

User impersonation lets a trusted operator temporarily act as another user in the same Appwrite project, without sharing credentials. The operator signs in as themselves first, then sets a single impersonation target on the client. Appwrite resolves that target and executes requests using their permissions.

This is especially useful when you need to:

- Reproduce a bug that only appears for a specific user
- Verify permissions and feature access from the user's point of view
- Help customer support teams troubleshoot account issues
- Review onboarding or upgrade flows exactly as an end user sees them

### How it works

Impersonation follows four steps:

1. Enable the `impersonator` capability for a trusted operator.
2. Have that operator sign in normally using any supported Appwrite Auth flow.
3. Set exactly one impersonation target on the Appwrite client.
4. Appwrite resolves the target user and evaluates the request as that user.

**User authentication is required**

Impersonation only works on requests that are already authenticated as a user with impersonation enabled. In server-side flows, an API key alone is not enough. You must also set a valid operator session on the client.

When impersonation is active, the returned user model includes `impersonatorUserId`. Your app can use this to show a visible banner or disable risky actions while someone is acting on behalf of another user.

### Enable impersonation for an operator

You can grant a user the impersonator capability in two ways:

- In the Appwrite Console under **Auth > Users**, open the user's profile and toggle on the impersonator capability
- Via the Users API, by setting the `impersonator` field to `true` on the user

```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 users = new sdk.Users(client);

const result = await users.updateImpersonator({
    userId: '<USER_ID>',
    impersonator: true
});
```

```server-deno
import * as sdk from "npm: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 users = new sdk.Users(client);

const result = await users.updateImpersonator({
    userId: '<USER_ID>',
    impersonator: true
});
```

```server-python
from appwrite.client import Client
from appwrite.services.users import Users

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

users = Users(client)

result = users.update_impersonator(
    user_id='<USER_ID>',
    impersonator=True
)
```

```server-php
use Appwrite\Client;
use Appwrite\Services\Users;

$client = new 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

$users = new Users($client);

$result = $users->updateImpersonator(
    userId: '<USER_ID>',
    impersonator: true
);
```

```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

users = Users.new(client)

result = users.update_impersonator(
    user_id: '<USER_ID>',
    impersonator: true
)
```

```server-dotnet
using Appwrite;
using Appwrite.Services;

var 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

var users = new Users(client);

var result = await users.UpdateImpersonator(
    userId: "<USER_ID>",
    impersonator: true
);
```

```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

final 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

final users = Users(client);

final result = await users.updateImpersonator(
    userId: '<USER_ID>',
    impersonator: true
);
```

```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 users = Users(client)

let result = try await users.updateImpersonator(
    userId: "<USER_ID>",
    impersonator: true
)
```

```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Users

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 users = Users(client)

val result = users.updateImpersonator(
    userId = "<USER_ID>",
    impersonator = true
)
```

```server-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Users;

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

Users users = new Users(client);

users.updateImpersonator(
    "<USER_ID>",
    true,
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return null;
        }
        System.out.println(result);
        return null;
    })
);
```

```server-rust
use appwrite::Client;
use appwrite::services::users::Users;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let 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

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

    let result = users.update_impersonator(
        "<USER_ID>",
        true,
    ).await?;

    println!("{:?}", result);
    Ok(())
}
```

Only grant this capability to internal users (support agents, QA engineers, or operations staff) who need to see the app exactly as a specific end user would.

**Recommended setup**

Create a dedicated internal operator account for each team member instead of sharing one support account.

This makes it easier to review internal audit activity and control access over time.

When a user is marked as an impersonator, Appwrite also grants them the `users.read` scope.

This allows trusted operators to list users in the project, which is especially useful when building internal admin-style tools that let support or QA teams search for a user and choose who to impersonate next.

### Initialize an impersonated client

After the operator signs in, initialize the Appwrite client as usual and set exactly one impersonation target. Only one target can be active per request; using more than one is not supported.

**SDK support**

Make sure you are using an Appwrite SDK version that supports impersonation. In client SDKs, the operator's session is persisted automatically after login, so enabling impersonation is just a matter of setting the right option on the client before making requests.

### By user ID

Impersonating by user ID is the most precise option. Use it when your internal tools already store the Appwrite user ID or when an operator selected a user from your support dashboard.

```client-web
import { Client, Account } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setImpersonateUserId('<TARGET_USER_ID>');

const account = new Account(client);
const user = await account.get();

if (user.impersonatorUserId) {
    console.log(`Impersonated by ${user.impersonatorUserId}`);
}
```

```client-flutter
import 'package:appwrite/appwrite.dart';

Client client = Client()
  .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
  .setProject('<PROJECT_ID>')
  .setImpersonateUserId('<TARGET_USER_ID>');

Account account = Account(client);
final user = await account.get();
print(user.name);
```

```client-apple
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setImpersonateUserId("<TARGET_USER_ID>")

let account = Account(client)
let user = try await account.get()
print(user.name)
```

```client-android-kotlin
import io.appwrite.Client
import io.appwrite.services.Account

val client = Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setImpersonateUserId("<TARGET_USER_ID>")

val account = Account(client)
val user = account.get()
println(user.name)
```

### By email

Impersonating by email is useful in admin panels and support workflows where operators search for users by email address first.

```client-web
import { Client, Account } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setImpersonateUserEmail('user@example.com');

const account = new Account(client);
const user = await account.get();
console.log(user.email);
```

```client-flutter
import 'package:appwrite/appwrite.dart';

Client client = Client()
  .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
  .setProject('<PROJECT_ID>')
  .setImpersonateUserEmail('user@example.com');

Account account = Account(client);
final user = await account.get();
print(user.email);
```

```client-apple
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setImpersonateUserEmail("user@example.com")

let account = Account(client)
let user = try await account.get()
print(user.email)
```

```client-android-kotlin
import io.appwrite.Client
import io.appwrite.services.Account

val client = Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setImpersonateUserEmail("user@example.com")

val account = Account(client)
val user = account.get()
println(user.email)
```

### By phone

Impersonating by phone is helpful for support flows where the phone number is the primary identifier or when your app is centered around SMS-based authentication.

```client-web
import { Client, Account } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setImpersonateUserPhone('+12065550100');

const account = new Account(client);
const user = await account.get();
console.log(user.phone);
```

```client-flutter
import 'package:appwrite/appwrite.dart';

Client client = Client()
  .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
  .setProject('<PROJECT_ID>')
  .setImpersonateUserPhone('+12065550100');

Account account = Account(client);
final user = await account.get();
print(user.phone);
```

```client-apple
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setImpersonateUserPhone("+12065550100")

let account = Account(client)
let user = try await account.get()
print(user.phone)
```

```client-android-kotlin
import io.appwrite.Client
import io.appwrite.services.Account

val client = Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setImpersonateUserPhone("+12065550100")

val account = Account(client)
val user = account.get()
println(user.phone)
```

### Choosing the right identifier

All three options result in the same impersonated session. The difference is which identifier your operator workflow has available:

- Use **user ID** when you already have the canonical Appwrite ID
- Use **email** when support teams search by email address
- Use **phone** when your app is centered around phone login or phone-based onboarding

Only set one impersonation value at a time. If you need to switch targets, create a fresh client or replace the previous impersonation value before continuing.

### Build safe support tooling

Impersonation is most useful when you wrap it in explicit operator UX:

- Use the automatically granted `users.read` scope to build a user picker or searchable support view
- Show a clear banner while impersonation is active
- Display both the operator identity and the effective user identity
- Require an explicit action to start impersonation
- Let operators stop impersonating with one click
- Limit impersonation features to internal tools and trusted roles

### Security and visibility

Keep impersonation limited to trusted operators and internal tools.

Important behavior to know:

- Impersonation must start from a real user session, not an API key by itself.
- Users with impersonation enabled are automatically granted the `users.read` scope.
- The target user's permissions are used for the impersonated request.
- The user model includes `impersonator`, which indicates whether that user can impersonate others, and `impersonatorUserId`, which is present only during an active impersonation session and identifies the operator performing the impersonation.
- Internal audit logs attribute the action to the original impersonator and include the impersonated target in internal audit payload data.

If you build an internal admin panel, use `impersonatorUserId` to make the impersonated state obvious at all times.

### More resources

- [Manage users with the Users API](/docs/products/auth/users)
- [REST API impersonation docs](/docs/apis/rest#impersonation-headers)
- [Users API reference](/docs/references/cloud/server-nodejs/users)

---

## JWT login

URL: 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

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/security/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

[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).

```client-web
import { Client, Account } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<PROJECT_ID>');                 // 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://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<PROJECT_ID>');                 // Your project ID

final account = Account(client);

final jwt = await account.createJWT();
```
```client-apple
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<PROJECT_ID>")                  // 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://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<PROJECT_ID>")                  // Your project ID

val account = Account(client)

val jwt = account.createJWT()
```
```graphql
mutation {
    accountCreateJWT {
        jwt
    }
}
```

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).

```js
const { Client } = require('node-appwrite');

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<PROJECT_ID>')                  // Your project ID
    .setJWT('eyJJ9.eyJ...886ca');                // Your secret JSON Web Token
```

```php
use Appwrite\Client;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')            // Your API Endpoint
    ->setProject('<PROJECT_ID>')                             // Your project ID
    ->setJWT('eyJJ9.eyJ...886ca');                           // Your secret JSON Web Token
```

```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_jwt('eyJJ9.eyJ...886ca')                 # Your secret JSON Web Token
)
```

```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('eyJJ9.eyJ...886ca')                   # Your secret JSON Web Token
```

```deno
import { Client } from "npm:node-appwrite";

let client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')    // Your API Endpoint
    .setProject('<PROJECT_ID>')                     // 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://<REGION>.cloud.appwrite.io/v1')    // Your API Endpoint
    .setProject('<PROJECT_ID>')                     // Your project ID
    .setJWT('eyJJ9.eyJ...886ca');                   // Your secret JSON Web Token
```

```kotlin
import io.appwrite.Client

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")    // Your API Endpoint
    .setProject("<PROJECT_ID>")                     // Your project ID
    .setJWT("eyJJ9.eyJ...886ca")                    // Your secret JSON Web Token
```

```swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")    // Your API Endpoint
    .setProject("<PROJECT_ID>")                     // Your project ID
    .setJWT("eyJJ9.eyJ...886ca")                    // Your secret JSON Web Token
```

```csharp
using Appwrite;

var client = new Client()
    .SetEndpoint("https://<REGION>.cloud.appwrite.io/v1")    // Your API Endpoint
    .SetProject("<PROJECT_ID>")                     // Your project ID
    .SetJWT("eyJJ9.eyJ...886ca");                   // Your secret JSON Web Token
```

### When should I use JWTs?

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/partners/project/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

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.

```js
const { Client } = require('node-appwrite');

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<PROJECT_ID>')                  // 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'
});
```
```php
use Appwrite\Client;

$client = (new Client())
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')            // Your API Endpoint
    .setProject('<PROJECT_ID>')                           // Your project ID
    .setJWT('eyJJ9.eyJ...886ca');                          // Your secret JSON Web Tokens

$tablesDB = new TablesDB($client);

$rows = $tablesDB->listRows(
    databaseId: '642f358bf4084c662590',
    tableId: '642f3592aa5fc856ad1e'
);
```
```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_jwt('eyJJ9.eyJ...886ca')                             # Your secret JSON Web Token
)

tablesDB = TablesDB(client)

rows = tablesDB.list_rows(
    database_id='642f358bf4084c662590',
    table_id='642f3592aa5fc856ad1e'
)
```
```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('eyJJ9.eyJ...886ca')                           # Your secret JSON Web Token

tablesDB = TablesDB.new(client)

rows = tablesDB.list_rows(
    database_id: '642f358bf4084c662590',
    table_id: '642f3592aa5fc856ad1e'
)
```
```deno
import { Client } from "npm:node-appwrite";

let client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')    // Your API Endpoint
    .setProject('<PROJECT_ID>')                     // 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'
});
```
```dart
import 'package:dart_appwrite/dart_appwrite.dart';

final client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')    // Your API Endpoint
    .setProject('<PROJECT_ID>')                     // 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',
);
```
```kotlin
import io.appwrite.Client

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")    // Your API Endpoint
    .setProject("<PROJECT_ID>")                     // Your project ID
    .setJWT("eyJJ9.eyJ...886ca")                    // Your secret JSON Web Token

val tablesDB = TablesDB(client)

val rows = tablesDB.listRows(
    databaseId = "642f358bf4084c662590",
    tableId = "642f3592aa5fc856ad1e",
)
```
```swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")    // Your API Endpoint
    .setProject("<PROJECT_ID>")                     // 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"
)
```
```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
    .SetJWT("eyJJ9.eyJ...886ca");                   // Your secret JSON Web Token

var tablesDB = new TablesDB(client);

var rows = await databases.listRows(
    databaseId: "642f358bf4084c662590",
    tableId: "642f3592aa5fc856ad1e");

```

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.

```js
const { Client } = require('node-appwrite');

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<PROJECT_ID>')                  // 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'
});
```

```php
use Appwrite\Client;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    ->setProject('<PROJECT_ID>')                  // Your project ID
    ->setKey('98fd4...a2ad2');                    // Your secret API key

$tablesDB = new TablesDB($client);

$rows = $tablesDB->listRows(
    databaseId: '642f358bf4084c662590',
    tableId: '642f3592aa5fc856ad1e'
);
```

```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('98fd4...a2ad2')                     // Your secret API key
)

tablesDB = TablesDB(client)

rows = tablesDB.list_rows(
    database_id='642f358bf4084c662590',
    table_id='642f3592aa5fc856ad1e'
)
```
```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('98fd4...a2ad2')                     # Your secret API key

tablesDB = TablesDB.new(client)

rows = tablesDB.list_rows(
    database_id: '642f358bf4084c662590',
    table_id: '642f3592aa5fc856ad1e'
)
```
```deno
import { Client } from "npm:node-appwrite";

let client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<PROJECT_ID>')                  // 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'
});
```
```dart
import 'package:dart_appwrite/dart_appwrite.dart';

final client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<PROJECT_ID>')                  // Your project ID
    .setKey('98fd4...a2ad2');                    // Your secret API key

final tablesDB = TablesDB(client);

final rows = await tablesDB.listRows(
    databaseId: '642f358bf4084c662590',
    tableId: '642f3592aa5fc856ad1e',
);
```
```kotlin
import io.appwrite.Client

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<PROJECT_ID>")                  // Your project ID
    .setKey('98fd4...a2ad2');                    // Your secret API key

val tablesDB = TablesDB(client)

val rows = tablesDB.listRows(
    databaseId = "642f358bf4084c662590",
    tableId = "642f3592aa5fc856ad1e",
)
```
```swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<PROJECT_ID>")                  // Your project ID
    .setKey('98fd4...a2ad2');                    // Your secret API key

let tablesDB = TablesDB(client)

let rows = try await tablesDB.listRows(
    databaseId: "642f358bf4084c662590",
    tableId: "642f3592aa5fc856ad1e"
)
```
```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('98fd4...a2ad2');                    // Your secret API key

var tablesDB = new TablesDB(client);

var rows = await databases.listRows(
    databaseId: "642f358bf4084c662590",
    tableId: "642f3592aa5fc856ad1e");

```

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

URL: 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.

```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('98fd4...a2ad2');                    // Your secret API key

const users = new sdk.Users(client);

const promise = users.updateLabels({
    userId: '<USER_ID>',
    labels: [ '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://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<PROJECT_ID>')                 // Your project ID
    .setKey('98fd4...a2ad2');                    // Your secret API key

$users = new Users($client);

$result = $users->updateLabels(
    '<USER_ID>',
    [ 'subscriber' ]
);
```

```python
from appwrite.client import Client
from appwrite.services.users import Users
from appwrite.role import Role

client = Client()

(client
  .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint
  .set_project('<PROJECT_ID>')                 # Your project ID
  .set_key('98fd4...a2ad2')                     # Your secret API key
)
 
users = Users(client)

result = users.update_labels(
    user_id='<USER_ID>',
    labels=[ 'subscriber' ]
);
```
```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('98fd4...a2ad2')                     # Your secret API key

users = Users.new(client)

response = users.update_labels(
    user_id: '<USER_ID>',
    labels: [ 'subscriber' ]
);
```

```deno
import * as sdk from "npm:node-appwrite";

let client = new sdk.Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<PROJECT_ID>')                 // Your project ID
    .setKey('98fd4...a2ad2');                    // Your secret API key

let users = new sdk.Users(client);

const promise = users.updateLabels({
    userId: '<USER_ID>',
    labels: [ '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://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<PROJECT_ID>')                 // Your project ID
    .setKey('98fd4...a2ad2');                    // Your secret API key

final users = Users(client);

final result = await users.updateLabels(
    userId: '<USER_ID>',
    labels: [ 'subscriber' ],
);
```
```kotlin
import io.appwrite.Client
import io.appwrite.Role
import io.appwrite.services.Users

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<PROJECT_ID>")                 // Your project ID
    .setKey("98fd4...a2ad2")                     // Your secret API key

val users = Users(client)

val response = users.updateLabels(
    userId = "<USER_ID>",
    labels = [ 'subscriber' ]
);
```

```swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<PROJECT_ID>")                 // Your project ID
    .setKey("98fd4...a2ad2")                     // Your secret API key

let users = Users(client)

let response = try await users.updateLabels(
    userId: "<USER_ID>",
    labels: [ 'subscriber' ]
);
```

```csharp
using Appwrite;

var client = new Client()
    .SetEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .SetProject("<PROJECT_ID>")                 // Your project ID
    .SetKey("98fd4...a2ad2");                    // Your secret API key

var users = new Users(client);

var response = await users.UpdateLabels(
    userId: "<USER_ID>",
    labels: [ 'subscriber' ]
);
```

```server-rust
use appwrite::Client;
use appwrite::services::users::Users;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new()
        .set_endpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
        .set_project("<PROJECT_ID>")                 // Your project ID
        .set_key("98fd4...a2ad2");                    // Your secret API key

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

    let response = users.update_labels(
        "<USER_ID>",
        vec!["subscriber"],
    ).await?;

    println!("{:?}", response);
    Ok(())
}
```

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'))` |

[Learn more about permissions](/docs/advanced/security/permissions)

---

## Magic URL login

URL: 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

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.

```client-web
import { Client, Account, ID } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<PROJECT_ID>');                 // Your project ID

const account = new Account(client);

const token = await account.createMagicURLToken({
    userId: ID.unique(),
    email: 'email@example.com',
    url: 'https://example.com/verify'
});
```

```graphql
mutation {
    accountCreateMagicURLToken(
        userId: "ID.unique()",
        email: "email@example.com",
        url: "https://example.com/verify"
    ) {
        _id
        _createdAt
        userId
        secret
        expire
    }
}
```

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

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.

```client-web
import { Client, Account } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<PROJECT_ID>');                // 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: "<SECRET>"
    ) {
        _id
        _createdAt
        userId
        expire
        provider
    }
}
```

---

## Message templates

URL: https://appwrite.io/docs/products/auth/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

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

You can customize email templates for each of your projects in the Appwrite Console.

**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.

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

You can customize the email templates for account verification, magic-url authentication, password resets, and user invites.

#### 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

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 examples

Here's an example of using these variables in a template.

```html
<!doctype html>
<html>

<head>
    <!-- <style>
        ... your style here
    </style> -->
</head>

<body style="direction: ltr">

    <div style="max-width:650px; word-wrap: break-word; overflow-wrap: break-word;
  word-break: break-all; margin:0 auto;">
        <table style="margin-top: 32px">
            <tr>
                <td>
                    <h1>{{subject}}</h1>
                </td>
            </tr>
        </table>

        <table style="margin-top: 40px">
            <tr>
                <td>
                    <p>Hello </p>

                    <p>Follow this link to reset your {{project}} password.</p>

                    <a href="{{redirect}}" target="_blank" rel="noopener noreferrer">{{redirect}}</a>

                    <p><br />If you didn't ask to reset your password, you can ignore this message.</p>
                    <br />

                    <p>Thanks
                        <br />
                        {{project}} team</p>
                </td>
            </tr>
        </table>
    </div>

</body>

</html>
```

### 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.

```client-web
import { Client, Account } from "appwrite";

const client = new Client();

const account = new Account(client);

client
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<PROJECT_ID>')                 // 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://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<PROJECT_ID>')                 // 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://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<PROJECT_ID>")                 // 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://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<PROJECT_ID>")                 // Your project ID
    .setLocale('fr')                             // Your locale

let account = Account(client)

let token = try await account.createVerification('https://example.com')
```

---

## Multi-factor authentication

URL: 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.

**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).

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.

#### 1. 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.

```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://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<PROJECT_ID>') // 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://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<PROJECT_ID>')             // Your project ID
  ;
}
```

```client-apple
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<PROJECT_ID>") // 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://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<PROJECT_ID>") // Your project ID

val account = Account(client)
val avatars = Avatars(client)
```

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()`.

```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());
}));
```

#### 2. 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.

**Email**

First, set your user's email if they haven't already.

```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());
    })
);
```

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`.

```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());
    })
);
```

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`.

```client-web
const response = await account.updateVerification({
    userId: '<USER_ID>',
    secret: '<SECRET>'
});
```
```client-flutter
Future result = account.updateVerification(
    userId: '<USER_ID>',
    secret: '<SECRET>',
);

result.then((response) {
    print(response);
}).catchError((error) {
    print(error.response);
});
```
```client-apple
let response = try await account.updateVerification(
  userId: "<USER_ID>",
  secret: "<SECRET>"
)
```
```client-android-kotlin
val response = account.updateVerification(
    userId = "<USER_ID>",
    secret = "<SECRET>"
)
```
```client-android-java
account.updateVerification(
    "<USER_ID>", // userId
    "<SECRET>", // secret
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        Log.d("Appwrite", result.toString());
    })
);
```

**Phone**

First, set your user's phone number if they haven't already.

```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());
    })
);
```

Then, initiate verification for the phone number by calling `account.createPhoneVerification()`.

```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());
}));
```

After the user receives the verification code, they can verify their phone number by calling `account.updatePhoneVerification()`.

```client-web
const response = await account.updatePhoneVerification({
    userId: '<USER_ID>',
    secret: '<SECRET>'
});
```
```client-flutter
Future result = account.updatePhoneVerification(
    userId: '<USER_ID>',
    secret: '<SECRET>',
);

result.then((response) {
        print(response);
    }).catchError((error) {
        print(error.response);
    });
```
```client-apple
let response = try await account.updatePhoneVerification(
  userId: "<USER_ID>",
  secret: "<SECRET>"
)
```
```client-android-kotlin
val response = account.updatePhoneVerification(
    userId = "<USER_ID>",
    secret = "<SECRET>"
)
```
```client-android-java
account.updatePhoneVerification(
    "<USER_ID>", // userId
    "<SECRET>", // secret
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        Log.d("Appwrite", result.toString());
    })
);
```

**Authenticator**

First, add a TOTP authenticator to the user's account by calling `account.addAuthenticator()`.

```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());
    })
);
```

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()`.

```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());
    })
);
```

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()`.

```client-web
const promise = account.updateMfaAuthenticator({
    type: 'totp',
    otp: '<OTP>'
});
```
```client-flutter
  Future result = account.updateMfaAuthenticator(
    type: 'totp',
    otp: '<OTP>',
  );

  result.then((response) {
    print(response);
  }).catchError((error) {
    print(error.response);
  });
```
```client-apple
let response = try await account.updateMfaAuthenticator(
  type: "totp",
  otp: "<OTP>"
)
```
```client-android-kotlin
val response = account.updateMfaAuthenticator(
    type = "totp",
    otp = "<OTP>"
)
```
```client-android-java
account.updateMfaAuthenticator(
    "totp", // type
    "<OTP>", // otp
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        Log.d("Appwrite", result.toString());
    })
);
```

#### 3. 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.

```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
)
```

#### 4. Initialize login

Begin your login flow with the default authentication method used by your app, for example, email password.

```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"
)
```

#### 5. 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.

```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
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
    }
}
```

#### 6. 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
}
```

```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.
```

#### 7. 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.

**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.

```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
```

**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.

```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
```

**TOTP**

Initiate a challenge for users to complete using an authenticator app.

```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
```

#### 8. Complete challenge

Once the user receives the challenge code, you can pass the code back to Appwrite to complete the challenge.

```client-web
const response = await account.updateMfaChallenge({
    challengeId: '<CHALLENGE_ID>',
    otp: '<OTP>'
});
```

```client-flutter
Future result = account.updateMfaChallenge(
    challengeId: '<CHALLENGE_ID>',
    otp: '<OTP>',
);

result.then((response) {
    print(response);
}).catchError((error) {
    print(error.response);
});
```

```client-apple
val response = account.updateMfaChallenge(
    challengeId = "<CHALLENGE_ID>",
    otp = "<OTP>"
)
```

```client-android-kotlin
let result = try await account.updateMfaChallenge(
  challengeId: "<CHALLENGE_ID>",
  otp: "<OTP>"
)
```

After completing the challenge, the user is now authenticated and all requests will be authorized.
You can confirm this by running `account.get()`

#### 9. 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`.

```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
```

Then complete the challenge by calling `account.updateMfaChallenge()` with the challenge ID and the recovery code.

```client-web
const response = await account.updateMfaChallenge({
    challengeId: '<CHALLENGE_ID>',
    otp: '<RECOVERY_CODE>'
});
```

```client-flutter
Future result = account.updateMfaChallenge(
    challengeId: '<CHALLENGE_ID>',
    otp: '<RECOVERY_CODE>',
);

result.then((response) {
    print(response);
}).catchError((error) {
    print(error.response);
});
```

```client-apple
val response = account.updateMfaChallenge(
    challengeId = "<CHALLENGE_ID>",
    otp = "<RECOVERY_CODE>"
)
```

```client-android-kotlin
let result = try await account.updateMfaChallenge(
  challengeId: "<CHALLENGE_ID>",
  otp: "<RECOVERY_CODE>"
)
```

---

## Multi-tenancy with Teams

URL: 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.

[Learn more about Teams](/docs/products/auth/teams)

### 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.

```client-web
import { Client, Teams, ID } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

const teams = new Teams(client);

// Create team for a new tenant
const tenantTeam = await teams.create({
    teamId: 'example_corp',
    name: 'Example Corp',
    roles: ['owner', 'admin', 'member']
});
```
```client-flutter
import 'package:appwrite/appwrite.dart';

final client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

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://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

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://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

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
)
```

### Add members to tenants

Invite users to join a tenant using team memberships. Each member can be assigned different roles for access control.

```client-web
import { Client, Teams } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

const teams = new Teams(client);

// Invite a member to the tenant
const membership = await teams.createMembership({
    teamId: 'example_corp',
    roles: ['admin'],
    email: 'user@example.com',
    url: 'https://example.com/accept-invite'
});
```
```client-flutter
import 'package:appwrite/appwrite.dart';

final client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

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://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

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://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

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
)
```

### Secure resources with team permissions

Control access to rows and resources using team-based permissions. This ensures data isolation between tenants.

```client-web
import { Client, TablesDB, ID, Permission, Role } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

const tablesDB = new TablesDB(client);

// Create a row that only members of "Example Corp" tenant can access
const 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-flutter
import 'package:appwrite/appwrite.dart';

final client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

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://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

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://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

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")))
    )
)
```

### Query tenant data

When querying data, users will automatically only see rows they have permission to access based on their team memberships.

```client-web
import { Client, TablesDB, Query } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

const tablesDB = new TablesDB(client);

// Current user will only see invoices they have access to
const rows = await tablesDB.listRows({
    databaseId: 'invoices_db',
    tableId: 'invoices'
});

// For specific tenant data, you can add a query filter
const tenantRows = await tablesDB.listRows({
    databaseId: 'invoices_db',
    tableId: 'invoices',
    queries: [
        Query.equal('tenant_id', 'example_corp')
    ]
});
```
```client-flutter
import 'package:appwrite/appwrite.dart';

final client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

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://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

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://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

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")
    )
)
```

[Learn how to manage team invitations](/docs/products/auth/team-invites)

---

## OAuth2 server

URL: https://appwrite.io/docs/products/auth/oauth-server

Your Appwrite project can act as an **OAuth 2.1 and OpenID Connect provider** (OIDC provider). When you enable the OAuth2 server, third-party apps register as clients, send your users to a consent screen you host, and receive tokens your project issues. Your project becomes an identity provider that any standards-compliant OAuth or OIDC library can integrate with: the same way apps offer "Sign in with Google" or "Sign in with GitHub", integrators can offer **Sign in with your product**.

### How it works

![A third-party app obtaining tokens from your project's OAuth2 server and calling your APIs](/images/docs/oauth-server/diagram-overview.avif)

The OAuth2 server has four components.

1. **The authorization server.** Enabling the server on your project exposes the full set of endpoints: JWKS, authorize, approve and reject, token, userinfo, introspect, logout, and revoke. The discovery documents are served whether or not the server is enabled. For more advanced integrations, device authorization, pushed authorization, and dynamic client registration are also available.
2. **Clients.** Each third-party app registers as a [client](/docs/products/auth/oauth-server/clients), either confidential (it has a backend that can hold a secret) or public (a browser or mobile app that cannot). A client declares the redirect URIs it can return to and the post-logout redirect URIs it can end sessions at, chooses whether the device flow is available (off by default), and carries what your consent screen shows for it: a name, logo, tagline, and description, along with marketplace details like tags, images, and contact and privacy information.
3. **The consent screen.** You host a consent screen at an authorization URL you configure. When a user authorizes a client, Appwrite redirects the user to your screen, your screen confirms the grant, and Appwrite issues an authorization code the client exchanges for tokens.
4. **Your APIs.** The resource servers that clients call with the issued access tokens. They verify each token against your project's public keys and check the granted [scopes](/docs/products/auth/oauth-server/scopes) before serving a request.

The flow follows the OAuth 2.1 authorization code grant, including PKCE for public clients, plus the OpenID Connect layer for identity. Because the server is spec-compliant, integrators can point any OAuth or OIDC client library at your project's discovery URL and it works without Appwrite-specific code. The URL is the same for every project, with only the project ID changing:

```text
https://<REGION>.cloud.appwrite.io/v1/oauth2/<PROJECT_ID>/.well-known/openid-configuration
```

### Standards support

The server follows the OAuth 2.1 security practices: the password and implicit access-token grants are not supported, refresh tokens rotate on every use with reuse detection, and redirect URIs match exactly. The implemented standards:

| Standard | What it covers |
| --- | --- |
| [RFC 6749](https://datatracker.ietf.org/doc/html/rfc6749) | OAuth 2.0, the core of the sign-in implementation. |
| [RFC 6750](https://datatracker.ietf.org/doc/html/rfc6750) | Bearer token usage, including `WWW-Authenticate` error responses. |
| [RFC 7009](https://datatracker.ietf.org/doc/html/rfc7009) | Token revocation. |
| [RFC 7591](https://datatracker.ietf.org/doc/html/rfc7591) | Dynamic client registration, useful for MCP server support. |
| [RFC 7636](https://datatracker.ietf.org/doc/html/rfc7636) | PKCE with `S256` hashing. Required for public clients, and a project setting can require it for confidential clients too. |
| [RFC 7662](https://datatracker.ietf.org/doc/html/rfc7662) | Token introspection, for building APIs that validate access tokens. |
| [RFC 8252](https://datatracker.ietf.org/doc/html/rfc8252) | OAuth for native apps. Loopback redirects match on any port for public clients, and any client can register private-use scheme redirect URIs. |
| [RFC 8414](https://datatracker.ietf.org/doc/html/rfc8414) | Authorization server metadata, published as the OpenID Connect discovery document. |
| [RFC 8628](https://datatracker.ietf.org/doc/html/rfc8628) | Device authorization grant, to sign in on input-constrained devices like TVs and remote servers. |
| [RFC 8707](https://datatracker.ietf.org/doc/html/rfc8707) | Resource indicators, so a token can be restricted to one of several APIs behind the same provider. |
| [RFC 7517](https://datatracker.ietf.org/doc/html/rfc7517), [RFC 7519](https://datatracker.ietf.org/doc/html/rfc7519), [RFC 9068](https://datatracker.ietf.org/doc/html/rfc9068) | JWT access tokens with an audience, and the JWK Set published at a well-known URL. |
| [RFC 9126](https://datatracker.ietf.org/doc/html/rfc9126) | Pushed authorization requests keep authorization URLs short and protect request parameters from exposure or tampering. |
| [RFC 9207](https://datatracker.ietf.org/doc/html/rfc9207) | Authorization server issuer identification: authorization responses carry an `iss` parameter, so clients can detect mix-up attacks. |
| [RFC 9396](https://datatracker.ietf.org/doc/html/rfc9396) | [Rich authorization requests](/docs/products/auth/oauth-server/scopes#rich-authorization-requests), structured constraints where a flat scope says too little. |
| [OpenID Connect Core 1.0](https://openid.net/specs/openid-connect-core-1_0.html) | The identity layer: ID tokens and the userinfo endpoint. |
| [OpenID Connect RP-Initiated Logout 1.0](https://openid.net/specs/openid-connect-rpinitiated-1_0.html) | The logout endpoint, with per-client post-logout redirect URIs. |

### Concepts

- [Quick start](/docs/products/auth/oauth-server/quick-start): Enable the server, register a client, and run your first sign-in end to end.
- [Clients](/docs/products/auth/oauth-server/clients): Register confidential and public OAuth clients and manage their secrets.
- [Authorization](/docs/products/auth/oauth-server/authorization): The authorization code flow, PKCE, and hosting your own consent screen.
- [Tokens](/docs/products/auth/oauth-server/tokens): Access, refresh, and ID tokens, their lifetimes, introspection, and revocation.
- [Scopes](/docs/products/auth/oauth-server/scopes): The built-in OpenID scopes and the custom scopes your clients can request.
- [Installations](/docs/products/auth/oauth-server/installations): Let clients install on teams and act with tokens that need no signed-in user.
- [Device flow](/docs/products/auth/oauth-server/device-flow): Authorize TVs, CLIs, and other input-constrained devices.

### Guides

- [Sign in with your product](/docs/products/auth/oauth-server/sign-in-with-your-product/step-1): Build a full sign-in with your product experience end to end, from the consent screen to the token exchange.
- [Protect your API with custom scopes](/docs/products/auth/oauth-server/custom-scopes/step-1): Define custom scopes, request them from a client, and enforce them on your own API.

---

## Authorization

URL: https://appwrite.io/docs/products/auth/oauth-server/authorization

Authorization is the step where a user allows a client to act on their behalf. Appwrite's OAuth2 server uses the authorization code flow. Public clients protect the flow with PKCE. Confidential clients authenticate with a client secret and can also use PKCE when your project requires it.

### The authorization code flow

![The authorization code flow between the browser, the client, the OAuth2 server, and your consent screen](/images/docs/oauth-server/diagram-authorization.avif)

1. The client sends the user to the **authorization endpoint** with its client ID, a registered redirect URI, `response_type=code`, and the scopes it wants.
2. Appwrite checks whether the user has a session on your project. If they are signed in, Appwrite creates a pending authorization request called a **grant**. The grant connects the user, client, requested scopes, and redirect URI.
3. Appwrite sends the browser to your **authorization URL**, which hosts your consent screen. The URL contains the grant ID for a signed-in user or the original authorization parameters for a signed-out user. Appwrite skips this step when the user already approved these scopes for this client and that consent is still valid, sending the browser straight to step 5.
4. Your consent screen signs the user in when needed, loads the grant, and lets the user approve or reject it.
5. On approval, Appwrite redirects the browser to the client's redirect URI with a short-lived authorization `code`.
6. The client exchanges the code for tokens at the token endpoint. See [Tokens](/docs/products/auth/oauth-server/tokens).

### PKCE

PKCE (Proof Key for Code Exchange) binds an authorization request to the client that started it. If someone intercepts the authorization code, they cannot exchange it without the original `code_verifier`.

The client creates a random `code_verifier`, hashes it with SHA-256 to produce a `code_challenge`, and sends both `code_challenge` and `code_challenge_method=S256` in the authorization request. Appwrite supports `S256` only.

PKCE is always required for **public clients**, which cannot safely keep a client secret. **Confidential clients** authenticate with a client secret and do not require PKCE by default. To require both protections for confidential clients, enable **Require PKCE** in the **Token lifetimes** card under **Auth > OAuth2 server > Server**. This setting is represented as `confidentialPkce` in the API.

### 1. Send the user to the authorization endpoint

The authorization endpoint is an Appwrite Cloud URL that the client opens in the user's browser. The client does not need an Appwrite SDK.

The request contains:

- `client_id`, which identifies the client asking for access.
- `redirect_uri`, which tells Appwrite where to return the browser after the user decides. It must match a URI registered for the client.
- `response_type=code`, which asks Appwrite to return an authorization code. The client exchanges this code for tokens later.
- `scope`, which lists the access the client is requesting.
- `state`, which the client uses to connect the callback to the request it started and protect against request forgery.
- `code_challenge` and `code_challenge_method=S256`, which are required for public clients and for confidential clients when **Require PKCE** is enabled.

```curl
curl -G 'https://<REGION>.cloud.appwrite.io/v1/oauth2/<PROJECT_ID>/authorize' \
  --data-urlencode 'client_id=<CLIENT_ID>' \
  --data-urlencode 'redirect_uri=https://client.example.com/callback' \
  --data-urlencode 'response_type=code' \
  --data-urlencode 'scope=openid profile' \
  --data-urlencode 'state=<STATE>' \
  --data-urlencode 'code_challenge=<CODE_CHALLENGE>' \
  --data-urlencode 'code_challenge_method=S256'
```
```hurl
GET https://<REGION>.cloud.appwrite.io/v1/oauth2/<PROJECT_ID>/authorize
[Query]
client_id: <CLIENT_ID>
redirect_uri: https://client.example.com/callback
response_type: code
scope: openid profile
state: <STATE>
code_challenge: <CODE_CHALLENGE>
code_challenge_method: S256
```

After Appwrite validates the request, the browser goes to the authorization URL configured for your project. Your consent screen handles the user's session and decision.

#### Pushed authorization requests

Pushed authorization requests (PAR) keep the authorization parameters out of the browser URL. The client first sends the parameters to the PAR endpoint:

```curl
curl -X POST 'https://<REGION>.cloud.appwrite.io/v1/oauth2/<PROJECT_ID>/par' \
  -H 'Content-Type: application/json' \
  -d '{
    "client_id": "<CLIENT_ID>",
    "redirect_uri": "https://client.example.com/callback",
    "response_type": "code",
    "scope": "openid profile",
    "state": "<STATE>",
    "code_challenge": "<CODE_CHALLENGE>",
    "code_challenge_method": "S256"
  }'
```
```hurl
POST https://<REGION>.cloud.appwrite.io/v1/oauth2/<PROJECT_ID>/par
Content-Type: application/json
{
    "client_id": "<CLIENT_ID>",
    "redirect_uri": "https://client.example.com/callback",
    "response_type": "code",
    "scope": "openid profile",
    "state": "<STATE>",
    "code_challenge": "<CODE_CHALLENGE>",
    "code_challenge_method": "S256"
}
```

Appwrite returns a `request_uri` and the number of seconds before it expires:

```json
{
  "request_uri": "urn:appwrite:oauth2:request:<REQUEST_ID>",
  "expires_in": 600
}
```

Before it expires, send the user to the authorization endpoint with only the `request_uri`. Do not repeat the original authorization parameters.

```curl
curl -G 'https://<REGION>.cloud.appwrite.io/v1/oauth2/<PROJECT_ID>/authorize' \
  --data-urlencode 'request_uri=<REQUEST_URI>'
```
```hurl
GET https://<REGION>.cloud.appwrite.io/v1/oauth2/<PROJECT_ID>/authorize
[Query]
request_uri: <REQUEST_URI>
```

### 2. Host the consent screen

The authorization URL is a page you host. It must make sure the user is signed in, load the pending grant, explain what the client is requesting, and record the user's decision.

The examples in this section use the generated client SDKs. The authorization URL is normally a browser page, but the same grant operations are available across the client SDKs.

#### Find or create the grant

A grant is Appwrite's record of one pending authorization request. It identifies the signed-in user and client, and stores the requested scopes, resources, and redirect URI. The `grant_id` in the consent page URL identifies the record your page needs to load.

Handle the incoming URL in this order:

1. Look for `grant_id` in the URL. If it is present, save the value and pass it to `oauth2.getGrant()` in [Load the request](#load-grant). The returned grant carries the requesting client's ID along with the scopes and resources to show the user.
2. If `grant_id` is missing, call `account.get()` to check whether the user has a project session.
3. If the user is signed out, build a return URL from the consent page's current path and query string. Send the user to your normal sign-in or sign-up page with that value in a `redirect` search parameter.
4. After authentication, read `redirect` and return the user to it. The original authorization parameters are now available alongside the user's session.

##### Create the grant after sign-in

When the signed-in user returns without `grant_id`, call `oauth2.authorize()` with the original authorization parameters. Use the returned `grantId` to continue. If the SDK returns `redirectUrl` instead, send the user there.

```client-web
import { Client, Oauth2 } from 'appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

const oauth2 = new Oauth2(client);

const result = await oauth2.authorize({
    clientId: '<CLIENT_ID>',
    redirectUri: 'https://example.com',
    responseType: 'code',
    scope: '<SCOPE>', // optional
    state: '<STATE>', // optional
    nonce: '<NONCE>', // optional
    codeChallenge: '<CODE_CHALLENGE>', // optional
    codeChallengeMethod: 'S256', // optional
    prompt: '<PROMPT>', // optional
    maxAge: 0, // optional
    authorizationDetails: '<AUTHORIZATION_DETAILS>', // optional
    resource: '' // optional
});

console.log(result);
```
```client-flutter
import 'package:appwrite/appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

Oauth2 oauth2 = Oauth2(client);

Oauth2Authorize result = await oauth2.authorize(
    clientId: '<CLIENT_ID>',
    redirectUri: 'https://example.com',
    responseType: 'code',
    scope: '<SCOPE>', // optional
    state: '<STATE>', // optional
    nonce: '<NONCE>', // optional
    codeChallenge: '<CODE_CHALLENGE>', // optional
    codeChallengeMethod: 'S256', // optional
    prompt: '<PROMPT>', // optional
    maxAge: 0, // optional
    authorizationDetails: '<AUTHORIZATION_DETAILS>', // optional
    resource: '', // optional
);
```
```client-apple
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>") // Your project ID

let oauth2 = Oauth2(client)

let oauth2Authorize = try await oauth2.authorize(
    clientId: "<CLIENT_ID>",
    redirectUri: "https://example.com",
    responseType: "code",
    scope: "<SCOPE>", // optional
    state: "<STATE>", // optional
    nonce: "<NONCE>", // optional
    codeChallenge: "<CODE_CHALLENGE>", // optional
    codeChallengeMethod: "S256", // optional
    prompt: "<PROMPT>", // optional
    maxAge: 0, // optional
    authorizationDetails: "<AUTHORIZATION_DETAILS>", // optional
    resource: "" // optional
)
```
```client-android-kotlin
import io.appwrite.Client
import io.appwrite.coroutines.CoroutineCallback
import io.appwrite.services.Oauth2

val client = Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>") // Your project ID

val oauth2 = Oauth2(client)

val result = oauth2.authorize(
    clientId = "<CLIENT_ID>", 
    redirectUri = "https://example.com", 
    responseType = "code", 
    scope = "<SCOPE>", // (optional)
    state = "<STATE>", // (optional)
    nonce = "<NONCE>", // (optional)
    codeChallenge = "<CODE_CHALLENGE>", // (optional)
    codeChallengeMethod = "S256", // (optional)
    prompt = "<PROMPT>", // (optional)
    maxAge = 0, // (optional)
    authorizationDetails = "<AUTHORIZATION_DETAILS>", // (optional)
    resource = "", // (optional)
)
```
```client-android-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Oauth2;

Client client = new Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>"); // Your project ID

Oauth2 oauth2 = new Oauth2(client);

oauth2.authorize(
    "<CLIENT_ID>", // client_id 
    "https://example.com", // redirect_uri 
    "code", // response_type 
    "<SCOPE>", // scope (optional)
    "<STATE>", // state (optional)
    "<NONCE>", // nonce (optional)
    "<CODE_CHALLENGE>", // code_challenge (optional)
    "S256", // code_challenge_method (optional)
    "<PROMPT>", // prompt (optional)
    0, // max_age (optional)
    "<AUTHORIZATION_DETAILS>", // authorization_details (optional)
    "", // resource (optional)
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        Log.d("Appwrite", result.toString());
    })
);
```
```client-react-native
import { Client, Oauth2 } from "react-native-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

const oauth2 = new Oauth2(client);

const result = await oauth2.authorize({
    clientId: '<CLIENT_ID>',
    redirectUri: 'https://example.com',
    responseType: 'code',
    scope: '<SCOPE>', // optional
    state: '<STATE>', // optional
    nonce: '<NONCE>', // optional
    codeChallenge: '<CODE_CHALLENGE>', // optional
    codeChallengeMethod: 'S256', // optional
    prompt: '<PROMPT>', // optional
    maxAge: 0, // optional
    authorizationDetails: '<AUTHORIZATION_DETAILS>', // optional
    resource: '' // optional
});

console.log(result);
```

For a PAR request, send the browser back to the authorization endpoint with only its `request_uri`, as shown in [Pushed authorization requests](#par).

#### Load the request

Pass the grant ID to `oauth2.getGrant()`. Use the returned scopes and resources to explain the request on the consent screen, and keep the same grant ID for the approve or reject call after the user decides.

The grant identifies the client by `appId` only. To show its name, logo, and tagline, pass that ID to [`apps.get()`](/docs/products/auth/oauth-server/clients#get).

```client-web
import { Client, Oauth2 } from 'appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

const oauth2 = new Oauth2(client);

const result = await oauth2.getGrant({
    grantId: '<GRANT_ID>'
});

console.log(result);
```
```client-flutter
import 'package:appwrite/appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

Oauth2 oauth2 = Oauth2(client);

Oauth2Grant result = await oauth2.getGrant(
    grantId: '<GRANT_ID>',
);
```
```client-apple
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>") // Your project ID

let oauth2 = Oauth2(client)

let oauth2Grant = try await oauth2.getGrant(
    grantId: "<GRANT_ID>"
)
```
```client-android-kotlin
import io.appwrite.Client
import io.appwrite.coroutines.CoroutineCallback
import io.appwrite.services.Oauth2

val client = Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>") // Your project ID

val oauth2 = Oauth2(client)

val result = oauth2.getGrant(
    grantId = "<GRANT_ID>", 
)
```
```client-android-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Oauth2;

Client client = new Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>"); // Your project ID

Oauth2 oauth2 = new Oauth2(client);

oauth2.getGrant(
    "<GRANT_ID>", // grant_id 
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        Log.d("Appwrite", result.toString());
    })
);
```
```client-react-native
import { Client, Oauth2 } from "react-native-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

const oauth2 = new Oauth2(client);

const result = await oauth2.getGrant({
    grantId: '<GRANT_ID>'
});

console.log(result);
```

#### Record the user's decision

When the user approves the request, you can pass the scopes they kept selected. Omit `scope` to approve every scope in the grant. Narrowing only works downward. A scope that was not in the grant is rejected, and `openid` is added back if the user dropped it, because it is the sign-in itself.

```client-web
import { Client, Oauth2 } from 'appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

const oauth2 = new Oauth2(client);

const result = await oauth2.approve({
    grantId: '<GRANT_ID>',
    authorizationDetails: '<AUTHORIZATION_DETAILS>', // optional
    scope: '<SCOPE>' // optional
});

console.log(result);
```
```client-flutter
import 'package:appwrite/appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

Oauth2 oauth2 = Oauth2(client);

Oauth2Approve result = await oauth2.approve(
    grantId: '<GRANT_ID>',
    authorizationDetails: '<AUTHORIZATION_DETAILS>', // optional
    scope: '<SCOPE>', // optional
);
```
```client-apple
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>") // Your project ID

let oauth2 = Oauth2(client)

let oauth2Approve = try await oauth2.approve(
    grantId: "<GRANT_ID>",
    authorizationDetails: "<AUTHORIZATION_DETAILS>", // optional
    scope: "<SCOPE>" // optional
)
```
```client-android-kotlin
import io.appwrite.Client
import io.appwrite.coroutines.CoroutineCallback
import io.appwrite.services.Oauth2

val client = Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>") // Your project ID

val oauth2 = Oauth2(client)

val result = oauth2.approve(
    grantId = "<GRANT_ID>", 
    authorizationDetails = "<AUTHORIZATION_DETAILS>", // (optional)
    scope = "<SCOPE>", // (optional)
)
```
```client-android-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Oauth2;

Client client = new Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>"); // Your project ID

Oauth2 oauth2 = new Oauth2(client);

oauth2.approve(
    "<GRANT_ID>", // grant_id 
    "<AUTHORIZATION_DETAILS>", // authorization_details (optional)
    "<SCOPE>", // scope (optional)
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        Log.d("Appwrite", result.toString());
    })
);
```
```client-react-native
import { Client, Oauth2 } from "react-native-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

const oauth2 = new Oauth2(client);

const result = await oauth2.approve({
    grantId: '<GRANT_ID>',
    authorizationDetails: '<AUTHORIZATION_DETAILS>', // optional
    scope: '<SCOPE>' // optional
});

console.log(result);
```

The SDK response contains `redirectUrl`. Send the user to that URL to return them to the client.

If the user declines, reject the grant and return them to the client with an `access_denied` error.

```client-web
import { Client, Oauth2 } from 'appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

const oauth2 = new Oauth2(client);

const result = await oauth2.reject({
    grantId: '<GRANT_ID>'
});

console.log(result);
```
```client-flutter
import 'package:appwrite/appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

Oauth2 oauth2 = Oauth2(client);

Oauth2Reject result = await oauth2.reject(
    grantId: '<GRANT_ID>',
);
```
```client-apple
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>") // Your project ID

let oauth2 = Oauth2(client)

let oauth2Reject = try await oauth2.reject(
    grantId: "<GRANT_ID>"
)
```
```client-android-kotlin
import io.appwrite.Client
import io.appwrite.coroutines.CoroutineCallback
import io.appwrite.services.Oauth2

val client = Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>") // Your project ID

val oauth2 = Oauth2(client)

val result = oauth2.reject(
    grantId = "<GRANT_ID>", 
)
```
```client-android-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Oauth2;

Client client = new Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>"); // Your project ID

Oauth2 oauth2 = new Oauth2(client);

oauth2.reject(
    "<GRANT_ID>", // grant_id 
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        Log.d("Appwrite", result.toString());
    })
);
```
```client-react-native
import { Client, Oauth2 } from "react-native-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

const oauth2 = new Oauth2(client);

const result = await oauth2.reject({
    grantId: '<GRANT_ID>'
});

console.log(result);
```

The SDK requests JSON responses so it can behave consistently with other SDK methods. The approve and reject methods therefore return `redirectUrl`, and your browser code must navigate to it. A direct HTTP request without an `Accept: application/json` header receives a `303` redirect automatically.

#### Consent screen best practices

- Show the client's name and logo so the user can recognize who is asking for access.
- Name the client in the approval action, such as **Allow Vantage**.
- Explain each scope in plain language instead of showing only its identifier.
- Let the user turn off optional scopes. Pass the remaining scopes to `oauth2.approve()`.
- Call attention to scopes that allow writing, deleting, broad access, or administrator-level access.
- Give the reject action clear, visible placement. Consent should be a real choice.
- Do not ask the user for their Appwrite password on the consent screen. Send signed-out users through your normal authentication flow.

### Device flow authorization

Device flow is used when the client cannot easily open a browser or accept a callback, such as a TV or command-line tool. The client starts device authorization and shows the user a verification URL and user code. See [Device flow](/docs/products/auth/oauth-server/device-flow) for the client requests and polling behavior.

Your verification page on the user's second device completes the authorization:

1. Read `user_code` from the URL. If it is missing, let the user enter the code shown on the original device.
2. Ask the user to confirm that the code matches the one on the original device.
3. Make sure the user is signed in. If not, use the same `redirect` pattern as the consent screen so the user returns with the code intact.
4. Call `oauth2.createGrant({ userCode })`. This connects the pending device request to the signed-in user and returns its grant record, including the client and requested access to show on the verification page.
5. Pass the returned grant's ID to `oauth2.approve()` or `oauth2.reject()` after the user decides.

The original device continues polling the token endpoint at the response's `interval`. After the user approves the grant, the device receives access and refresh tokens directly. Device flow does not use an authorization code callback.

### Redirect URI matching

The `redirect_uri` on an authorization request must exactly match one of the client's registered redirect URIs, character for character. This stops an attacker from redirecting a code to a URL they control.

There is one narrow exception. For **public** clients, an `http` loopback address (`localhost`, `127.0.0.1`, or `[::1]`) matches on everything except the port. Native and CLI apps bind an unpredictable local port at runtime and cannot register it ahead of time (RFC 8252). Confidential clients get no such exception. Their redirect URIs must match exactly, including the port.

---

## Clients

URL: https://appwrite.io/docs/products/auth/oauth-server/clients

A **client** is a third-party app that authenticates users through your project's OAuth2 server. Each client registers the redirect URIs it is allowed to return to and the post-logout redirect URIs it can end sessions at, sets its type, and chooses whether the [device flow](/docs/products/auth/oauth-server/device-flow) is enabled. Its other attributes serve two surfaces: branding like the name, logo, and tagline can appear on your consent screen, while attributes like tags, images, and the privacy policy URL are for your project's apps marketplace.

Integrators never visit the Appwrite Console. You are expected to build a developer platform on your own website with the [Client SDKs](/docs/sdks#client)' `apps` service, which covers the full lifecycle: `create`, `update`, `get`, `list`, `delete`, along with `createSecret`, `listSecrets`, `getSecret`, `deleteSecret`, `updateTeam`, and `deleteTokens`. Any signed-in user on your project can call them; no API key is involved. The Console's **Auth > OAuth2 server > Apps** tab is your own administrative view of the same data.

![OAuth2 clients list in the Appwrite Console](/images/docs/oauth-server/oauth2-server-apps-list.avif)

Two operations need a project API key instead of a client secret. [Introspecting](/docs/products/auth/oauth-server/tokens#introspect) a public client's tokens needs one because a public client has no secret to authenticate with, and the introspection endpoint has no SDK method, so call it over HTTP. [Curating labels](#labels) is an administrative operation that the Server SDKs expose.

### Confidential and public clients

![Confidential clients exchange the code from their backend with a client secret, while public clients exchange it from the device with PKCE](/images/docs/oauth-server/diagram-clients.avif)

Every client is one of two types, and the difference comes down to a single question: can the app keep a secret?

- A **confidential** client runs code on a server the developer controls, so it can store a `client_secret` that users never see. It authenticates to the token endpoint with that secret, which lets your server prove which client is calling.
- A **public** client runs entirely on the user's device (a single-page app, a native mobile app, a CLI), where any embedded secret would ship to the user and could be read. Public clients receive no secret and rely on PKCE instead.

![Token lifetimes for confidential and public clients](/images/docs/oauth-server/oauth2-server-token-lifetimes.avif)

The type a client uses changes what it can do:

| | Confidential | Public |
| --- | --- | --- |
| Client secret | Issued, sent on token requests | None issued |
| PKCE | Optional (configurable per project) | Always required |
| Token introspection | With its client secret | With a project API key holding the `oauth2.introspect` scope |
| Default access token lifetime | 8 hours | 1 hour |
| Default refresh token lifetime | 365 days | 30 days |

Choose confidential whenever the app has a backend. It is the safer default: the token exchange is protected by a secret, tokens never touch the browser, and sessions can last longer. Reserve public for apps that genuinely have no server to hold a secret.

### Register a client

Create a client with the `create` method. It needs an app ID, a name, and a redirect URI. Everything else can be filled in later with `update`. Registering a confidential client also calls for a [secret](#secrets) before it can exchange tokens.

```client-web
import { Client, Apps, ID } from 'appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

const apps = new Apps(client);

const app = await apps.create({
    appId: ID.unique(),
    name: 'Custom App',
    redirectUris: ['https://vantage.localhost/auth/redirect'],
    type: 'confidential',
});
```
```client-flutter
import 'package:appwrite/appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

Apps apps = Apps(client);

App app = await apps.create(
    appId: ID.unique(),
    name: 'Custom App',
    redirectUris: ['https://vantage.localhost/auth/redirect'],
    type: 'confidential',
);
```
```client-apple
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>") // Your project ID

let apps = Apps(client)

let app = try await apps.create(
    appId: ID.unique(),
    name: "Custom App",
    redirectUris: ["https://vantage.localhost/auth/redirect"],
    type: "confidential"
)
```
```client-android-kotlin
import io.appwrite.Client
import io.appwrite.ID
import io.appwrite.services.Apps

val client = Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>") // Your project ID

val apps = Apps(client)

val app = apps.create(
    appId = ID.unique(),
    name = "Custom App",
    redirectUris = listOf("https://vantage.localhost/auth/redirect"),
    type = "confidential"
)
```
```client-android-java
import io.appwrite.Client;
import io.appwrite.ID;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Apps;

Client client = new Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>"); // Your project ID

Apps apps = new Apps(client);

apps.create(
    ID.unique(), // appId
    "Custom App", // name
    List.of("https://vantage.localhost/auth/redirect"), // redirectUris
    null, // description (optional)
    null, // clientUri (optional)
    null, // logoUri (optional)
    null, // privacyPolicyUrl (optional)
    null, // termsUrl (optional)
    null, // contacts (optional)
    null, // tagline (optional)
    null, // tags (optional)
    null, // images (optional)
    null, // supportUrl (optional)
    null, // dataDeletionUrl (optional)
    null, // postLogoutRedirectUris (optional)
    null, // enabled (optional)
    "confidential", // type (optional)
    null, // deviceFlow (optional)
    null, // teamId (optional)
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        Log.d("Appwrite", result.toString());
    })
);
```
```client-react-native
import { Client, Apps, ID } from "react-native-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

const apps = new Apps(client);

const app = await apps.create({
    appId: ID.unique(),
    name: 'Custom App',
    redirectUris: ['https://vantage.localhost/auth/redirect'],
    type: 'confidential',
});
```

An app is owned by the user who created it. For team-oriented platforms, pass `teamId` on creation to make it team-owned instead: every team member can see it, while members with the `owner` or `developer` role manage it.

You can also create clients from the Console's **Apps** tab. It covers the same fields except `teamId`, so team ownership has to be set through the API.

![Create an OAuth2 client dialog](/images/docs/oauth-server/oauth2-server-create-app.avif)

**Use Storage for logos and images**

`logoUri` and `images` accept URLs, not files. [Appwrite Storage](/docs/products/storage) pairs well here: upload the file to a bucket, take its preview URL, and store that URL on the app.

### Manage client secrets

A confidential client authenticates with a secret. Four methods manage them in one place: `createSecret`, `listSecrets`, `getSecret`, and `deleteSecret`.

Generate a new secret with `createSecret`. The plaintext value is returned only in this response.

![OAuth2 client secret shown once on creation](/images/docs/oauth-server/oauth2-server-secret-created.avif)

```client-web
import { Client, Apps } from 'appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

const apps = new Apps(client);

const secret = await apps.createSecret({
    appId: '<APP_ID>',
});
```
```client-flutter
import 'package:appwrite/appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

Apps apps = Apps(client);

AppSecretPlaintext secret = await apps.createSecret(
    appId: '<APP_ID>',
);
```
```client-apple
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>") // Your project ID

let apps = Apps(client)

let secret = try await apps.createSecret(
    appId: "<APP_ID>"
)
```
```client-android-kotlin
import io.appwrite.Client
import io.appwrite.services.Apps

val client = Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>") // Your project ID

val apps = Apps(client)

val secret = apps.createSecret(
    appId = "<APP_ID>"
)
```
```client-android-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Apps;

Client client = new Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>"); // Your project ID

Apps apps = new Apps(client);

apps.createSecret(
    "<APP_ID>", // appId
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        Log.d("Appwrite", result.toString());
    })
);
```
```client-react-native
import { Client, Apps } from "react-native-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

const apps = new Apps(client);

const secret = await apps.createSecret({
    appId: '<APP_ID>',
});
```

A client can hold several secrets at once, which is how you rotate them without downtime: create the new secret, roll it out, then delete the old one. Each entry in `listSecrets` carries the metadata for deciding which secret can be removed safely: a `hint` of the value, who created it (`createdById`, `createdByName`), when it was created, and `lastAccessedAt` for when it last authenticated a request.

```client-web
import { Client, Apps } from 'appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

const apps = new Apps(client);

const secrets = await apps.listSecrets({
    appId: '<APP_ID>',
});
```
```client-flutter
import 'package:appwrite/appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

Apps apps = Apps(client);

AppSecretList secrets = await apps.listSecrets(
    appId: '<APP_ID>',
);
```
```client-apple
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>") // Your project ID

let apps = Apps(client)

let secrets = try await apps.listSecrets(
    appId: "<APP_ID>"
)
```
```client-android-kotlin
import io.appwrite.Client
import io.appwrite.services.Apps

val client = Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>") // Your project ID

val apps = Apps(client)

val secrets = apps.listSecrets(
    appId = "<APP_ID>"
)
```
```client-android-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Apps;

Client client = new Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>"); // Your project ID

Apps apps = new Apps(client);

apps.listSecrets(
    "<APP_ID>", // appId
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        Log.d("Appwrite", result.toString());
    })
);
```
```client-react-native
import { Client, Apps } from "react-native-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

const apps = new Apps(client);

const secrets = await apps.listSecrets({
    appId: '<APP_ID>',
});
```

`getSecret` reads a single entry by ID, and `deleteSecret` revokes it immediately.

### List clients

The `list` method drives three different screens, depending on the queries you pass:

- **A developer portal**: filter by the signed-in user with `Query.equal('userId', userId)` so developers manage their own apps.
- **Team settings**: filter with `Query.equal('teamId', teamId)` for the apps a team owns.
- **An apps marketplace**: list without an owner filter to show all registered apps. Filter by [labels](#labels), such as `Query.contains('labels', ['official'])`, when the marketplace should only show apps you have vetted, because labels cannot be self-assigned.

Always paginate with `Query.limit()` and `Query.cursorAfter()`; a marketplace can grow past any single page.

```client-web
import { Client, Apps, Query } from 'appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

const apps = new Apps(client);

const portal = await apps.list({
    queries: [
        Query.equal('userId', '<SIGNED_IN_USER_ID>'),
        Query.limit(25),
    ],
});
```
```client-flutter
import 'package:appwrite/appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

Apps apps = Apps(client);

AppsList portal = await apps.list(
    queries: [
        Query.equal('userId', '<SIGNED_IN_USER_ID>'),
        Query.limit(25),
    ],
);
```
```client-apple
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>") // Your project ID

let apps = Apps(client)

let portal = try await apps.list(
    queries: [
        Query.equal("userId", value: "<SIGNED_IN_USER_ID>"),
        Query.limit(25)
    ]
)
```
```client-android-kotlin
import io.appwrite.Client
import io.appwrite.Query
import io.appwrite.services.Apps

val client = Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>") // Your project ID

val apps = Apps(client)

val portal = apps.list(
    queries = listOf(
        Query.equal("userId", "<SIGNED_IN_USER_ID>"),
        Query.limit(25)
    )
)
```
```client-android-java
import io.appwrite.Client;
import io.appwrite.Query;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Apps;

Client client = new Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>"); // Your project ID

Apps apps = new Apps(client);

apps.list(
    List.of(
        Query.Companion.equal("userId", "<SIGNED_IN_USER_ID>"),
        Query.Companion.limit(25)
    ), // queries (optional)
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        Log.d("Appwrite", result.toString());
    })
);
```
```client-react-native
import { Client, Apps, Query } from "react-native-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

const apps = new Apps(client);

const portal = await apps.list({
    queries: [
        Query.equal('userId', '<SIGNED_IN_USER_ID>'),
        Query.limit(25),
    ],
});
```

### Get a client

Read a single client with `get`. This backs the app detail page in a developer portal, and a [consent screen](/docs/products/auth/oauth-server/authorization#consent) uses it to show the requesting app's name and logo.

Callers who cannot update the client get a trimmed response: `secrets`, `redirectUris`, and `deviceFlow` are removed. A consent screen reading another developer's app sees the branding it needs and nothing more. The same trimming applies to `list`.

```client-web
import { Client, Apps } from 'appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

const apps = new Apps(client);

const app = await apps.get({
    appId: '<APP_ID>',
});
```
```client-flutter
import 'package:appwrite/appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

Apps apps = Apps(client);

App app = await apps.get(
    appId: '<APP_ID>',
);
```
```client-apple
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>") // Your project ID

let apps = Apps(client)

let app = try await apps.get(
    appId: "<APP_ID>"
)
```
```client-android-kotlin
import io.appwrite.Client
import io.appwrite.services.Apps

val client = Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>") // Your project ID

val apps = Apps(client)

val app = apps.get(
    appId = "<APP_ID>"
)
```
```client-android-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Apps;

Client client = new Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>"); // Your project ID

Apps apps = new Apps(client);

apps.get(
    "<APP_ID>", // appId
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        Log.d("Appwrite", result.toString());
    })
);
```
```client-react-native
import { Client, Apps } from "react-native-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

const apps = new Apps(client);

const app = await apps.get({
    appId: '<APP_ID>',
});
```

### Update a client

Change a client's redirect URIs, branding, or type with the `update` method. It replaces the whole client rather than patching it, so any optional field you leave out is reset to its default. Send the values you want to keep alongside the ones you are changing. `name` is required on every call.

```client-web
import { Client, Apps } from 'appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

const apps = new Apps(client);

const app = await apps.update({
    appId: '<APP_ID>',
    name: 'Vantage',
    description: 'A dashboard that signs in with TaskFlow.',
    clientUri: 'https://vantage.localhost',
    logoUri: 'https://vantage.localhost/logo.png',
    privacyPolicyUrl: 'https://vantage.localhost/privacy',
    termsUrl: 'https://vantage.localhost/terms',
    contacts: ['security@vantage.localhost'],
    tagline: 'Product analytics for modern teams',
    tags: ['analytics', 'productivity'],
    images: ['https://vantage.localhost/screenshot.png'],
    supportUrl: 'https://vantage.localhost/support',
    dataDeletionUrl: 'https://vantage.localhost/data-deletion',
    enabled: true,
    redirectUris: ['https://vantage.localhost/auth/redirect'],
    postLogoutRedirectUris: ['https://vantage.localhost/signed-out'],
    type: 'confidential',
    deviceFlow: false,
});
```
```client-flutter
import 'package:appwrite/appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

Apps apps = Apps(client);

App app = await apps.update(
    appId: '<APP_ID>',
    name: 'Vantage',
    description: 'A dashboard that signs in with TaskFlow.',
    clientUri: 'https://vantage.localhost',
    logoUri: 'https://vantage.localhost/logo.png',
    privacyPolicyUrl: 'https://vantage.localhost/privacy',
    termsUrl: 'https://vantage.localhost/terms',
    contacts: ['security@vantage.localhost'],
    tagline: 'Product analytics for modern teams',
    tags: ['analytics', 'productivity'],
    images: ['https://vantage.localhost/screenshot.png'],
    supportUrl: 'https://vantage.localhost/support',
    dataDeletionUrl: 'https://vantage.localhost/data-deletion',
    enabled: true,
    redirectUris: ['https://vantage.localhost/auth/redirect'],
    postLogoutRedirectUris: ['https://vantage.localhost/signed-out'],
    type: 'confidential',
    deviceFlow: false,
);
```
```client-apple
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>") // Your project ID

let apps = Apps(client)

let app = try await apps.update(
    appId: "<APP_ID>",
    name: "Vantage",
    description: "A dashboard that signs in with TaskFlow.",
    clientUri: "https://vantage.localhost",
    logoUri: "https://vantage.localhost/logo.png",
    privacyPolicyUrl: "https://vantage.localhost/privacy",
    termsUrl: "https://vantage.localhost/terms",
    contacts: ["security@vantage.localhost"],
    tagline: "Product analytics for modern teams",
    tags: ["analytics", "productivity"],
    images: ["https://vantage.localhost/screenshot.png"],
    supportUrl: "https://vantage.localhost/support",
    dataDeletionUrl: "https://vantage.localhost/data-deletion",
    enabled: true,
    redirectUris: ["https://vantage.localhost/auth/redirect"],
    postLogoutRedirectUris: ["https://vantage.localhost/signed-out"],
    type: "confidential",
    deviceFlow: false
)
```
```client-android-kotlin
import io.appwrite.Client
import io.appwrite.services.Apps

val client = Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>") // Your project ID

val apps = Apps(client)

val app = apps.update(
    appId = "<APP_ID>",
    name = "Vantage",
    description = "A dashboard that signs in with TaskFlow.",
    clientUri = "https://vantage.localhost",
    logoUri = "https://vantage.localhost/logo.png",
    privacyPolicyUrl = "https://vantage.localhost/privacy",
    termsUrl = "https://vantage.localhost/terms",
    contacts = listOf("security@vantage.localhost"),
    tagline = "Product analytics for modern teams",
    tags = listOf("analytics", "productivity"),
    images = listOf("https://vantage.localhost/screenshot.png"),
    supportUrl = "https://vantage.localhost/support",
    dataDeletionUrl = "https://vantage.localhost/data-deletion",
    enabled = true,
    redirectUris = listOf("https://vantage.localhost/auth/redirect"),
    postLogoutRedirectUris = listOf("https://vantage.localhost/signed-out"),
    type = "confidential",
    deviceFlow = false,
)
```
```client-android-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Apps;

Client client = new Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>"); // Your project ID

Apps apps = new Apps(client);

apps.update(
    "<APP_ID>", // appId
    "Vantage", // name
    "A dashboard that signs in with TaskFlow.", // description
    "https://vantage.localhost", // clientUri
    "https://vantage.localhost/logo.png", // logoUri
    "https://vantage.localhost/privacy", // privacyPolicyUrl
    "https://vantage.localhost/terms", // termsUrl
    List.of("security@vantage.localhost"), // contacts
    "Product analytics for modern teams", // tagline
    List.of("analytics", "productivity"), // tags
    List.of("https://vantage.localhost/screenshot.png"), // images
    "https://vantage.localhost/support", // supportUrl
    "https://vantage.localhost/data-deletion", // dataDeletionUrl
    true, // enabled
    List.of("https://vantage.localhost/auth/redirect"), // redirectUris
    List.of("https://vantage.localhost/signed-out"), // postLogoutRedirectUris
    "confidential", // type
    false, // deviceFlow
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        Log.d("Appwrite", result.toString());
    })
);
```
```client-react-native
import { Client, Apps } from "react-native-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

const apps = new Apps(client);

const app = await apps.update({
    appId: '<APP_ID>',
    name: 'Vantage',
    description: 'A dashboard that signs in with TaskFlow.',
    clientUri: 'https://vantage.localhost',
    logoUri: 'https://vantage.localhost/logo.png',
    privacyPolicyUrl: 'https://vantage.localhost/privacy',
    termsUrl: 'https://vantage.localhost/terms',
    contacts: ['security@vantage.localhost'],
    tagline: 'Product analytics for modern teams',
    tags: ['analytics', 'productivity'],
    images: ['https://vantage.localhost/screenshot.png'],
    supportUrl: 'https://vantage.localhost/support',
    dataDeletionUrl: 'https://vantage.localhost/data-deletion',
    enabled: true,
    redirectUris: ['https://vantage.localhost/auth/redirect'],
    postLogoutRedirectUris: ['https://vantage.localhost/signed-out'],
    type: 'confidential',
    deviceFlow: false,
});
```

The `type` parameter accepts `confidential` (the default) or `public`. Switching an existing client to `public` fails while it still holds client secrets, so delete every secret first. Set `deviceFlow` to `true` to let the client use the [device authorization flow](/docs/products/auth/oauth-server/device-flow). The branding fields (`logoUri`, `tagline`, `privacyPolicyUrl`, `termsUrl`) can appear on the consent screen, and they fill out the app's listing on your marketplace.

### Transfer to a team

Convert a user-owned app to team ownership, or move it between teams, with `updateTeam`. The member doing the transfer needs the `owner` or `developer` role in the app's current team, and at least membership in the new one.

```client-web
import { Client, Apps } from 'appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

const apps = new Apps(client);

const app = await apps.updateTeam({
    appId: '<APP_ID>',
    teamId: '<TEAM_ID>',
});
```
```client-flutter
import 'package:appwrite/appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

Apps apps = Apps(client);

App app = await apps.updateTeam(
    appId: '<APP_ID>',
    teamId: '<TEAM_ID>',
);
```
```client-apple
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>") // Your project ID

let apps = Apps(client)

let app = try await apps.updateTeam(
    appId: "<APP_ID>",
    teamId: "<TEAM_ID>"
)
```
```client-android-kotlin
import io.appwrite.Client
import io.appwrite.services.Apps

val client = Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>") // Your project ID

val apps = Apps(client)

val app = apps.updateTeam(
    appId = "<APP_ID>",
    teamId = "<TEAM_ID>"
)
```
```client-android-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Apps;

Client client = new Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>"); // Your project ID

Apps apps = new Apps(client);

apps.updateTeam(
    "<APP_ID>", // appId
    "<TEAM_ID>", // teamId
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        Log.d("Appwrite", result.toString());
    })
);
```
```client-react-native
import { Client, Apps } from "react-native-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

const apps = new Apps(client);

const app = await apps.updateTeam({
    appId: '<APP_ID>',
    teamId: '<TEAM_ID>',
});
```

### Curate with labels

Labels are trust markers like `official`, `partner`, or `verified`. They are read-only for clients. Only a [Server SDK](/docs/sdks#server) using a project API key with the `apps.write` scope can set them, so app owners cannot mark themselves as trusted. That is what makes them safe to filter a marketplace by.

The `official` label is special and final. Once a client carries it, every write to that client is refused, including another call to `updateLabels`, so the label cannot be removed through the API. Apply it only to clients you publish yourself.

```server-nodejs
import { Client, Apps } from '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 apps = new Apps(client);

const app = await apps.updateLabels({
    appId: '<APP_ID>',
    labels: ['official'],
});
```
```server-deno
import { Client, Apps } 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 apps = new Apps(client);

const app = await apps.updateLabels({
    appId: '<APP_ID>',
    labels: ['official'],
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Apps;

$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

$apps = new Apps($client);

$app = $apps->updateLabels(
    appId: '<APP_ID>',
    labels: ['official']
);
```
```server-python
from appwrite.client import Client
from appwrite.services.apps import Apps

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

apps = Apps(client)

app = apps.update_labels(
    app_id = '<APP_ID>',
    labels = ['official']
)
```
```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

apps = Apps.new(client)

app = apps.update_labels(
    app_id: '<APP_ID>',
    labels: ['official']
)
```
```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

Apps apps = Apps(client);

App app = await apps.updateLabels(
    appId: '<APP_ID>',
    labels: ['official'],
);
```
```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

Apps apps = new Apps(client);

App app = await apps.UpdateLabels(
    appId: "<APP_ID>",
    labels: new List<string> { "official" }
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Apps

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 apps = Apps(client)

val app = apps.updateLabels(
    appId = "<APP_ID>",
    labels = listOf("official")
)
```
```server-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Apps;

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

Apps apps = new Apps(client);

apps.updateLabels(
    "<APP_ID>", // appId
    List.of("official"), // labels
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        System.out.println(result);
    })
);
```
```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 apps = Apps(client)

let app = try await apps.updateLabels(
    appId: "<APP_ID>",
    labels: ["official"]
)
```
```server-go
package main

import (
    "fmt"
    "log"

    "github.com/appwrite/sdk-for-go/v6/appwrite"
)

func main() {
    client := appwrite.NewClient(
        appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"),
        appwrite.WithProject("<YOUR_PROJECT_ID>"),
        appwrite.WithKey("<YOUR_API_KEY>"),
    )

    apps := appwrite.NewApps(client)

    app, err := apps.UpdateLabels(
        "<APP_ID>",
        []string{"official"},
    )
    if err != nil {
        log.Fatal(err)
    }

    fmt.Println(app)
}
```
```server-rust
use appwrite::Client;
use appwrite::services::Apps;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let 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

    let apps = Apps::new(&client);

    let app = apps
        .update_labels(
            "<APP_ID>",
            vec!["official"],
        )
        .await?;

    let _ = app;

    Ok(())
}
```

Labels replace the previous set on every call. Up to 1000 labels are allowed, each up to 36 alphanumeric characters.

### Revoke all tokens

`deleteTokens` invalidates every token issued to a client at once: a kill switch for all of its sessions. Reach for it when testing, since it forces the consent screen to reappear, or as an emergency response to a leaked secret, together with rotating the secret itself.

```client-web
import { Client, Apps } from 'appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

const apps = new Apps(client);

await apps.deleteTokens({
    appId: '<APP_ID>',
});
```
```client-flutter
import 'package:appwrite/appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

Apps apps = Apps(client);

await apps.deleteTokens(
    appId: '<APP_ID>',
);
```
```client-apple
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>") // Your project ID

let apps = Apps(client)

try await apps.deleteTokens(
    appId: "<APP_ID>"
)
```
```client-android-kotlin
import io.appwrite.Client
import io.appwrite.services.Apps

val client = Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>") // Your project ID

val apps = Apps(client)

apps.deleteTokens(
    appId = "<APP_ID>"
)
```
```client-android-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Apps;

Client client = new Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>"); // Your project ID

Apps apps = new Apps(client);

apps.deleteTokens(
    "<APP_ID>", // appId
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        Log.d("Appwrite", result.toString());
    })
);
```
```client-react-native
import { Client, Apps } from "react-native-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

const apps = new Apps(client);

await apps.deleteTokens({
    appId: '<APP_ID>',
});
```

### Delete a client

Deleting a client immediately invalidates every token issued to it.

```client-web
import { Client, Apps } from 'appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

const apps = new Apps(client);

await apps.delete({
    appId: '<APP_ID>',
});
```
```client-flutter
import 'package:appwrite/appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

Apps apps = Apps(client);

await apps.delete(
    appId: '<APP_ID>',
);
```
```client-apple
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>") // Your project ID

let apps = Apps(client)

try await apps.delete(
    appId: "<APP_ID>"
)
```
```client-android-kotlin
import io.appwrite.Client
import io.appwrite.services.Apps

val client = Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>") // Your project ID

val apps = Apps(client)

apps.delete(
    appId = "<APP_ID>"
)
```
```client-android-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Apps;

Client client = new Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>"); // Your project ID

Apps apps = new Apps(client);

apps.delete(
    "<APP_ID>", // appId
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        Log.d("Appwrite", result.toString());
    })
);
```
```client-react-native
import { Client, Apps } from "react-native-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

const apps = new Apps(client);

await apps.delete({
    appId: '<APP_ID>',
});
```

### Dynamic client registration

Clients can also register themselves over plain HTTP, without an Appwrite SDK or a signed-in user, through the registration endpoint ([RFC 7591](https://datatracker.ietf.org/doc/html/rfc7591)). This is what makes your OAuth2 server compatible with MCP servers and other software that provisions its own client on first contact. Registration is rate-limited per IP.

**Dynamically registered apps**

Apps created through this endpoint receive the reserved `oauth-dcr` label. Appwrite manages this label, so you cannot add or remove it. You can use it to exclude dynamically registered apps from your marketplace when calling `apps.list()`.

```curl
curl -X POST 'https://<REGION>.cloud.appwrite.io/v1/oauth2/<PROJECT_ID>/register' \
  -H 'Content-Type: application/json' \
  -d '{
    "client_name": "MCP Client",
    "redirect_uris": [
        "https://vantage.localhost/auth/redirect"
    ],
    "token_endpoint_auth_method": "none"
  }'
```
```hurl
POST https://<REGION>.cloud.appwrite.io/v1/oauth2/<PROJECT_ID>/register
Content-Type: application/json
{
    "client_name": "MCP Client",
    "redirect_uris": [
        "https://vantage.localhost/auth/redirect"
    ],
    "token_endpoint_auth_method": "none"
}
```

```json
{
    "client_id": "6a56677caf736a2310a2",
    "client_id_issued_at": 1784047484,
    "redirect_uris": ["https://vantage.localhost/auth/redirect"],
    "token_endpoint_auth_method": "none",
    "grant_types": ["authorization_code"],
    "response_types": ["code"],
    "client_name": "MCP Client"
}
```

`token_endpoint_auth_method: none` registers a public client for PKCE; `client_secret_basic` (the default) and `client_secret_post` register confidential clients. The registered app appears in your Console and in `list` like any other client.

---

## Protect your API with custom scopes

URL: https://appwrite.io/docs/products/auth/oauth-server/custom-scopes/step-1

The [Sign in with your product](/docs/products/auth/oauth-server/sign-in-with-your-product/step-1) guide gave Vantage the user's identity. Identity alone only answers who the user is. To let an integration read the user's data from your product, you need [custom scopes](/docs/products/auth/oauth-server/scopes): permissions you define, users approve, and your API enforces.

This tutorial continues with the same two apps. TaskFlow gains a task API that checks scopes, and Vantage asks for permission to read the user's tasks and shows them on its dashboard.

### What you will build

- Two custom scopes on TaskFlow's OAuth2 server: `tasks.read` and `tasks.write`.
- A consent screen where the user grants or withholds **each permission individually**.
- A **resource server**: an API route on TaskFlow that validates access tokens and enforces the scopes they carry, operation by operation.
- A task composer on Vantage's dashboard that succeeds or gets refused depending on what the user granted.

The resource server is the new idea. The OAuth2 server issues tokens and stamps the granted scopes onto them, but it does not know what `tasks.read` means in your product. Your API gives the scope its meaning by checking it on every request.

### The flow

1. Vantage adds `tasks.read` and `tasks.write` to its authorization request.
2. TaskFlow's consent screen shows each permission, and the user decides which to grant.
3. The access token Vantage receives carries exactly the approved scopes in its `scope` claim.
4. Vantage calls TaskFlow's task API with the access token.
5. TaskFlow verifies the token's signature, checks the scope the operation needs, and allows or refuses.

Everything up to step 3 is the authorization code flow from the first guide, with more scopes in the request. Steps 4 and 5 are what you build here.

### Prerequisites

- The two apps from [Sign in with your product](/docs/products/auth/oauth-server/sign-in-with-your-product/step-1), running against a project with the OAuth2 server enabled and Vantage registered as a confidential client.
- [Node.js](https://nodejs.org/) 20 or newer and `pnpm`.

**Get the finished code**

The complete apps from this tutorial are on GitHub at [appwrite-community/oauth-guide-custom-scopes](https://github.com/appwrite-community/oauth-guide-custom-scopes). Clone it to follow along or to compare against your own.

Continue to define the scopes on your project.

---

## Define the scopes

URL: https://appwrite.io/docs/products/auth/oauth-server/custom-scopes/step-2

Scopes have to be defined on the OAuth2 server before a client can request them. Requesting a scope you have not defined fails the authorization request with `invalid_scope`.

### Add the scopes

![The tasks.read and tasks.write scopes on the OAuth2 server settings](/images/docs/oauth-server/scopes-defined.avif)

In the Console, open **Auth**, select the **OAuth2 server** tab, and find the **Scopes** field on the **Integration** card. Add two scopes and click **Update**:

- `tasks.read` grants read access to the user's tasks.
- `tasks.write` grants permission to create and update tasks.

The `openid`, `profile`, `email`, and `phone` scopes stay locked in place. They are always available, so you only define the ones specific to your product.

### Naming scopes

The `verb:resource` shape is a convention, not a requirement. Any string up to 128 characters works. Splitting read and write per resource keeps grants small: an integration that only shows data never has to ask for permission to change it, which is an easier ask on the consent screen.

Both scopes are now published in your project's [discovery document](/docs/products/auth/oauth-server/quick-start#discovery) under `scopes_supported`, so integrators can see them without asking you.

Continue to request the new scopes from Vantage.

---

## Request the scopes

URL: https://appwrite.io/docs/products/auth/oauth-server/custom-scopes/step-3

A client receives a scope by asking for it during [authorization](/docs/products/auth/oauth-server/authorization). Vantage requests both task scopes, and TaskFlow's consent screen lets the user decide which of them to grant.

### Add the scopes to the request

In the consumer, extend the scope list in `consumer/src/lib/oauth.ts`:

```ts
// consumer/src/lib/oauth.ts
// tasks.read and tasks.write are custom scopes defined on TaskFlow's
// OAuth2 server. They authorize Vantage against TaskFlow's task API.
export const SCOPES = 'openid profile email tasks.read tasks.write'
```

`authorizeUrl` already passes `SCOPES` as the `scope` parameter, so nothing else changes on the consumer. The OAuth2 server carries the requested scopes into the grant and shows them to the user.

### Label the scopes on the consent screen

TaskFlow's consent card maps scopes to human-readable lines through `SCOPE_LABELS`. Add labels for the new scopes in `provider/src/lib/consent-types.ts`:

```ts
// provider/src/lib/consent-types.ts
// Human-readable labels for the scopes shown on the consent card.
// Custom scopes get labels too, so users understand what they grant.
export const SCOPE_LABELS: Record<string, string> = {
  openid: 'Confirm your identity',
  profile: 'See your name and profile details',
  email: 'See your email address',
  phone: 'See your phone number',
  'tasks.read': 'See your tasks',
  'tasks.write': 'Create and update your tasks',
}
```

A raw scope string like `tasks.read` means something to you, not to your users. The label is what stands between them and approving a permission they do not understand.

### Let the user choose

Consent is not all-or-nothing. When approving a grant, the consent screen can pass the subset of scopes the user agreed to, and the OAuth2 server narrows the grant to exactly that. It rejects any scope that was not requested, and `openid` is always retained because it is the sign-in itself.

Replace the one-line `approve` export in `provider/src/lib/oauth-server.ts` with this function, which takes the chosen scopes. Delete the old `export const approve = ...` line, otherwise the two declarations collide. `reject` keeps using `decide`.

```ts
// provider/src/lib/oauth-server.ts
/** Approve a grant. The OAuth2 server responds with the URL to send the
 *  user back to, carrying the authorization code. Passing scopes narrows
 *  the grant to that subset; the server rejects anything that was not
 *  requested and always retains openid. */
export async function approve(
  grantId: string,
  sessionToken: string,
  scopes?: string[],
): Promise<string> {
  const res = await fetch(`${issuer}/approve`, {
    method: 'POST',
    headers: {
      ...projectHeaders,
      'X-Appwrite-Session': sessionToken,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      grant_id: grantId,
      ...(scopes ? { scope: scopes.join(' ') } : {}),
    }),
    redirect: 'manual',
  })
  const location = res.headers.get('location')
  if (!location) throw new Error(`Approve failed: ${res.status}`)
  return location
}
```

On the consent card in `provider/src/routes/oauth.consent.tsx`, track the user's selection and send it on approve. Each permission renders as a toggle, on by default, with `openid` locked:

```tsx
// provider/src/routes/oauth.consent.tsx (selection state)
// The user decides scope by scope. openid is the sign-in itself, so it
// stays on; everything else starts granted and can be switched off.
const [selected, setSelected] = useState<Set<string>>(
  () => new Set(grant.scopes),
)

const toggle = (scope: string) => {
  if (scope === 'openid') return
  setSelected((prev) => {
    const next = new Set(prev)
    if (next.has(scope)) next.delete(scope)
    else next.add(scope)
    return next
  })
}
```

```tsx
// provider/src/routes/oauth.consent.tsx (approve with the selection)
const approveGrant = createServerFn({ method: 'POST' })
  .validator((d: { grantId: string; scopes: string[] }) => d)
  .handler(async ({ data }) => {
    const session = await taskflowSession()
    const location = await approve(data.grantId, session.data.token!, data.scopes)
    throw redirect({ href: location })
  })
```

The card's list renders one row per scope in `grant.scopes`, calling `toggle` on click, and the **Authorize** button submits `Array.from(selected)`. The full component is in the [tutorial repository](https://github.com/appwrite-community/oauth-guide-custom-scopes).

### What the user sees

![TaskFlow consent screen listing each requested permission as a toggle](/images/docs/oauth-server/scopes-guide/taskflow-consent-scopes.avif)

When a user signs in to Vantage now, the consent card lists **See your tasks** and **Create and update your tasks** alongside the identity permissions, each one granted or withheld with a click. Users who approved Vantage before this change are asked again, because their earlier grant does not cover the new scopes.

Continue to validate access tokens on TaskFlow's server.

---

## Validate access tokens

URL: https://appwrite.io/docs/products/auth/oauth-server/custom-scopes/step-4

TaskFlow's API is about to accept access tokens from the outside world, so it first needs a way to tell a token it issued from one somebody made up. Access tokens from your OAuth2 server are RS256-signed JWTs, and the matching public keys are published at your project's JWKS endpoint. That means TaskFlow can verify tokens locally, with no call back to the OAuth2 server on each request.

### Install jose

[jose](https://github.com/panva/jose) handles the JWT verification and the JWKS fetching. Install it in the provider:

```sh
cd provider
pnpm add jose
```

### The token guard

Create `provider/src/lib/resource-server.ts`:

```ts
// provider/src/lib/resource-server.ts
// TaskFlow's resource server: validates access tokens issued by the
// project's OAuth2 server and enforces the scopes they carry.
import { createRemoteJWKSet, jwtVerify, type JWTPayload } from 'jose'

const issuer = process.env.OAUTH_ISSUER!

// The OAuth2 server publishes its signing keys as a JWK Set. jose caches
// the keys and refetches them when it sees an unknown key ID.
const jwks = createRemoteJWKSet(new URL(`${issuer}/.well-known/jwks.json`))

export type AccessToken = JWTPayload & {
  scope?: string
  client_id: string
}

/** Verify a Bearer token's signature against the JWKS and return its
 *  claims. Throws if the token is missing, expired, not ours, or not an
 *  access token. Requiring the at+jwt type (RFC 9068) is what stops an
 *  ID token, which is signed with the same key, from being accepted here. */
export async function verifyAccessToken(
  authorization: string | null,
): Promise<AccessToken> {
  const token = authorization?.match(/^Bearer (.+)$/)?.[1]
  if (!token) throw new Error('missing_token')
  const { payload } = await jwtVerify(token, jwks, { typ: 'at+jwt' })
  return payload as AccessToken
}

/** Check that the token was granted a scope. Scopes are a
 *  space-separated string in the token's scope claim. */
export function hasScope(token: AccessToken, scope: string): boolean {
  return (token.scope ?? '').split(' ').includes(scope)
}
```

`jwtVerify` checks the signature against the published keys and rejects expired tokens, so a forged token never reaches your handlers. It also pins the algorithm to the RS256 keys in the JWKS, so `alg: none` and HMAC confusion attacks fail here too.

Two limits of this guard are worth knowing.

Offline verification reads the token and nothing else, so it cannot see that a token was revoked or that its refresh rotated. A revoked token keeps passing this check until it expires, which for a confidential client is up to eight hours. Where that window is too long, call [introspection](/docs/products/auth/oauth-server/tokens#introspect) instead, which checks the token against its stored family.

The guard also accepts any access token this project issued, because it checks the signature and type but not `aud`. That is enough here, since TaskFlow's OAuth2 server is the only issuer whose keys the JWKS publishes. An API that should only accept tokens minted for itself asks the client to send a `resource` indicator on the authorization request, then checks that value in `aud`.

The `{ typ: 'at+jwt' }` option matters more than it looks. Your OAuth2 server signs [ID tokens](/docs/products/auth/oauth-server/tokens) with the same key as access tokens, and an ID token has no `scope` claim. Without the type check, a client could present its ID token to your API; requiring `at+jwt` (the [access token type](/docs/products/auth/oauth-server/tokens)) rejects it before it reaches a handler. An ID token is meant for the client that received it, never as a credential for your API.

Two claims in the verified payload matter for the API:

- `scope` holds the granted scopes as a single space-separated string, such as `openid profile email tasks.read`. `hasScope` splits it and looks for an exact entry.
- `sub` is the ID of the user who approved the grant. The API uses it to load that user's tasks, so a token can never read anyone else's data.

**Build the JWKS URL from your own configuration**

The guard builds the JWKS URL from the `OAUTH_ISSUER` value the provider already has, the same base it uses for every other OAuth2 server call. Since [tokens](/docs/products/auth/oauth-server/tokens) are standard JWTs, any JWT library with JWKS support in any language can do this job. The API route just happens to live next to the consent screen here.

Continue to put the guard in front of an API route.

---

## Protect the API route

URL: https://appwrite.io/docs/products/auth/oauth-server/custom-scopes/step-5

With the guard in place, TaskFlow can expose its task API. This is where the scope stops being a label and becomes a rule.

### The task data

Create `provider/src/lib/tasks.ts` with an in-memory store, keyed by user ID. It stands in for your product's database so the tutorial stays focused on the OAuth side:

```ts
// provider/src/lib/tasks.ts
// TaskFlow's task data, keyed by the owner's user ID. A stand-in for
// your product's database so the guide stays focused on OAuth.

export type Task = {
  id: string
  title: string
  project: string
  due: string
  done: boolean
}

const seed: Task[] = [
  { id: 'tsk_01', title: 'Finalize Q3 launch checklist', project: 'Launch', due: '2026-07-17', done: false },
  { id: 'tsk_02', title: 'Review onboarding copy', project: 'Growth', due: '2026-07-15', done: true },
  { id: 'tsk_03', title: 'Ship dark mode to beta', project: 'Product', due: '2026-07-21', done: false },
  { id: 'tsk_04', title: 'Prepare investor update', project: 'Ops', due: '2026-07-24', done: false },
]

const store = new Map<string, Task[]>()

/** Every TaskFlow user gets the same starter tasks the first time
 *  their list is read. */
export function tasksFor(userId: string): Task[] {
  let tasks = store.get(userId)
  if (!tasks) {
    tasks = seed.map((t) => ({ ...t }))
    store.set(userId, tasks)
  }
  return tasks
}

export function addTaskFor(userId: string, title: string): Task {
  const tasks = tasksFor(userId)
  const task: Task = {
    id: `tsk_${String(tasks.length + 1).padStart(2, '0')}`,
    title,
    project: 'Inbox',
    due: '2026-07-31',
    done: false,
  }
  tasks.push(task)
  return task
}
```

### The guarded route

Create `provider/src/routes/api.tasks.ts`. TanStack Start serves the `GET` and `POST` handlers at `/api/tasks`:

```ts
// provider/src/routes/api.tasks.ts
import { createFileRoute } from '@tanstack/react-router'
import {
  hasScope,
  verifyAccessToken,
  type AccessToken,
} from '../lib/resource-server'
import { addTaskFor, tasksFor } from '../lib/tasks'

// Standard OAuth resource server errors (RFC 6750): 401 when the token
// itself is bad, 403 when it's valid but missing the required scope.
function unauthorized() {
  return Response.json(
    { error: 'invalid_token' },
    { status: 401, headers: { 'WWW-Authenticate': 'Bearer error="invalid_token"' } },
  )
}

function forbidden(scope: string) {
  return Response.json(
    { error: 'insufficient_scope', required_scope: scope },
    {
      status: 403,
      headers: {
        'WWW-Authenticate': `Bearer error="insufficient_scope", scope="${scope}"`,
      },
    },
  )
}

async function authenticate(request: Request): Promise<AccessToken | null> {
  try {
    return await verifyAccessToken(request.headers.get('authorization'))
  } catch {
    return null
  }
}

export const Route = createFileRoute('/api/tasks')({
  server: {
    handlers: {
      GET: async ({ request }) => {
        const token = await authenticate(request)
        if (!token) return unauthorized()
        if (!hasScope(token, 'tasks.read')) return forbidden('tasks.read')

        // The token's subject is the TaskFlow user who approved access.
        return Response.json({ tasks: tasksFor(token.sub!) })
      },

      POST: async ({ request }) => {
        const token = await authenticate(request)
        if (!token) return unauthorized()
        if (!hasScope(token, 'tasks.write')) return forbidden('tasks.write')

        const { title } = await request.json()
        return Response.json(
          { task: addTaskFor(token.sub!, title) },
          { status: 201 },
        )
      },
    },
  },
})
```

Each handler applies the same two checks, in order:

1. **Authentication**: is the token real? A missing, forged, or expired token gets `401 invalid_token`.
2. **Authorization**: was this token granted the scope this operation needs? A valid token without it gets `403 insufficient_scope`.

The two error shapes follow RFC 6750, including the `WWW-Authenticate` header, so standard OAuth client libraries understand the refusal. The distinction matters to callers: a 401 means get a new token, a 403 means ask the user for more scopes.

Note what the `POST` handler implies: a client can hold a perfectly valid token and still be refused a write with it, whenever the user granted `tasks.read` but withheld `tasks.write` on the consent screen.

Continue to call the API from Vantage.

---

## Call the API from Vantage

URL: https://appwrite.io/docs/products/auth/oauth-server/custom-scopes/step-6

Vantage already holds the access token in its session after the [token exchange](/docs/products/auth/oauth-server/sign-in-with-your-product/step-6). Reading tasks is one authenticated fetch away, and a small composer will exercise the write path.

### Point Vantage at the API

Add TaskFlow's API base to `consumer/.env`:

```sh
### consumer/.env
### TaskFlow's API, called with the granted access token.
TASKFLOW_API_URL=http://localhost:4000
```

### The API client

Create `consumer/src/lib/taskflow.ts`. Every request carries the access token as a Bearer header, and TaskFlow's guard does the rest:

```ts
// consumer/src/lib/taskflow.ts
// Vantage's client for TaskFlow's API. Every request carries the access
// token the user granted, and TaskFlow enforces its scopes.

const taskflowApi = process.env.TASKFLOW_API_URL!

export type TaskFlowTask = {
  id: string
  title: string
  project: string
  due: string
  done: boolean
}

/** Read the user's tasks. Requires the tasks.read scope. */
export async function fetchTasks(accessToken: string): Promise<TaskFlowTask[]> {
  const res = await fetch(`${taskflowApi}/api/tasks`, {
    headers: { Authorization: `Bearer ${accessToken}` },
  })
  if (!res.ok) throw new Error(`tasks failed: ${res.status}`)
  const { tasks } = await res.json()
  return tasks
}

export type WriteResult =
  | { created: true; task: TaskFlowTask }
  | { created: false; status: number; error: string }

/** Create a task on TaskFlow. Succeeds only when the access token
 *  carries the tasks.write scope; otherwise TaskFlow answers with
 *  403 insufficient_scope. */
export async function createTask(
  accessToken: string,
  title: string,
): Promise<WriteResult> {
  const res = await fetch(`${taskflowApi}/api/tasks`, {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${accessToken}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ title }),
  })
  if (res.ok) {
    const { task } = await res.json()
    return { created: true, task }
  }
  const body = await res.json().catch(() => ({ error: 'unknown' }))
  return { created: false, status: res.status, error: body.error }
}
```

`createTask` does not check any scope itself. Vantage cannot know what the user granted until it tries; the refusal comes from TaskFlow, which is the only side that can be trusted to enforce it.

### Keep the granted scope

The token response reports which scopes were granted, which matters now that the user picks them individually on the consent screen. Store it in the session in `consumer/src/routes/oauth.callback.tsx`, next to the access token:

```ts
// consumer/src/routes/oauth.callback.tsx
await session.update({
  accessToken: tokens.access_token,
  grantedScope: tokens.scope,
  user,
  state: undefined,
})
```

Add the matching `grantedScope?: string` field to `SessionData` in `consumer/src/lib/oauth.ts`.

### Load tasks and add the composer

Replace the dashboard loader in `consumer/src/routes/dashboard.tsx` and add a server function for the write:

```tsx
// consumer/src/routes/dashboard.tsx (loader and write)
const loadDashboard = createServerFn().handler(
  async (): Promise<DashboardData> => {
    const session = await vantageSession()
    const { user, accessToken, grantedScope } = session.data
    if (!user || !accessToken) throw redirect({ to: '/' })

    // Reads the user's tasks from TaskFlow's API with the granted
    // access token. Works because the user granted tasks.read.
    const tasks = await fetchTasks(accessToken)

    return { user, grantedScope: grantedScope ?? '', tasks }
  },
)

const addTask = createServerFn({ method: 'POST' })
  .validator((d: { title: string }) => d)
  .handler(async ({ data }): Promise<WriteResult> => {
    const session = await vantageSession()
    const { accessToken } = session.data
    if (!accessToken) throw redirect({ to: '/' })

    // TaskFlow only accepts this if the token carries tasks.write.
    return createTask(accessToken, data.title)
  })
```

The component renders the granted scopes as chips, the task list, and an **Add task** composer under it. On submit it calls `addTask`; when the result is `created` it refreshes the list with `router.invalidate()`, and when it is not, it shows TaskFlow's refusal next to the composer. The full component is in the [tutorial repository](https://github.com/appwrite-community/oauth-guide-custom-scopes).

### The payoff

![Vantage dashboard showing live TaskFlow tasks and the task composer](/images/docs/oauth-server/scopes-guide/vantage-tasks-dashboard.avif)

The dashboard shows the user's tasks, fetched live from TaskFlow's API with the granted token, with the composer ready below them. What happens when you use it depends entirely on what the user granted on the consent screen, which is exactly what the final step walks through.

Continue to run the whole flow.

---

## Run the flow

URL: https://appwrite.io/docs/products/auth/oauth-server/custom-scopes/step-7

Everything is wired up. Run the flow twice: once granting only read access, and once granting the write too. The same button on the dashboard behaves differently each time, and the only thing that changed is what the user agreed to.

### Start both apps

In two terminals:

```sh
### in consumer/
pnpm dev
```

```sh
### in provider/
pnpm dev
```

### Grant read, withhold write

![TaskFlow consent screen with the write permission switched off](/images/docs/oauth-server/scopes-guide/taskflow-consent-choice.avif)

Open `http://localhost:4100` and click **Sign in with TaskFlow**. On the consent screen, switch **Create and update your tasks** off and authorize. The OAuth2 server narrows the grant to what was actually approved, so the access token comes back carrying `tasks.read` but not `tasks.write`.

![Vantage dashboard with the write attempt refused as insufficient_scope](/images/docs/oauth-server/scopes-guide/vantage-write-denied.avif)

On the dashboard, your tasks render live, so the read scope is doing its job. Now type a task and click **Add task**: TaskFlow refuses it with `403 insufficient_scope`. Vantage holds a perfectly valid token and still cannot write with it, because the user never agreed to that.

### Grant the write

Vantage has no sign-out route, so clear its `vantage_session` cookie or open a private window, then sign in again. This time leave every permission on and authorize. The previous grant did not cover `tasks.write`, and a request for a scope the user has not approved always returns to the consent screen.

![Vantage dashboard with tasks.write granted and the new task in the list](/images/docs/oauth-server/scopes-guide/vantage-write-granted.avif)

Add the same task again. It lands in the list, and the `tasks.write` chip sits in the granted scopes. Nothing about TaskFlow's API changed between the two runs; the same guard that refused the write now lets it through, because the token finally carries the scope it checks for.

### What each piece did

- **The OAuth2 server** published the scopes, put them in front of the user as individual choices, and stamped the approved subset onto a signed access token.
- **TaskFlow's API** verified each token against the project's JWKS and turned the `scope` claim into an allow-or-deny decision per operation.
- **Vantage** requested the scopes it can use, and its access ends exactly where the user's grant does.

The full source for both apps is on GitHub at [appwrite-community/oauth-guide-custom-scopes](https://github.com/appwrite-community/oauth-guide-custom-scopes).

### Next steps

- [Tokens](/docs/products/auth/oauth-server/tokens): Refresh access tokens, and revoke them on sign-out.
- [Scopes](/docs/products/auth/oauth-server/scopes): The scope model in full, including limits and the built-in scopes.
- [Device flow](/docs/products/auth/oauth-server/device-flow): Support TVs, CLIs, and other input-constrained devices.

---

## Device flow

URL: https://appwrite.io/docs/products/auth/oauth-server/device-flow

The device authorization grant (RFC 8628) lets a client request access even when it cannot open a browser or accept a callback. A TV app, command-line tool, or hardware device shows the user a code, and the user completes authorization on a phone or computer.

This flow involves two applications:

- The **device client** is the third-party application requesting access. It communicates with Appwrite over HTTP.
- The **verification page** belongs to your project. You build this page with an Appwrite Client SDK so the user can sign in, review the request, and approve or reject it.

### How it works

![The device flow between the device, the OAuth2 server, and a second device like the user's phone or laptop](/images/docs/oauth-server/diagram-device-flow.avif)

1. The device client starts a device authorization. Appwrite returns a device code, a shorter user code, and the URL of your verification page.
2. The device shows the user code and verification URL to the user.
3. The user opens the verification page on a second device, confirms the code, signs in, and reviews the request.
4. The verification page connects the pending request to the signed-in user and asks them to approve or reject it.
5. While this happens, the original device polls the token endpoint. After approval, the next successful poll returns tokens directly to the device.

### Configure device flow

Device flow must be enabled in both the OAuth2 server settings and the client settings.

#### Configure the OAuth2 server

Use `project.updateOAuth2Server` from a Server SDK to configure the verification page and code behavior:

| Setting | Purpose |
| --- | --- |
| `verificationUrl` | The URL of the verification page you host, such as `https://your-product.com/activate`. This setting is required for device flow. |
| `userCodeLength` | The number of characters in the user code. It can be from 6 to 12 and defaults to 8. |
| `userCodeFormat` | The characters used in the user code: `numeric`, `alphabetic`, or `alphanumeric`. The default is `alphanumeric`. |
| `deviceCodeDuration` | How long the device code and user code remain valid, in seconds. It can be from 60 to 1,800 and defaults to 600. |

**The call replaces the whole configuration**

`updateOAuth2Server` sets the full OAuth2 server configuration in one call. Include your existing authorization URL, scopes, and other settings when you add the device flow settings.

#### Enable device flow for the client

The client must also have **Device flow** enabled. Turn it on when you create or update the client in the Console, or set `deviceFlow: true` with any SDK that exposes the `apps` service. Devices that cannot protect a client secret should be registered as public clients. See [Clients](/docs/products/auth/oauth-server/clients#register) for client registration.

### Integrate the device client

The device client uses HTTP for the device authorization and token requests.

#### 1. Start a device authorization

Send the client ID and requested scopes to the device authorization endpoint as JSON:

```curl
curl -X POST 'https://<REGION>.cloud.appwrite.io/v1/oauth2/<PROJECT_ID>/device_authorization' \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json' \
  -d '{
    "client_id": "<CLIENT_ID>",
    "scope": "openid profile tasks.read"
  }'
```
```hurl
POST https://<REGION>.cloud.appwrite.io/v1/oauth2/<PROJECT_ID>/device_authorization
Content-Type: application/json
Accept: application/json
{
    "client_id": "<CLIENT_ID>",
    "scope": "openid profile tasks.read"
}
```

The request can also include:

- `authorization_details` for a JSON-encoded rich authorization request.
- `resource` for one resource indicator URI or an array of URIs.
- `audience` as a compatibility alias for one resource indicator.

See [Scopes](/docs/products/auth/oauth-server/scopes#rich-authorization-requests) for authorization details and resource restrictions.

Appwrite returns the codes, expiration, and polling interval:

```json
{
    "device_code": "8575375669b9031cb5371b0ee39985c2...",
    "user_code": "3RG9K9QF",
    "verification_uri": "https://your-product.com/activate",
    "verification_uri_complete": "https://your-product.com/activate?user_code=3RG9K9QF",
    "expires_in": 600,
    "interval": 1
}
```

Show the `user_code` and `verification_uri` on the device. You can also present `verification_uri_complete` as a link or QR code, which opens the verification page with the code already filled in.

#### 2. Poll for tokens

Start polling the token endpoint in the background. Wait at least the number of seconds in `interval` between requests:

```curl
curl -X POST 'https://<REGION>.cloud.appwrite.io/v1/oauth2/<PROJECT_ID>/token' \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json' \
  -d '{
    "grant_type": "urn:ietf:params:oauth:grant-type:device_code",
    "device_code": "<DEVICE_CODE>",
    "client_id": "<CLIENT_ID>"
  }'
```
```hurl
POST https://<REGION>.cloud.appwrite.io/v1/oauth2/<PROJECT_ID>/token
Content-Type: application/json
Accept: application/json
{
    "grant_type": "urn:ietf:params:oauth:grant-type:device_code",
    "device_code": "<DEVICE_CODE>",
    "client_id": "<CLIENT_ID>"
}
```

Handle each response according to its `error` value:

- `authorization_pending`: The user has not finished authorization. Wait for `interval`, then poll again.
- `slow_down`: The client is polling too quickly. Increase the delay before the next request.
- `expired_token`: The device code expired. Start a new device authorization and show the new user code.
- `access_denied`: The user rejected the request. Stop polling and let them restart if they want to try again.

### Build the verification page

The `verificationUrl` page runs on the user's second device and completes the interactive part of the flow.

1. Read `user_code` from the URL search parameters. If it is missing, show an input where the user can enter the code displayed on the original device.
2. Show the code and ask the user to confirm that it matches the original device.
3. Make sure the user is signed in to your project. If they are signed out, send them through your sign-in or sign-up flow and return them to the verification page with the user code intact.
4. Pass the confirmed code to `oauth2.createGrant`. This connects the pending device request to the signed-in user and returns the grant record.

```client-web
import { Client, Oauth2 } from 'appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

const oauth2 = new Oauth2(client);

const result = await oauth2.createGrant({
    userCode: '<USER_CODE>'
});

console.log(result);
```
```client-flutter
import 'package:appwrite/appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

Oauth2 oauth2 = Oauth2(client);

Oauth2Grant result = await oauth2.createGrant(
    userCode: '<USER_CODE>',
);
```
```client-apple
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>") // Your project ID

let oauth2 = Oauth2(client)

let oauth2Grant = try await oauth2.createGrant(
    userCode: "<USER_CODE>"
)
```
```client-android-kotlin
import io.appwrite.Client
import io.appwrite.services.Oauth2

val client = Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>") // Your project ID

val oauth2 = Oauth2(client)

val result = oauth2.createGrant(
    userCode = "<USER_CODE>",
)
```
```client-android-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Oauth2;

Client client = new Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>"); // Your project ID

Oauth2 oauth2 = new Oauth2(client);

oauth2.createGrant(
    "<USER_CODE>", // user_code
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        Log.d("Appwrite", result.toString());
    })
);
```
```client-react-native
import { Client, Oauth2 } from 'react-native-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

const oauth2 = new Oauth2(client);

const result = await oauth2.createGrant({
    userCode: '<USER_CODE>'
});

console.log(result);
```

Use the returned grant to show the client, requested scopes, and authorization details. Then let the user approve or reject it with the same [consent flow](/docs/products/auth/oauth-server/authorization#consent) used for browser authorization.

### Finish on the original device

The original device keeps polling while the user completes the verification page. After approval, the token endpoint returns access and refresh tokens, plus an ID token when `openid` was granted. The device uses these tokens directly. Device flow has no authorization code or redirect callback on the original device.

```json
{
    "access_token": "<ACCESS_TOKEN>",
    "refresh_token": "<REFRESH_TOKEN>",
    "id_token": "<ID_TOKEN>",
    "token_type": "Bearer",
    "expires_in": 3600,
    "scope": "openid profile tasks.read",
    "authorization_details": null
}
```

Store the newest refresh token securely and use the token response's granted scopes to determine which features are available. See [Tokens](/docs/products/auth/oauth-server/tokens) for validation, refresh, and revocation.

---

## Installations

URL: https://appwrite.io/docs/products/auth/oauth-server/installations

A [client](/docs/products/auth/oauth-server/clients) is a third-party app registered on your OAuth2 server. Your server can grant a client access in two ways. The first is **user consent**: the [authorization flow](/docs/products/auth/oauth-server/authorization) sends a user to your consent screen, the user approves, and the client receives tokens that act as that user. The grant ends when the user revokes it or leaves.

An **installation** is the second way. It connects a client to one team in your project. A team owner installs the client once. After that, the client's backend creates its own access tokens for that team, signed by your project's keys, with no user in the flow. The grant belongs to the team, so it survives when no one is signed in. Use it for sync jobs, bots, and provisioning backends.

The two models differ in who grants access and how long it lives:

| | User consent | Installation |
| --- | --- | --- |
| Who grants it | One user, on your consent screen | A team owner, once |
| The token acts as | The user who signed in | The client itself |
| Token renewal | Refresh token rotation | Create a new token with an app key |
| Revoked by | The user, per token or per grant | The owner, by removing the installation |

### Allow installation scopes

You decide which scopes clients may request at install time. Declare them in the `installationScopes` array on the `updateOAuth2Server` method, next to your other OAuth2 server settings. A project starts with an empty list. With an empty list, clients can still install, but every installation carries an empty grant.

The list accepts two kinds of values:

- **Appwrite catalog scopes.** The `project:` scopes act on your project's Appwrite APIs, the same set an [API key](/docs/partners/project/api-keys) reaches. (`organization:` scopes resolve only on the Appwrite Console, so they do nothing on your own project's OAuth2 server.) A token with `project:databases.read` can list your project's databases, and a token with `project:teams.read` can read the team it is installed on. A call outside the granted scopes fails with `general_unauthorized_scope`.
- **Your own scope values.** Any vocabulary you define, such as `tasks.sync`. Appwrite stamps these onto the token's `scope` claim, and your API enforces them, the same way it enforces [custom scopes](/docs/products/auth/oauth-server/scopes#custom) in the user consent flow.

Identity scopes such as `openid` have no place in the list, because no user takes part in an installation. The update replaces the whole list, so send every value on each `updateOAuth2Server` call; omitting the parameter clears it.

### Request installation scopes

A client declares the scopes it requests at install time in its `installationScopes` setting.

- Set the scopes with the `update` method. The `create` method does not accept installation fields, so register the client first, then update it.
- A client can request only values from the project's allowed list. Other values fail validation, and the error message lists the accepted scopes.
- The `listInstallationScopes` method returns the allowed list, so a client can discover it.
- The optional `installationRedirectUrl` tells your product where to send the owner after an install or update. Appwrite stores the URL; your install flow performs the redirect.

```client-web
import { Client, Apps } from 'appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

const apps = new Apps(client);

const app = await apps.update({
    appId: '<APP_ID>',
    name: 'Vantage',
    redirectUris: ['https://vantage.localhost/auth/redirect'],
    type: 'confidential',
    installationScopes: ['project:databases.read', 'project:teams.read'],
    installationRedirectUrl: 'https://vantage.localhost/setup',
});
```
```client-flutter
import 'package:appwrite/appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

Apps apps = Apps(client);

App app = await apps.update(
    appId: '<APP_ID>',
    name: 'Vantage',
    redirectUris: ['https://vantage.localhost/auth/redirect'],
    type: 'confidential',
    installationScopes: ['project:databases.read', 'project:teams.read'],
    installationRedirectUrl: 'https://vantage.localhost/setup',
);
```
```client-apple
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>") // Your project ID

let apps = Apps(client)

let app = try await apps.update(
    appId: "<APP_ID>",
    name: "Vantage",
    redirectUris: ["https://vantage.localhost/auth/redirect"],
    type: "confidential",
    installationScopes: ["project:databases.read", "project:teams.read"],
    installationRedirectUrl: "https://vantage.localhost/setup"
)
```
```client-android-kotlin
import io.appwrite.Client
import io.appwrite.services.Apps

val client = Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>") // Your project ID

val apps = Apps(client)

val app = apps.update(
    appId = "<APP_ID>",
    name = "Vantage",
    redirectUris = listOf("https://vantage.localhost/auth/redirect"),
    type = "confidential",
    installationScopes = listOf("project:databases.read", "project:teams.read"),
    installationRedirectUrl = "https://vantage.localhost/setup"
)
```
```client-android-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Apps;

Client client = new Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>"); // Your project ID

Apps apps = new Apps(client);

apps.update(
    "<APP_ID>", // appId
    "Vantage", // name
    null, // description (optional)
    null, // clientUri (optional)
    null, // logoUri (optional)
    null, // privacyPolicyUrl (optional)
    null, // termsUrl (optional)
    null, // contacts (optional)
    null, // tagline (optional)
    null, // tags (optional)
    null, // images (optional)
    null, // supportUrl (optional)
    null, // dataDeletionUrl (optional)
    null, // enabled (optional)
    List.of("https://vantage.localhost/auth/redirect"), // redirectUris
    null, // postLogoutRedirectUris (optional)
    "confidential", // type (optional)
    null, // deviceFlow (optional)
    List.of("project:databases.read", "project:teams.read"), // installationScopes
    "https://vantage.localhost/setup", // installationRedirectUrl
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        Log.d("Appwrite", result.toString());
    })
);
```
```client-react-native
import { Client, Apps } from "react-native-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

const apps = new Apps(client);

const app = await apps.update({
    appId: '<APP_ID>',
    name: 'Vantage',
    redirectUris: ['https://vantage.localhost/auth/redirect'],
    type: 'confidential',
    installationScopes: ['project:databases.read', 'project:teams.read'],
    installationRedirectUrl: 'https://vantage.localhost/setup',
});
```

Request the smallest set that serves the client. The owner sees every scope at install time, and a long list costs installs.

**Scopes are a snapshot**

Each installation copies the client's scopes at the moment the owner creates or updates it. When the client's scopes change later, existing installations keep their old grant until their owner updates them.

### Install on a team

Only team members with the owner role can install a client on their team, though a project API key or an admin-mode call can install on any team. Each client installs once per team; a second attempt fails with `app_installation_already_exists`. The owner installs with the `createInstallation` method, authenticated by their own session in your product:

```client-web
import { Client, Teams } from 'appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

const teams = new Teams(client);

const installation = await teams.createInstallation({
    teamId: '<TEAM_ID>',
    appId: '<APP_ID>',
    authorizationDetails: JSON.stringify([
        { type: 'workspace', identifiers: ['<WORKSPACE_ID>'] },
    ]),
});
```
```client-flutter
import 'package:appwrite/appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

Teams teams = Teams(client);

AppInstallation installation = await teams.createInstallation(
    teamId: '<TEAM_ID>',
    appId: '<APP_ID>',
);
```
```client-apple
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>") // Your project ID

let teams = Teams(client)

let installation = try await teams.createInstallation(
    teamId: "<TEAM_ID>",
    appId: "<APP_ID>"
)
```
```client-android-kotlin
import io.appwrite.Client
import io.appwrite.services.Teams

val client = Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>") // Your project ID

val teams = Teams(client)

val installation = teams.createInstallation(
    teamId = "<TEAM_ID>",
    appId = "<APP_ID>"
)
```
```client-android-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Teams;

Client client = new Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>"); // Your project ID

Teams teams = new Teams(client);

teams.createInstallation(
    "<TEAM_ID>", // teamId
    "<APP_ID>", // appId
    null, // authorizationDetails (optional)
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        Log.d("Appwrite", result.toString());
    })
);
```
```client-react-native
import { Client, Teams } from "react-native-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

const teams = new Teams(client);

const installation = await teams.createInstallation({
    teamId: '<TEAM_ID>',
    appId: '<APP_ID>',
});
```

The response is the installation record, with the scope snapshot in its `scopes` field:

```json
{
  "$id": "6a731f142c5de7834fec",
  "$createdAt": "2026-08-05T11:30:00.000+00:00",
  "$updatedAt": "2026-08-05T11:30:00.000+00:00",
  "appId": "<APP_ID>",
  "teamId": "<TEAM_ID>",
  "scopes": ["project:databases.read", "project:teams.read"],
  "authorizationDetails": [{ "type": "workspace", "identifiers": ["<WORKSPACE_ID>"] }],
  "createdById": "6a150ace003bc4c2919e",
  "createdByName": "Walter O'Brien",
  "lastAccessedAt": null
}
```

The optional `authorizationDetails` parameter narrows the grant with entries in the [rich authorization request](/docs/products/auth/oauth-server/scopes#rich-authorization-requests) shape. Each entry is an object with a `type` you define and any fields your client understands. Pass it as a JSON string; the response returns it parsed. Omit it for a grant with no extra narrowing. When the client has an `installationRedirectUrl`, send the owner there after the install so the client can finish its setup.

### App keys

The client's backend authenticates its installation calls with an app key. App keys are separate from [client secrets](/docs/products/auth/oauth-server/clients#secrets): a client secret proves the client's identity at the token endpoint during user sign-in, while an app key mints installation tokens. App keys carry no scopes; each installation decides what the key's tokens can do.

The client's developer creates a key with the `createKey` method:

```client-web
import { Client, Apps } from 'appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

const apps = new Apps(client);

const key = await apps.createKey({
    appId: '<APP_ID>',
});
```
```client-flutter
import 'package:appwrite/appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

Apps apps = Apps(client);

AppKey key = await apps.createKey(
    appId: '<APP_ID>',
);
```
```client-apple
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>") // Your project ID

let apps = Apps(client)

let key = try await apps.createKey(
    appId: "<APP_ID>"
)
```
```client-android-kotlin
import io.appwrite.Client
import io.appwrite.services.Apps

val client = Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>") // Your project ID

val apps = Apps(client)

val key = apps.createKey(
    appId = "<APP_ID>"
)
```
```client-android-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Apps;

Client client = new Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>"); // Your project ID

Apps apps = new Apps(client);

apps.createKey(
    "<APP_ID>", // appId
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        Log.d("Appwrite", result.toString());
    })
);
```
```client-react-native
import { Client, Apps } from "react-native-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

const apps = new Apps(client);

const key = await apps.createKey({
    appId: '<APP_ID>',
});
```

The response holds the secret in its `secret` field. App keys are encrypted at rest and readable on every read, so `apps.getKey` and `apps.listKeys` return the secret again whenever you need it. The `hint` field repeats the secret's last six characters, so you can tell keys apart later. Treat the secret like a password: anyone who holds it can mint tokens for every installation of the client.

### Create installation tokens

The client's backend holds no user session in your project, so its installation calls go over plain HTTP with the app key. This is the same pattern [dynamic client registration](/docs/products/auth/oauth-server/clients#dynamic-registration) uses. Send the key in the `X-Appwrite-Key` header together with the client ID in the `X-Appwrite-App` header.

Exchange the app key for an access token bound to one installation:

```curl
curl -X POST https://<REGION>.cloud.appwrite.io/v1/apps/<APP_ID>/installations/<INSTALLATION_ID>/tokens \
  -H "X-Appwrite-Project: <PROJECT_ID>" \
  -H "X-Appwrite-App: <APP_ID>" \
  -H "X-Appwrite-Key: <APP_KEY_SECRET>"
```

```hurl
POST https://<REGION>.cloud.appwrite.io/v1/apps/<APP_ID>/installations/<INSTALLATION_ID>/tokens
X-Appwrite-Project: <PROJECT_ID>
X-Appwrite-App: <APP_ID>
X-Appwrite-Key: <APP_KEY_SECRET>
```

The response is a standard OAuth2 token response. The `refresh_token` field comes back empty and `id_token` comes back `null`; only user grants carry those:

```json
{
  "access_token": "eyJ0eXAiOiJhdCtqd3QiLCJhbGciOiJSUzI1NiJ9...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "",
  "scope": "project:databases.read project:teams.read",
  "authorization_details": [
    { "type": "workspace", "identifiers": ["<WORKSPACE_ID>"] },
    { "type": "organization", "identifiers": ["<TEAM_ID>"] }
  ],
  "id_token": null
}
```

Three things to know about this token:

- **It acts as the installation, not as a user.** The token's `sub` claim is the installation ID, and its `client_id` is the client.
- **It is pinned to the installed team.** Appwrite writes the `organization` entry in `authorization_details` itself, next to any entries the installation stored. A token can never claim a different team than the one that installed the client.
- **There is no refresh token.** Tokens last 1 hour by default; your project's `installationAccessTokenDuration` setting controls the lifetime. When one expires, create another; the installation record is the durable grant. Several tokens can be active at once, so each worker of the client's backend can hold its own.

### List installations

The client's backend can list where it is installed, with the same headers:

```curl
curl https://<REGION>.cloud.appwrite.io/v1/apps/<APP_ID>/installations \
  -H "X-Appwrite-Project: <PROJECT_ID>" \
  -H "X-Appwrite-App: <APP_ID>" \
  -H "X-Appwrite-Key: <APP_KEY_SECRET>"
```

```hurl
GET https://<REGION>.cloud.appwrite.io/v1/apps/<APP_ID>/installations
X-Appwrite-Project: <PROJECT_ID>
X-Appwrite-App: <APP_ID>
X-Appwrite-Key: <APP_KEY_SECRET>
```

The response wraps the records in an `installations` array with a `total` count. Each record has the shape shown in [Install on a team](#install), and its `lastAccessedAt` timestamp reports when the client last minted a token for it. The write is throttled to at most once an hour, so the value can trail real usage by that much.

### Validate installation tokens

An installation token is a JWT of type `at+jwt`, signed by your project's keys, the same as every access token your server issues. Its `iss` claim is your project's issuer and its `aud` claim is your project's API. Verify it against your project's JWKS exactly as [Tokens](/docs/products/auth/oauth-server/tokens#validate) describes, then read `scope` and `authorization_details` to decide what it may do. The decoded payload:

```json
{
  "iss": "https://<REGION>.cloud.appwrite.io/v1/oauth2/<PROJECT_ID>",
  "aud": ["https://<REGION>.cloud.appwrite.io/v1/<PROJECT_ID>"],
  "sub": "<INSTALLATION_ID>",
  "client_id": "<APP_ID>",
  "scope": "project:databases.read project:teams.read",
  "authorization_details": [
    { "type": "workspace", "identifiers": ["<WORKSPACE_ID>"] },
    { "type": "organization", "identifiers": ["<TEAM_ID>"] }
  ],
  "exp": 1785933118,
  "iat": 1785929518,
  "jti": "711d16054c093d7b1841340dae47d957"
}
```

### Update and remove

The team owner stays in control after the install. Updating an installation refreshes its grant to the client's current scopes and revokes every active token for it. Removing it deletes the grant and every token together. Both emit `teams.[teamId].installations.[installationId].update` and `.delete` [events](/docs/apis/events), so integrations can react.

The owner updates an installation with the `updateInstallation` method. Pass `authorizationDetails` to change the stored entries, or omit it to keep them:

```client-web
import { Client, Teams } from 'appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

const teams = new Teams(client);

const installation = await teams.updateInstallation({
    teamId: '<TEAM_ID>',
    installationId: '<INSTALLATION_ID>',
});
```
```client-flutter
import 'package:appwrite/appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

Teams teams = Teams(client);

AppInstallation installation = await teams.updateInstallation(
    teamId: '<TEAM_ID>',
    installationId: '<INSTALLATION_ID>',
);
```
```client-apple
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>") // Your project ID

let teams = Teams(client)

let installation = try await teams.updateInstallation(
    teamId: "<TEAM_ID>",
    installationId: "<INSTALLATION_ID>"
)
```
```client-android-kotlin
import io.appwrite.Client
import io.appwrite.services.Teams

val client = Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>") // Your project ID

val teams = Teams(client)

val installation = teams.updateInstallation(
    teamId = "<TEAM_ID>",
    installationId = "<INSTALLATION_ID>"
)
```
```client-android-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Teams;

Client client = new Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>"); // Your project ID

Teams teams = new Teams(client);

teams.updateInstallation(
    "<TEAM_ID>", // teamId
    "<INSTALLATION_ID>", // installationId
    null, // authorizationDetails (optional)
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        Log.d("Appwrite", result.toString());
    })
);
```
```client-react-native
import { Client, Teams } from "react-native-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

const teams = new Teams(client);

const installation = await teams.updateInstallation({
    teamId: '<TEAM_ID>',
    installationId: '<INSTALLATION_ID>',
});
```

The owner removes an installation with the `deleteInstallation` method:

```client-web
import { Client, Teams } from 'appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

const teams = new Teams(client);

await teams.deleteInstallation({
    teamId: '<TEAM_ID>',
    installationId: '<INSTALLATION_ID>',
});
```
```client-flutter
import 'package:appwrite/appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

Teams teams = Teams(client);

await teams.deleteInstallation(
    teamId: '<TEAM_ID>',
    installationId: '<INSTALLATION_ID>',
);
```
```client-apple
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>") // Your project ID

let teams = Teams(client)

try await teams.deleteInstallation(
    teamId: "<TEAM_ID>",
    installationId: "<INSTALLATION_ID>"
)
```
```client-android-kotlin
import io.appwrite.Client
import io.appwrite.services.Teams

val client = Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>") // Your project ID

val teams = Teams(client)

teams.deleteInstallation(
    teamId = "<TEAM_ID>",
    installationId = "<INSTALLATION_ID>"
)
```
```client-android-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Teams;

Client client = new Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>"); // Your project ID

Teams teams = new Teams(client);

teams.deleteInstallation(
    "<TEAM_ID>", // teamId
    "<INSTALLATION_ID>", // installationId
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        Log.d("Appwrite", result.toString());
    })
);
```
```client-react-native
import { Client, Teams } from "react-native-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

const teams = new Teams(client);

await teams.deleteInstallation({
    teamId: '<TEAM_ID>',
    installationId: '<INSTALLATION_ID>',
});
```

Because updates and removals revoke tokens immediately, a client should treat a `401` on a previously working token as a lifecycle event. Create a fresh token; when that also fails with `app_installation_not_found`, the installation is gone, and the client should mark the team as disconnected.

---

## OAuth2 server quick start

URL: https://appwrite.io/docs/products/auth/oauth-server/quick-start

This guide turns your project into an OAuth2 provider and runs one sign-in through it. By the end you will have an enabled server, a registered client, and an access token issued by your project.

The examples follow two apps, the same pair the [tutorials](/docs/products/auth/oauth-server/sign-in-with-your-product/step-1) build out in full:

- **TaskFlow** (`https://taskflow.localhost`): your product and the **OAuth2 provider**, also called the authorization server. It authenticates users, presents the consent screen, and issues tokens.
- **Vantage** (`https://vantage.localhost`): the third-party **consumer**, called the client in OAuth2. It sends users to TaskFlow for authorization and receives tokens after they approve access.

### Enable the server

In the Console, open **Auth**, select the **OAuth2 server** tab, and turn on **Enable OAuth2 server**.

![Enabling the OAuth2 server in the Appwrite Console](/images/docs/oauth-server/oauth2-server-settings.avif)

Set the **Authorization URL** to the page you will host the consent screen on. This is where Appwrite redirects users during authorization, and where you present them the details of the request so they can approve or reject. Point it at `https://taskflow.localhost/consent`, where TaskFlow will host its consent screen. Nothing needs to run there yet. The `openid`, `profile`, `email`, and `phone` scopes are always included. Add any scopes your custom APIs will support.

You can also enable and configure the server with a [Server SDK](/docs/sdks#server) using the `updateOAuth2Server` method.

**Required scope**

The API key used for this call needs the `project.write` scope.

```server-nodejs
import { Client, Project } from '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 project = new Project(client);

const result = await project.updateOAuth2Server({
    enabled: true,
    authorizationUrl: 'https://taskflow.localhost/consent',
    scopes: ['tasks.read'] // optional
});
```
```server-deno
import { Client, Project } 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 project = new Project(client);

const result = await project.updateOAuth2Server({
    enabled: true,
    authorizationUrl: 'https://taskflow.localhost/consent',
    scopes: ['tasks.read'] // optional
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Project;

$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

$project = new Project($client);

$result = $project->updateOAuth2Server(
    enabled: true,
    authorizationUrl: 'https://taskflow.localhost/consent',
    scopes: ['tasks.read'] // optional
);
```
```server-python
from appwrite.client import Client
from appwrite.services.project import Project
from appwrite.models import Project as ProjectModel

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

project = Project(client)

result: ProjectModel = project.update_o_auth2_server(
    enabled = True,
    authorization_url = 'https://taskflow.localhost/consent',
    scopes = ['tasks.read'] # optional
)

print(result.model_dump())
```
```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

project = Project.new(client)

result = project.update_o_auth2_server(
    enabled: true,
    authorization_url: 'https://taskflow.localhost/consent',
    scopes: ['tasks.read'] # 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

Project project = Project(client);

Project result = await project.updateOAuth2Server(
    enabled: true,
    authorizationUrl: 'https://taskflow.localhost/consent',
    scopes: ['tasks.read'], // (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

Project project = new Project(client);

Project result = await project.UpdateOAuth2Server(
    enabled: true,
    authorizationUrl: "https://taskflow.localhost/consent",
    scopes: new List<string> { "tasks.read" } // optional
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.coroutines.CoroutineCallback
import io.appwrite.services.Project

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 project = Project(client)

val response = project.updateOAuth2Server(
    enabled = true,
    authorizationUrl = "https://taskflow.localhost/consent",
    scopes = listOf("tasks.read") // optional
)
```
```server-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Project;

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

Project project = new Project(client);

project.updateOAuth2Server(
    true, // enabled
    "https://taskflow.localhost/consent", // authorizationUrl
    List.of("tasks.read"), // scopes (optional)
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        System.out.println(result);
    })
);
```
```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 project = Project(client)

let result = try await project.updateOAuth2Server(
    enabled: true,
    authorizationUrl: "https://taskflow.localhost/consent",
    scopes: ["tasks.read"] // optional
)
```
```server-go
package main

import (
    "fmt"
    "log"

    "github.com/appwrite/sdk-for-go/v6/appwrite"
)

func main() {
    client := appwrite.NewClient(
        appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"),
        appwrite.WithProject("<YOUR_PROJECT_ID>"),
        appwrite.WithKey("<YOUR_API_KEY>"),
    )

    project := appwrite.NewProject(client)

    response, err := project.UpdateOAuth2Server(
        true,
        "https://taskflow.localhost/consent",
        project.WithUpdateOAuth2ServerScopes([]string{"tasks.read"}), // optional
    )
    if err != nil {
        log.Fatal(err)
    }

    fmt.Println(response)
}
```
```server-rust
use appwrite::Client;
use appwrite::services::Project;

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

    let project = Project::new(&client);

    let result = project.update_o_auth2_server(
        true,
        "https://taskflow.localhost/consent",
        Some(vec!["tasks.read".to_string()]), // optional
        None, // optional
        None, // optional
        None, // optional
        None, // optional
        None, // optional
        None, // optional
        None, // optional
        None, // optional
        None, // optional
        None, // optional
        None, // optional
        None, // optional
        None, // optional
    ).await?;

    let _ = result;

    Ok(())
}
```

### Copy the discovery URL

Once enabled, the server publishes an OpenID Connect discovery document. Integrators point their OAuth or OIDC library at this URL and it learns every endpoint automatically. If the integrating platform supports OIDC sign-in out of the box, this URL is all it needs: it can skip most of the steps below instead of building the flow from scratch.

![The OIDC discovery URL in the Appwrite Console](/images/docs/oauth-server/oauth2-server-discovery.avif)

Open it in a browser to confirm the server is live. It returns JSON describing the authorization, token, userinfo, and JWKS endpoints, and more.

### Register a client

Each app that integrates with your project registers as a client. Here that is Vantage. The **redirect URI** it declares is a URL on Vantage, `https://vantage.localhost/auth/redirect`, where the OAuth2 server sends users back with the authorization code. For security, redirect URIs must use HTTPS. There are two exceptions: loopback addresses like `localhost`, `127.0.0.1`, and `[::1]` can use HTTP during development, and native apps can register a private-use scheme URI such as `com.example.app:/oauth`. No redirect URI may contain a fragment.

Open the **Apps** sub-tab and create a client. Name it `Vantage`, add the redirect URI, and choose a type. Pick **Confidential** for this walkthrough so you get a secret to authenticate the token exchange. Copy the secret when it is shown, because it appears only once. For a mobile or single-page app that cannot hold a secret, pick **Public** instead: the token exchange then uses [PKCE](/docs/products/auth/oauth-server/authorization#pkce) in place of the secret, and the [client types](/docs/products/auth/oauth-server/clients#client-types) comparison shows what else changes.

![Creating an OAuth2 client](/images/docs/oauth-server/oauth2-server-create-app.avif)

Clients can also be registered from code with the `apps` service in the [Client SDKs](/docs/sdks#client). Any signed-in user on your project can register an app, which enables self-serve registration for integrators. Creating a client needs an app ID, a name, and a redirect URI:

```client-web
import { Client, Apps, ID } from 'appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

const apps = new Apps(client);

const app = await apps.create({
    appId: ID.unique(),
    name: 'Vantage',
    redirectUris: ['https://vantage.localhost/auth/redirect'],
    type: 'confidential',
});
```
```client-flutter
import 'package:appwrite/appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

Apps apps = Apps(client);

App app = await apps.create(
    appId: ID.unique(),
    name: 'Vantage',
    redirectUris: ['https://vantage.localhost/auth/redirect'],
    type: 'confidential',
);
```
```client-apple
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>") // Your project ID

let apps = Apps(client)

let app = try await apps.create(
    appId: ID.unique(),
    name: "Vantage",
    redirectUris: ["https://vantage.localhost/auth/redirect"],
    type: "confidential"
)
```
```client-android-kotlin
import io.appwrite.Client
import io.appwrite.ID
import io.appwrite.services.Apps

val client = Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>") // Your project ID

val apps = Apps(client)

val app = apps.create(
    appId = ID.unique(),
    name = "Vantage",
    redirectUris = listOf("https://vantage.localhost/auth/redirect"),
    type = "confidential"
)
```
```client-android-java
import io.appwrite.Client;
import io.appwrite.ID;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Apps;

Client client = new Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>"); // Your project ID

Apps apps = new Apps(client);

apps.create(
    ID.unique(), // appId
    "Vantage", // name
    List.of("https://vantage.localhost/auth/redirect"), // redirectUris
    null, // description (optional)
    null, // clientUri (optional)
    null, // logoUri (optional)
    null, // privacyPolicyUrl (optional)
    null, // termsUrl (optional)
    null, // contacts (optional)
    null, // tagline (optional)
    null, // tags (optional)
    null, // images (optional)
    null, // supportUrl (optional)
    null, // dataDeletionUrl (optional)
    null, // postLogoutRedirectUris (optional)
    null, // enabled (optional)
    "confidential", // type (optional)
    null, // deviceFlow (optional)
    null, // teamId (optional)
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        Log.d("Appwrite", result.toString());
    })
);
```
```client-react-native
import { Client, Apps, ID } from "react-native-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

const apps = new Apps(client);

const app = await apps.create({
    appId: ID.unique(),
    name: 'Vantage',
    redirectUris: ['https://vantage.localhost/auth/redirect'],
    type: 'confidential',
});
```

Updating a client accepts the full set of options, from consent screen branding to logout URIs and the device flow:

```client-web
import { Client, Apps } from 'appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

const apps = new Apps(client);

const app = await apps.update({
    appId: '<APP_ID>',
    name: 'Vantage',
    description: 'A dashboard that signs in with TaskFlow.',
    clientUri: 'https://vantage.localhost',
    logoUri: 'https://vantage.localhost/logo.png',
    privacyPolicyUrl: 'https://vantage.localhost/privacy',
    termsUrl: 'https://vantage.localhost/terms',
    contacts: ['security@vantage.localhost'],
    tagline: 'Product analytics for modern teams',
    tags: ['analytics', 'productivity'],
    images: ['https://vantage.localhost/screenshot.png'],
    supportUrl: 'https://vantage.localhost/support',
    dataDeletionUrl: 'https://vantage.localhost/data-deletion',
    enabled: true,
    redirectUris: ['https://vantage.localhost/auth/redirect'],
    postLogoutRedirectUris: ['https://vantage.localhost/signed-out'],
    type: 'confidential',
    deviceFlow: false,
});
```
```client-flutter
import 'package:appwrite/appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

Apps apps = Apps(client);

App app = await apps.update(
    appId: '<APP_ID>',
    name: 'Vantage',
    description: 'A dashboard that signs in with TaskFlow.',
    clientUri: 'https://vantage.localhost',
    logoUri: 'https://vantage.localhost/logo.png',
    privacyPolicyUrl: 'https://vantage.localhost/privacy',
    termsUrl: 'https://vantage.localhost/terms',
    contacts: ['security@vantage.localhost'],
    tagline: 'Product analytics for modern teams',
    tags: ['analytics', 'productivity'],
    images: ['https://vantage.localhost/screenshot.png'],
    supportUrl: 'https://vantage.localhost/support',
    dataDeletionUrl: 'https://vantage.localhost/data-deletion',
    enabled: true,
    redirectUris: ['https://vantage.localhost/auth/redirect'],
    postLogoutRedirectUris: ['https://vantage.localhost/signed-out'],
    type: 'confidential',
    deviceFlow: false,
);
```
```client-apple
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>") // Your project ID

let apps = Apps(client)

let app = try await apps.update(
    appId: "<APP_ID>",
    name: "Vantage",
    description: "A dashboard that signs in with TaskFlow.",
    clientUri: "https://vantage.localhost",
    logoUri: "https://vantage.localhost/logo.png",
    privacyPolicyUrl: "https://vantage.localhost/privacy",
    termsUrl: "https://vantage.localhost/terms",
    contacts: ["security@vantage.localhost"],
    tagline: "Product analytics for modern teams",
    tags: ["analytics", "productivity"],
    images: ["https://vantage.localhost/screenshot.png"],
    supportUrl: "https://vantage.localhost/support",
    dataDeletionUrl: "https://vantage.localhost/data-deletion",
    enabled: true,
    redirectUris: ["https://vantage.localhost/auth/redirect"],
    postLogoutRedirectUris: ["https://vantage.localhost/signed-out"],
    type: "confidential",
    deviceFlow: false
)
```
```client-android-kotlin
import io.appwrite.Client
import io.appwrite.services.Apps

val client = Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>") // Your project ID

val apps = Apps(client)

val app = apps.update(
    appId = "<APP_ID>",
    name = "Vantage",
    description = "A dashboard that signs in with TaskFlow.",
    clientUri = "https://vantage.localhost",
    logoUri = "https://vantage.localhost/logo.png",
    privacyPolicyUrl = "https://vantage.localhost/privacy",
    termsUrl = "https://vantage.localhost/terms",
    contacts = listOf("security@vantage.localhost"),
    tagline = "Product analytics for modern teams",
    tags = listOf("analytics", "productivity"),
    images = listOf("https://vantage.localhost/screenshot.png"),
    supportUrl = "https://vantage.localhost/support",
    dataDeletionUrl = "https://vantage.localhost/data-deletion",
    enabled = true,
    redirectUris = listOf("https://vantage.localhost/auth/redirect"),
    postLogoutRedirectUris = listOf("https://vantage.localhost/signed-out"),
    type = "confidential",
    deviceFlow = false,
)
```
```client-android-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Apps;

Client client = new Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>"); // Your project ID

Apps apps = new Apps(client);

apps.update(
    "<APP_ID>", // appId
    "Vantage", // name
    "A dashboard that signs in with TaskFlow.", // description
    "https://vantage.localhost", // clientUri
    "https://vantage.localhost/logo.png", // logoUri
    "https://vantage.localhost/privacy", // privacyPolicyUrl
    "https://vantage.localhost/terms", // termsUrl
    List.of("security@vantage.localhost"), // contacts
    "Product analytics for modern teams", // tagline
    List.of("analytics", "productivity"), // tags
    List.of("https://vantage.localhost/screenshot.png"), // images
    "https://vantage.localhost/support", // supportUrl
    "https://vantage.localhost/data-deletion", // dataDeletionUrl
    true, // enabled
    List.of("https://vantage.localhost/auth/redirect"), // redirectUris
    List.of("https://vantage.localhost/signed-out"), // postLogoutRedirectUris
    "confidential", // type
    false, // deviceFlow
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        Log.d("Appwrite", result.toString());
    })
);
```
```client-react-native
import { Client, Apps } from "react-native-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

const apps = new Apps(client);

const app = await apps.update({
    appId: '<APP_ID>',
    name: 'Vantage',
    description: 'A dashboard that signs in with TaskFlow.',
    clientUri: 'https://vantage.localhost',
    logoUri: 'https://vantage.localhost/logo.png',
    privacyPolicyUrl: 'https://vantage.localhost/privacy',
    termsUrl: 'https://vantage.localhost/terms',
    contacts: ['security@vantage.localhost'],
    tagline: 'Product analytics for modern teams',
    tags: ['analytics', 'productivity'],
    images: ['https://vantage.localhost/screenshot.png'],
    supportUrl: 'https://vantage.localhost/support',
    dataDeletionUrl: 'https://vantage.localhost/data-deletion',
    enabled: true,
    redirectUris: ['https://vantage.localhost/auth/redirect'],
    postLogoutRedirectUris: ['https://vantage.localhost/signed-out'],
    type: 'confidential',
    deviceFlow: false,
});
```

The same `apps` service is available in the [Server SDKs](/docs/sdks#server) with an API key. See [Clients](/docs/products/auth/oauth-server/clients) for the full set of options.

### Run the authorization code flow

With the server enabled and a client registered, you can run a sign-in. The flow has four steps: send the user to authorize, approve the grant, exchange the returned code for tokens, and use the access token to read their profile.

#### 1. Send the user to the authorization endpoint

Vantage begins the sign-in by sending the user's browser to TaskFlow's authorization endpoint. This is the URL behind Vantage's **Sign in with TaskFlow** button.

The URL uses TaskFlow's Appwrite API endpoint because its Appwrite project is acting as the authorization server. Replace `<REGION>` with the region from your API endpoint, `<PROJECT_ID>` with TaskFlow's Appwrite project ID, and `<CLIENT_ID>` with the ID generated when you registered Vantage:

```text
https://<REGION>.cloud.appwrite.io/v1/oauth2/<PROJECT_ID>/authorize
    ?client_id=<CLIENT_ID>
    &redirect_uri=https://vantage.localhost/auth/redirect
    &response_type=code
    &scope=openid profile email
```
```curl
curl -G 'https://<REGION>.cloud.appwrite.io/v1/oauth2/<PROJECT_ID>/authorize' \
  --data-urlencode 'client_id=<CLIENT_ID>' \
  --data-urlencode 'redirect_uri=https://vantage.localhost/auth/redirect' \
  --data-urlencode 'response_type=code' \
  --data-urlencode 'scope=openid profile email'
```
```hurl
GET https://<REGION>.cloud.appwrite.io/v1/oauth2/<PROJECT_ID>/authorize
[Query]
client_id: <CLIENT_ID>
redirect_uri: https://vantage.localhost/auth/redirect
response_type: code
scope: openid profile email
```

Each query parameter tells TaskFlow how to handle the request:

| Parameter | Meaning |
| --- | --- |
| `client_id` | Identifies Vantage as the client requesting access. |
| `redirect_uri` | Tells TaskFlow where to return the browser after the user approves or rejects. It must match a URI registered for Vantage. |
| `response_type=code` | Requests an authorization code. The browser receives this temporary code, then Vantage's server exchanges it for tokens in step 3. |
| `scope` | Lists the access Vantage is requesting. `openid` starts an OpenID Connect sign-in, while `profile` and `email` request the user's basic profile and email claims. |

When you open this URL, Appwrite validates the request and checks for an active TaskFlow user session.

#### 2. Review and approve Vantage's request

When Vantage opens the authorization endpoint for a signed-in user, Appwrite creates a pending authorization request called a **grant**. The grant connects the user, Vantage, the requested scopes, and Vantage's redirect URI. Appwrite then redirects the browser to TaskFlow's **authorization URL**, the consent page, with the `grant_id` in the query string. A complete consent screen uses that grant ID to show what Vantage is asking for and lets the user approve or reject; the [Authorization](/docs/products/auth/oauth-server/authorization#consent) guide shows how to build it.

This quick start does not build TaskFlow's consent screen, so the browser lands on `https://taskflow.localhost/consent` and finds nothing there. That is fine: copy the `grant_id` from the address bar and approve the request manually. If the URL has no `grant_id`, the user has no active TaskFlow session. Sign in a user on the project, then open Vantage's authorization URL again.

Approving needs the same TaskFlow user's session, sent as the `a_session_<PROJECT_ID>` cookie, which holds the session secret. To find its value, open your browser's developer tools on the Network tab, look at any request to Appwrite, such as `GET /v1/account`, and copy the cookie from the request headers. Then send this request:

```curl
curl -X POST 'https://<REGION>.cloud.appwrite.io/v1/oauth2/<PROJECT_ID>/approve' \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json' \
  -H 'Cookie: a_session_<PROJECT_ID>=<SESSION_SECRET>' \
  -d '{
    "grant_id": "<GRANT_ID>"
  }'
```
```hurl
POST https://<REGION>.cloud.appwrite.io/v1/oauth2/<PROJECT_ID>/approve
Content-Type: application/json
Accept: application/json
Cookie: a_session_<PROJECT_ID>=<SESSION_SECRET>
{
    "grant_id": "<GRANT_ID>"
}
```

The `Accept: application/json` header asks for the redirect target as JSON instead of following it, which is what a consent screen needs and what lets you read the code here:

```json
{
    "redirectUrl": "https://vantage.localhost/auth/redirect?code=<CODE>&iss=..."
}
```

Copy the `code` value out of `redirectUrl`. Vantage uses it in the next step. Without the `Accept` header, Appwrite answers with a `303` redirect and the code appears only in the `Location` header.

#### 3. Exchange the code for tokens

Vantage's server sends the code to the token endpoint. Vantage is a confidential client, so it authenticates with its `client_secret`. A public client sends a `code_verifier` in place of the secret, but only when its authorization request carried a `code_challenge`. Sending a verifier for a code that did not use PKCE is rejected, and sending a secret from a public client is rejected too.

```curl
curl -X POST 'https://<REGION>.cloud.appwrite.io/v1/oauth2/<PROJECT_ID>/token' \
  -H 'Content-Type: application/json' \
  -d '{
    "grant_type": "authorization_code",
    "code": "<CODE>",
    "redirect_uri": "https://vantage.localhost/auth/redirect",
    "client_id": "<CLIENT_ID>",
    "client_secret": "<CLIENT_SECRET>"
  }'
```
```hurl
POST https://<REGION>.cloud.appwrite.io/v1/oauth2/<PROJECT_ID>/token
Content-Type: application/json
{
    "grant_type": "authorization_code",
    "code": "<CODE>",
    "redirect_uri": "https://vantage.localhost/auth/redirect",
    "client_id": "<CLIENT_ID>",
    "client_secret": "<CLIENT_SECRET>"
}
```

The response contains an access token, a refresh token, and an ID token:

```json
{
    "access_token": "eyJ0eXAiOiJhdCtqd3Qi...",
    "token_type": "Bearer",
    "expires_in": 28800,
    "refresh_token": "eyJ0eXAiOiJKV1Qi...",
    "scope": "openid profile email",
    "id_token": "eyJ0eXAiOiJKV1Qi..."
}
```

#### 4. Read the user's profile

Call the userinfo endpoint with the access token to confirm the sign-in worked end to end:

```curl
curl 'https://<REGION>.cloud.appwrite.io/v1/oauth2/<PROJECT_ID>/userinfo' \
  -H 'Authorization: Bearer <ACCESS_TOKEN>'
```
```hurl
GET https://<REGION>.cloud.appwrite.io/v1/oauth2/<PROJECT_ID>/userinfo
Authorization: Bearer <ACCESS_TOKEN>
```

```json
{
    "sub": "6a5138d1971d49a87f0a",
    "email": "walter@example.com",
    "email_verified": true,
    "name": "Walter O'Brien",
    "updated_at": 1784040398
}
```

See [Tokens](/docs/products/auth/oauth-server/tokens) for token structure, validation, refresh, and revocation.

### Next steps

Enabling the server is half of becoming a provider. Integrators like Vantage rely on TaskFlow for two more things:

- **Documentation** that covers the discovery URL, the available scopes and what they grant, and how to register a client.
- **A developer platform** on TaskFlow's own website where integrators register and manage their clients with the `apps` service, which the [Clients](/docs/products/auth/oauth-server/clients) page walks through.

- [Authorization](/docs/products/auth/oauth-server/authorization): Host your consent screen and drive the authorize, grant, and approve steps.
- [Clients](/docs/products/auth/oauth-server/clients): Confidential vs public clients, secrets, and rotation.
- [Tokens](/docs/products/auth/oauth-server/tokens): Token lifetimes, refresh with rotation, introspection, and revocation.

---

## Scopes

URL: https://appwrite.io/docs/products/auth/oauth-server/scopes

Scopes are the permissions a client asks for during authorization. The user sees the requested scopes on the consent screen and approves or declines them. The access token the server issues carries the scopes that were granted.

### Built-in scopes

Four OpenID Connect scopes are always available and cannot be removed:

| Scope | Grants access to |
| --- | --- |
| `openid` | The user's subject identifier. Required for OpenID Connect and to receive an ID token. |
| `profile` | The user's profile claims, such as their name. |
| `email` | The user's email address. |
| `phone` | The user's phone number. |

These appear as locked entries in the **Scopes** field on the OAuth2 server settings. A client requests them by listing them in the `scope` parameter on the authorization request, space-separated, for example `openid profile email`.

### Custom scopes

Beyond the built-in scopes, you define your own to represent permissions in your product. A common naming pattern is `resource.action`, such as `games.read` or `billing.write`. Some products also define an `admin` scope for full access. If you use a broad scope, describe it clearly on the consent screen and enforce it consistently in your API.

Add custom scopes in the **Scopes** field on the OAuth2 server settings, or with the `scopes` array on the `updateOAuth2Server` method. The `openid`, `profile`, `email`, and `phone` scopes are always merged in, so you only list the custom ones.

![Configuring scopes on the OAuth2 server](/images/docs/oauth-server/oauth2-server-settings.avif)

A project can define up to 100 scopes, each up to 128 characters. A client can only request scopes you have defined. Requesting an unknown scope does not return an error to the caller: Appwrite redirects the browser back to the client's redirect URI with `error=invalid_scope` and an `error_description` naming the scope.

Most custom scopes are labels the OAuth2 server carries through the flow and stamps onto the access token. Enforcing what they allow is your resource server's job. Read the `scope` claim from the access token, or from an [introspection](/docs/products/auth/oauth-server/tokens#introspect) response, and allow or deny the request accordingly.

The `project:` namespace behaves differently. Appwrite resolves those scopes against your project's own API, so a token granted `project:databases.read` authorizes that Appwrite operation directly, and `project:all` grants every ordinary project scope. Define a `project:` scope only when you intend to hand a client access to your Appwrite APIs.

### Requesting scopes

![A scope travels from the client's request through consent and the access token to the resource server's check](/images/docs/oauth-server/diagram-scopes.avif)

A client asks for scopes in the space-separated `scope` parameter when it starts authorization, for example `openid profile games.read`. See [Authorization](/docs/products/auth/oauth-server/authorization#authorize) for the authorize request and SDK examples.

Your consent screen shows the requested scopes and can approve a subset. The access token and token response contain the scopes that were granted, so the client can detect when it received less access than it requested. Clients should request only the permissions the integration needs.

### Rich authorization requests

A scope is a flat permission such as `tasks.read`. It tells your API what the client may do, but it cannot identify which projects the permission applies to. Rich authorization requests (RAR, [RFC 9396](https://datatracker.ietf.org/doc/html/rfc9396)) add those structured details.

Keep scopes as the primary permission contract for third-party clients. Use authorization details to bind those permissions to resources selected during consent. With this model, a client can request `tasks.read` without knowing a project ID, and your consent screen can ask the user which projects to grant.

#### Define accepted types

The OAuth2 server accepts only the authorization detail types configured in `authorizationDetailsTypes`. For project-level restrictions, include `project`. A `project` entry contains a non-empty `identifiers` array with project IDs, or `*` to represent every project.

Configure the accepted types when you update the OAuth2 server:

```server-nodejs
import { Client, Project } from '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 project = new Project(client);

const result = await project.updateOAuth2Server({
    enabled: true,
    authorizationUrl: 'https://your-product.com/oauth/consent',
    scopes: ['tasks.read', 'tasks.write'],
    authorizationDetailsTypes: ['project'],
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Project;

$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

$project = new Project($client);

$result = $project->updateOAuth2Server(
    enabled: true,
    authorizationUrl: 'https://your-product.com/oauth/consent',
    scopes: ['tasks.read', 'tasks.write'],
    authorizationDetailsTypes: ['project']
);
```
```server-python
from appwrite.client import Client
from appwrite.services.project import Project

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

project = Project(client)

result = project.update_o_auth2_server(
    enabled = True,
    authorization_url = 'https://your-product.com/oauth/consent',
    scopes = ['tasks.read', 'tasks.write'],
    authorization_details_types = ['project']
)
```
```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

project = Project.new(client)

result = project.update_o_auth2_server(
    enabled: true,
    authorization_url: 'https://your-product.com/oauth/consent',
    scopes: ['tasks.read', 'tasks.write'],
    authorization_details_types: ['project']
)
```
```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

Project project = Project(client);

Project result = await project.updateOAuth2Server(
    enabled: true,
    authorizationUrl: 'https://your-product.com/oauth/consent',
    scopes: ['tasks.read', 'tasks.write'],
    authorizationDetailsTypes: ['project'],
);
```
```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

Project project = new Project(client);

Project result = await project.UpdateOAuth2Server(
    enabled: true,
    authorizationUrl: "https://your-product.com/oauth/consent",
    scopes: new List<string> { "tasks.read", "tasks.write" },
    authorizationDetailsTypes: new List<string> { "project" }
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Project

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 project = Project(client)

val response = project.updateOAuth2Server(
    enabled = true,
    authorizationUrl = "https://your-product.com/oauth/consent",
    scopes = listOf("tasks.read", "tasks.write"),
    authorizationDetailsTypes = listOf("project")
)
```
```server-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Project;

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

Project project = new Project(client);

project.updateOAuth2Server(
    true, // enabled
    "https://your-product.com/oauth/consent", // authorizationUrl
    List.of("tasks.read", "tasks.write"), // scopes (optional)
    List.of("project"), // authorizationDetailsTypes (optional)
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        System.out.println(result);
    })
);
```
```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 project = Project(client)

let result = try await project.updateOAuth2Server(
    enabled: true,
    authorizationUrl: "https://your-product.com/oauth/consent",
    scopes: ["tasks.read", "tasks.write"],
    authorizationDetailsTypes: ["project"]
)
```
```server-go
package main

import (
    "fmt"
    "log"

    "github.com/appwrite/sdk-for-go/v6/appwrite"
)

func main() {
    client := appwrite.NewClient(
        appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"),
        appwrite.WithProject("<YOUR_PROJECT_ID>"),
        appwrite.WithKey("<YOUR_API_KEY>"),
    )

    project := appwrite.NewProject(client)

    response, err := project.UpdateOAuth2Server(
        true,
        "https://your-product.com/oauth/consent",
        project.WithUpdateOAuth2ServerScopes([]string{"tasks.read", "tasks.write"}),
        project.WithUpdateOAuth2ServerAuthorizationDetailsTypes([]string{"project"}),
    )
    if err != nil {
        log.Fatal(err)
    }

    fmt.Println(response)
}
```
```server-rust
use appwrite::Client;
use appwrite::services::Project;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let 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

    let project = Project::new(&client);

    let result = project
        .update_o_auth2_server(
            true,
            "https://your-product.com/oauth/consent",
            Some(vec!["tasks.read".to_string(), "tasks.write".to_string()]),
            Some(vec!["project".to_string()]), // authorization details types
            None,
            None,
            None,
            None,
            None,
            None,
            None,
            None,
            None,
            None,
            None,
            None,
        )
        .await?;

    let _ = result;

    Ok(())
}
```

**The call replaces the whole configuration**

`updateOAuth2Server` sets the full OAuth2 server configuration in one call. Include your existing scopes and settings when adding types, or they reset to their defaults.

Like scopes, a project can define up to 100 types, each up to 128 characters. The accepted types are published in the [discovery document](/docs/products/auth/oauth-server/quick-start#discovery) under `authorization_details_types_supported`.

#### Request authorization details

Most clients should request scopes without sending `authorization_details`. Your consent screen can read the requested scopes, ask the user to select resources, and add the details when it approves the grant. This keeps project IDs and resource-selection logic out of the third-party integration.

For example, after a client requests `tasks.read`, the user might choose one project. The consent screen approves the grant with the selected project in `authorization_details`:

```bash
curl --request POST \
  'https://<REGION>.cloud.appwrite.io/v1/oauth2/<PROJECT_ID>/approve' \
  --header 'Accept: application/json' \
  --header 'Cookie: a_session_<PROJECT_ID>=<SESSION_SECRET>' \
  --form 'grant_id=<GRANT_ID>' \
  --form 'authorization_details=[
    {
      "type": "project",
      "identifiers": ["<RESOURCE_PROJECT_ID>"]
    }
  ]'
```

In a browser-based consent screen, the project session cookie is sent automatically, so the `Cookie` header is not needed. The JSON response contains the client's redirect URL. Send the browser to that URL to continue the authorization flow.

A client that already knows the relevant resource IDs may send the same structure on its [authorize request](/docs/products/auth/oauth-server/authorization#authorize). Treat that as an optional preselection. The authorization flow should also work when the client omits it, and the consent screen should let the user review or narrow the final resource selection.

Appwrite reserves two authorization details types, `project` and `organization`. Both accept only `type` and a non-empty `identifiers` array. For `project`, each identifier must be a project ID or `*` for every project, and the Console project is rejected because it is reserved. Other values fail validation before the grant is approved. Types you define yourself carry no such restriction and may include any extra fields your consent screen needs.

#### Consent and enforcement

Scopes and authorization details answer different questions throughout the flow:

1. The client requests capabilities such as `tasks.read`.
2. Your consent screen reads the grant and asks the user which projects those capabilities should cover.
3. The consent screen approves the grant with the selected `authorization_details`.
4. Appwrite returns the granted details in the token response, includes them in the access token, and returns them from token introspection.

Your resource server must enforce both parts. After [introspecting the access token](/docs/products/auth/oauth-server/tokens#introspect), check that `active` is `true`, require the scope needed by the API operation, and then require an authorization detail that covers the requested resource. For an endpoint that reads tasks from one project, require `tasks.read` and a `project` entry whose `identifiers` contains that project ID or `*`.

Apply these checks conservatively:

- Treat a missing scope, missing authorization detail, unknown type, or unmatched identifier as denied.
- Interpret `*` explicitly as every project. Decide whether that includes projects created later and state that behavior on the consent screen.
- If `admin` grants every operation, document whether it also bypasses resource restrictions. Keep that rule consistent across every API endpoint.
- Compare stable resource IDs, not display names, and remove duplicate identifiers before evaluating them.

Appwrite validates and carries the authorization details. Your consent screen decides what the user grants, and your resource server decides whether the granted scopes and details cover each API request.

---

## Sign in with your product

URL: https://appwrite.io/docs/products/auth/oauth-server/sign-in-with-your-product/step-1

Once your project's [OAuth2 server](/docs/products/auth/oauth-server) is enabled, other apps can offer "Sign in with your product". This tutorial builds that experience end to end with two small [TanStack Start](https://tanstack.com/start) apps, so you can see every part of the flow.

### What you will build

Two apps play the two sides of an OAuth integration:

- **TaskFlow**, the provider. It owns the Appwrite project with the OAuth2 server enabled, and it hosts the **consent screen** where its users approve access.
- **Vantage**, the consumer. A separate product that adds a **Sign in with TaskFlow** button, exchanges the authorization code for tokens on its server, and reads the user's profile.

TaskFlow is your product. Vantage stands in for any third party integrating with it.

### The flow

When a Vantage user clicks **Sign in with TaskFlow**, this happens:

1. Vantage redirects the user to TaskFlow's **authorize** endpoint.
2. The OAuth2 server sends the user to TaskFlow's **consent screen** to sign in and approve.
3. On approval, the server redirects back to Vantage with an **authorization code**.
4. Vantage's server **exchanges the code for tokens** using its client secret.
5. Vantage reads the user's profile from **userinfo** and signs them in.

The authorization code flow is standard OAuth 2.1, so nothing here is Appwrite-specific on the wire. The two pieces you build are the consent screen TaskFlow hosts and the sign-in Vantage adds.

### Why the token exchange runs on the server

Vantage is a **confidential client**: it has a client secret. That secret authenticates the token exchange and must never reach the browser. TanStack Start makes this natural, the exchange runs inside a **server function**, so the secret stays on the server the whole time.

### Prerequisites

- An Appwrite Cloud project.
- [Node.js](https://nodejs.org/) 20 or newer and a package manager (this tutorial uses `pnpm`).
- Basic familiarity with React.

**Get the finished code**

The complete apps from this tutorial are on GitHub at [appwrite-community/oauth-guide-taskflow](https://github.com/appwrite-community/oauth-guide-taskflow). Clone it to follow along or to compare against your own.

Continue to enable the OAuth2 server on your project.

---

## Enable the OAuth2 server

URL: https://appwrite.io/docs/products/auth/oauth-server/sign-in-with-your-product/step-2

Before writing any code, turn TaskFlow's project into an OAuth provider and register Vantage as a client.

### Enable the server

![Enabling the OAuth2 server in the Appwrite Console](/images/docs/oauth-server/oauth2-server-settings.avif)

In the Console, open **Auth**, select the **OAuth2 server** tab, and turn on **Enable OAuth2 server**.

Set the **Authorization URL** to where TaskFlow will host its consent screen. In this tutorial that is `http://localhost:4000/oauth/consent`. This is where the OAuth2 server sends users to sign in and approve.

Leave the scopes at their defaults. `openid`, `profile`, `email`, and `phone` are always included, and Vantage only needs the first three. See [Scopes](/docs/products/auth/oauth-server/scopes) to add your own later.

### Register the client

![Creating the Vantage client in the Appwrite Console](/images/docs/oauth-server/oauth2-server-create-app.avif)

Open the **Apps** sub-tab and create a client for Vantage:

- **Name**: `Vantage`. This appears on the consent screen.
- **Client type**: `Confidential`. Vantage has a server that can hold a secret.
- **Redirect URIs**: `http://localhost:4100/oauth/callback`. The OAuth2 server only returns codes to registered URIs.

When you create the client, copy the **client secret**. It is shown once. You will also need the **client ID** from the clients list.

![The client secret shown once on creation](/images/docs/oauth-server/oauth2-server-secret-created.avif)

**Keep these three values**

You now have everything the apps need to talk to the OAuth2 server: the **client ID**, the **client secret**, and your **project ID**. Keep them handy for the next step.

For the full set of client options and the SDK equivalents, see [Clients](/docs/products/auth/oauth-server/clients).

Continue to scaffold the two apps.

---

## Create the apps

URL: https://appwrite.io/docs/products/auth/oauth-server/sign-in-with-your-product/step-3

Both sides are TanStack Start apps. Scaffold them in a single folder.

### Scaffold the projects

Create the consumer (Vantage) and the provider (TaskFlow):

```sh
npx @tanstack/cli create consumer --framework React --package-manager pnpm --no-examples
npx @tanstack/cli create provider --framework React --package-manager pnpm --no-examples
```

This gives you two full TanStack Start apps with server functions, file-based routing, and Tailwind CSS already set up.

Give each a fixed port so the redirect URIs stay stable. In each app's `package.json`, set the dev script:

```json
// consumer/package.json
"scripts": { "dev": "vite dev --port 4100" }
```

```json
// provider/package.json
"scripts": { "dev": "vite dev --port 4000" }
```

### Configure the environment

The apps read the OAuth values from environment variables. Add a `.env` to each.

Vantage needs the client credentials and its redirect URI:

```sh
### consumer/.env
OAUTH_ISSUER=https://<REGION>.cloud.appwrite.io/v1/oauth2/<PROJECT_ID>
OAUTH_CLIENT_ID=<CLIENT_ID>
OAUTH_CLIENT_SECRET=<CLIENT_SECRET>
OAUTH_REDIRECT_URI=http://localhost:4100/oauth/callback
SESSION_SECRET=<A_LONG_RANDOM_STRING>
```

TaskFlow needs its project details and an API key (used only to read a client's display name for the consent card):

```sh
### provider/.env
APPWRITE_ENDPOINT=https://<REGION>.cloud.appwrite.io/v1
APPWRITE_PROJECT=<PROJECT_ID>
OAUTH_ISSUER=https://<REGION>.cloud.appwrite.io/v1/oauth2/<PROJECT_ID>
APPWRITE_API_KEY=<API_KEY_WITH_APPS_READ>
SESSION_SECRET=<A_LONG_RANDOM_STRING>
```

Vite only exposes variables prefixed with `VITE_` to the browser, and these are secrets, so load them into the server with `dotenv`. Install it in both apps:

```sh
pnpm add dotenv
```

Then import it at the top of each `vite.config.ts`, before anything else, so `process.env` is populated when the server runs:

```ts
// vite.config.ts
import 'dotenv/config'
import { defineConfig } from 'vite'
// ...rest of the config
```

With both apps scaffolded and configured, build Vantage's sign-in next.

---

## Add Sign in with your product

URL: https://appwrite.io/docs/products/auth/oauth-server/sign-in-with-your-product/step-4

Start with Vantage, the consumer. It needs a helper for the OAuth values, a landing page with a **Sign in with TaskFlow** button, and a route that kicks off the flow.

### The OAuth helper

Create `consumer/src/lib/oauth.ts`. It reads the config and builds the authorization URL. Only server functions import this module, and TanStack Start strips server function bodies out of the browser bundle, so the client secret stays on the server. Never import it from a component.

```ts
// consumer/src/lib/oauth.ts
import { useSession } from '@tanstack/react-start/server'

const issuer = process.env.OAUTH_ISSUER!
const clientId = process.env.OAUTH_CLIENT_ID!
const redirectUri = process.env.OAUTH_REDIRECT_URI!

export const SCOPES = 'openid profile email'

/** Build the URL that starts the authorization code flow. */
export function authorizeUrl(state: string) {
  const params = new URLSearchParams({
    client_id: clientId,
    redirect_uri: redirectUri,
    response_type: 'code',
    scope: SCOPES,
    state,
  })
  return `${issuer}/authorize?${params.toString()}`
}

type SessionData = { accessToken?: string; user?: unknown; state?: string }

/** A signed, httpOnly cookie session for Vantage. */
export function vantageSession() {
  return useSession<SessionData>({
    name: 'vantage_session',
    password: process.env.SESSION_SECRET!,
  })
}
```

### The start route

Clicking the button navigates to `/oauth/start`. Its loader mints a random `state` value, stores it in the session to protect against CSRF, and redirects to the OAuth2 server. Create `consumer/src/routes/oauth.start.tsx`:

```tsx
// consumer/src/routes/oauth.start.tsx
import { createFileRoute, redirect } from '@tanstack/react-router'
import { createServerFn } from '@tanstack/react-start'
import { authorizeUrl, vantageSession } from '../lib/oauth'

const start = createServerFn().handler(async () => {
  const state = crypto.randomUUID()
  const session = await vantageSession()
  await session.update({ state })
  throw redirect({ href: authorizeUrl(state) })
})

export const Route = createFileRoute('/oauth/start')({
  loader: async () => {
    await start()
  },
  component: () => null,
})
```

### The sign-in button

![Vantage landing page with a Sign in with TaskFlow button](/images/docs/oauth-server/guide/vantage-landing.avif)

On the landing page, the button is a link to `/oauth/start`:

```tsx
// consumer/src/routes/index.tsx (excerpt)
<a href="/oauth/start" className="...">
  Sign in with TaskFlow
</a>
```

Start the app with `pnpm dev` and open `http://localhost:4100`. You have a landing page with a working sign-in button. Clicking it redirects to the OAuth2 server, which sends the user to TaskFlow's consent screen. That screen does not exist yet, so build it next.

---

## Build the consent screen

URL: https://appwrite.io/docs/products/auth/oauth-server/sign-in-with-your-product/step-5

The consent screen is the page TaskFlow hosts at its authorization URL. When the OAuth2 server sends a user here, the screen signs them in, shows what the client is asking for, and records their decision. All of it runs on TaskFlow's server, carrying the user's Appwrite session.

### Types for the consent card

Create `provider/src/lib/consent-types.ts`. It holds only client-safe values, so the browser can import it:

```ts
// provider/src/lib/consent-types.ts
export type Grant = {
  $id: string
  appId: string
  scopes: string[]
  redirectUri: string
}

export type ClientApp = { $id: string; name: string; tagline?: string }

// Human-readable labels for the scopes shown on the consent card.
export const SCOPE_LABELS: Record<string, string> = {
  openid: 'Confirm your identity',
  profile: 'See your name and profile details',
  email: 'See your email address',
  phone: 'See your phone number',
}
```

### The server helpers

Create `provider/src/lib/oauth-server.ts`. Every function here calls the OAuth2 server on behalf of the signed-in user. Appwrite returns the user's session token in a cookie on login, and that token is passed as the `X-Appwrite-Session` header on later calls.

```ts
// provider/src/lib/oauth-server.ts
import { useSession } from '@tanstack/react-start/server'
import type { ClientApp, Grant } from './consent-types'

const endpoint = process.env.APPWRITE_ENDPOINT!
const project = process.env.APPWRITE_PROJECT!
const issuer = process.env.OAUTH_ISSUER!
const apiKey = process.env.APPWRITE_API_KEY!

const projectHeaders = { 'X-Appwrite-Project': project }

/** Log a TaskFlow user in and return their Appwrite session token. */
export async function login(email: string, password: string): Promise<string> {
  const res = await fetch(`${endpoint}/account/sessions/email`, {
    method: 'POST',
    headers: { ...projectHeaders, 'Content-Type': 'application/json' },
    body: JSON.stringify({ email, password }),
    redirect: 'manual',
  })
  if (!res.ok) throw new Error('Invalid email or password')
  const setCookie = res.headers.get('set-cookie') ?? ''
  const match = setCookie.match(new RegExp(`a_session_${project}=([^;]+)`))
  if (!match) throw new Error('No session returned')
  // The cookie value is URL-encoded on the wire, so the base64 padding
  // arrives as %3D. Decode it before sending it back as a header.
  return decodeURIComponent(match[1])
}

type AuthorizeParams = Record<string, string>

/** Create a grant for the signed-in user, or get a redirect if the OAuth
 *  server auto-approved a request the user already consented to. */
export async function authorize(
  params: AuthorizeParams,
  sessionToken: string,
): Promise<{ grantId?: string; redirect?: string }> {
  const qs = new URLSearchParams(params).toString()
  const res = await fetch(`${issuer}/authorize?${qs}`, {
    headers: { ...projectHeaders, 'X-Appwrite-Session': sessionToken },
    redirect: 'manual',
  })
  const location = res.headers.get('location') ?? ''
  const grantId = new URL(location, endpoint).searchParams.get('grant_id')
  return grantId ? { grantId } : { redirect: location }
}

export async function getGrant(grantId: string, sessionToken: string): Promise<Grant> {
  const res = await fetch(`${issuer}/grants/${grantId}`, {
    headers: { ...projectHeaders, 'X-Appwrite-Session': sessionToken },
  })
  if (!res.ok) throw new Error(`Grant not found: ${res.status}`)
  return res.json()
}

/** Read a client's display name for the consent card, using a server-side key. */
export async function getClientApp(appId: string): Promise<ClientApp> {
  const res = await fetch(`${endpoint}/apps/${appId}`, {
    headers: { ...projectHeaders, 'X-Appwrite-Key': apiKey },
  })
  if (!res.ok) throw new Error(`App not found: ${res.status}`)
  return res.json()
}

/** Approve or reject a grant. The OAuth2 server returns the URL to send the
 *  user back to, carrying the authorization code (approve) or an error (reject). */
async function decide(
  action: 'approve' | 'reject',
  grantId: string,
  sessionToken: string,
): Promise<string> {
  const res = await fetch(`${issuer}/${action}`, {
    method: 'POST',
    headers: {
      ...projectHeaders,
      'X-Appwrite-Session': sessionToken,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ grant_id: grantId }),
    redirect: 'manual',
  })
  const location = res.headers.get('location')
  if (!location) throw new Error(`${action} failed: ${res.status}`)
  return location
}

export const approve = (grantId: string, token: string) => decide('approve', grantId, token)
export const reject = (grantId: string, token: string) => decide('reject', grantId, token)

type SessionData = { token?: string; email?: string; params?: AuthorizeParams }

/** TaskFlow's own signed session cookie, holding the Appwrite session token. */
export function taskflowSession() {
  return useSession<SessionData>({
    name: 'taskflow_session',
    password: process.env.SESSION_SECRET!,
  })
}
```

### The consent route

Create `provider/src/routes/oauth.consent.tsx`. The loader decides what to show: the login form if the user is not signed in, or the consent card once they are. It captures the incoming authorize parameters so it can resume the request after login.

```tsx
// provider/src/routes/oauth.consent.tsx (loader and server functions)
import { createFileRoute, redirect } from '@tanstack/react-router'
import { createServerFn } from '@tanstack/react-start'
import { getRequestUrl } from '@tanstack/react-start/server'
import {
  approve, authorize, getClientApp, getGrant, login, reject, taskflowSession,
} from '../lib/oauth-server'
import type { ClientApp, Grant } from '../lib/consent-types'

// Every authorize parameter the consent screen must carry across login.
// Dropping one silently changes the request. Without code_challenge, a
// PKCE client fails when the flow resumes.
const AUTHORIZE_KEYS = [
  'client_id', 'redirect_uri', 'response_type', 'scope', 'state', 'nonce',
  'code_challenge', 'code_challenge_method', 'prompt', 'max_age',
  'authorization_details', 'resource',
]

type ConsentView =
  | { view: 'login' }
  | { view: 'consent'; grant: Grant; app: ClientApp; email: string }

const loadConsent = createServerFn().handler(async (): Promise<ConsentView> => {
  const url = getRequestUrl()
  const session = await taskflowSession()

  // Capture the authorize request so we can resume it after login.
  const incoming: Record<string, string> = {}
  for (const key of AUTHORIZE_KEYS) {
    const value = url.searchParams.get(key)
    if (value) incoming[key] = value
  }

  if (!session.data.token) {
    if (Object.keys(incoming).length) await session.update({ params: incoming })
    return { view: 'login' }
  }

  let grantId = url.searchParams.get('grant_id')
  if (!grantId) {
    const params = { ...session.data.params, ...incoming }
    const result = await authorize(params, session.data.token)
    if (result.redirect) throw redirect({ href: result.redirect })
    grantId = result.grantId!
  }

  const grant = await getGrant(grantId, session.data.token)
  const app = await getClientApp(grant.appId)
  return { view: 'consent', grant, app, email: session.data.email ?? '' }
})

const submitLogin = createServerFn({ method: 'POST' })
  .validator((d: { email: string; password: string }) => d)
  .handler(async ({ data }) => {
    const token = await login(data.email, data.password)
    const session = await taskflowSession()
    await session.update({ token, email: data.email })
  })

const decideGrant = createServerFn({ method: 'POST' })
  .validator((d: { grantId: string; action: 'approve' | 'reject' }) => d)
  .handler(async ({ data }) => {
    const session = await taskflowSession()
    const act = data.action === 'approve' ? approve : reject
    const location = await act(data.grantId, session.data.token!)
    throw redirect({ href: location })
  })

export const Route = createFileRoute('/oauth/consent')({
  component: Consent,
  loader: async () => loadConsent(),
})
```

The component renders one of two cards from the loader data. The login form submits `submitLogin` and then calls `router.invalidate()` so the loader re-runs and moves to the consent view. The consent card reads `grant.scopes` and maps them through `SCOPE_LABELS`, and calls `decideGrant` on **Authorize** or **Cancel**. Only the styling is left out here.

### Sign in and approve

![TaskFlow consent screen sign-in form](/images/docs/oauth-server/guide/taskflow-login.avif)

When the OAuth2 server sends a user to the consent screen, they first sign in with their TaskFlow account.

![TaskFlow consent screen showing the requested permissions](/images/docs/oauth-server/guide/taskflow-consent.avif)

The consent card then shows the client's name and exactly what it is asking for. On **Authorize**, the OAuth2 server redirects back to Vantage with an authorization code. Vantage handles it next.

---

## Exchange the code for tokens

URL: https://appwrite.io/docs/products/auth/oauth-server/sign-in-with-your-product/step-6

The OAuth2 server redirects back to Vantage's redirect URI with a `code` and the `state`. Vantage exchanges that code for tokens on its server, reads the user's profile, and signs them in.

### Add the token functions

Extend `consumer/src/lib/oauth.ts` with the exchange and userinfo calls. The exchange authenticates with the client secret using HTTP Basic auth, which is why it must run on the server.

```ts
// consumer/src/lib/oauth.ts (additions)
const clientSecret = process.env.OAUTH_CLIENT_SECRET!

export type Tokens = {
  access_token: string
  refresh_token: string
  id_token: string
  expires_in: number
  scope: string
}

/** Exchange an authorization code for tokens. */
export async function exchangeCode(code: string): Promise<Tokens> {
  const basic = Buffer.from(`${clientId}:${clientSecret}`).toString('base64')
  const res = await fetch(`${issuer}/token`, {
    method: 'POST',
    headers: {
      Authorization: `Basic ${basic}`,
      'Content-Type': 'application/x-www-form-urlencoded',
    },
    body: new URLSearchParams({
      grant_type: 'authorization_code',
      code,
      redirect_uri: redirectUri,
    }),
  })
  if (!res.ok) throw new Error(`Token exchange failed: ${res.status}`)
  return res.json()
}

export type UserInfo = {
  sub: string
  name?: string
  email?: string
  email_verified?: boolean
}

/** Read the signed-in user's profile from the userinfo endpoint. */
export async function fetchUserInfo(accessToken: string): Promise<UserInfo> {
  const res = await fetch(`${issuer}/userinfo`, {
    headers: { Authorization: `Bearer ${accessToken}` },
  })
  if (!res.ok) throw new Error(`userinfo failed: ${res.status}`)
  return res.json()
}
```

### Handle the callback

Create `consumer/src/routes/oauth.callback.tsx`. Its loader runs on the server: it checks the `state` against the session, exchanges the code, reads the profile, stores it in the session, and sends the user to the dashboard.

```tsx
// consumer/src/routes/oauth.callback.tsx
import { createFileRoute, redirect } from '@tanstack/react-router'
import { createServerFn } from '@tanstack/react-start'
import { getRequestUrl } from '@tanstack/react-start/server'
import { exchangeCode, fetchUserInfo, vantageSession } from '../lib/oauth'

const handleCallback = createServerFn().handler(async () => {
  const url = getRequestUrl()
  const code = url.searchParams.get('code')
  const state = url.searchParams.get('state')
  const session = await vantageSession()

  // The state must match the value we set in /oauth/start.
  if (!code || !state || state !== session.data.state) {
    throw redirect({ to: '/', search: { error: 'invalid_state' } })
  }

  const tokens = await exchangeCode(code)
  const user = await fetchUserInfo(tokens.access_token)

  await session.update({
    accessToken: tokens.access_token,
    user,
    state: undefined,
  })

  throw redirect({ to: '/dashboard' })
})

export const Route = createFileRoute('/oauth/callback')({
  loader: async () => {
    await handleCallback()
  },
  component: () => null,
})
```

The client secret and the tokens live only inside these server functions. The browser only ever holds Vantage's own signed session cookie.

### Show the signed-in user

![Vantage dashboard showing the signed-in user's TaskFlow identity](/images/docs/oauth-server/guide/vantage-dashboard.avif)

The dashboard reads the user from the session and renders it. Create `consumer/src/routes/dashboard.tsx`:

```tsx
// consumer/src/routes/dashboard.tsx (loader)
import { createFileRoute, redirect } from '@tanstack/react-router'
import { createServerFn } from '@tanstack/react-start'
import { vantageSession, type UserInfo } from '../lib/oauth'

const loadUser = createServerFn().handler(async (): Promise<UserInfo> => {
  const session = await vantageSession()
  if (!session.data.user) throw redirect({ to: '/' })
  return session.data.user as UserInfo
})

export const Route = createFileRoute('/dashboard')({
  component: Dashboard,
  loader: async () => ({ user: await loadUser() }),
})
```

The component renders `user.name`, `user.email`, and `user.sub` from the loader data. That profile came from TaskFlow, through the authorization code flow you built. Run the whole thing next.

---

## Run the flow

URL: https://appwrite.io/docs/products/auth/oauth-server/sign-in-with-your-product/step-7

Everything is in place. Run both apps and sign in.

### Start both apps

In two terminals:

```sh
### in consumer/
pnpm dev
```

```sh
### in provider/
pnpm dev
```

Vantage is at `http://localhost:4100` and TaskFlow's consent screen at `http://localhost:4000`.

### Sign in

Open `http://localhost:4100` and click **Sign in with TaskFlow**. You will:

1. Land on TaskFlow's consent screen and sign in with a TaskFlow user.
2. See exactly what Vantage is requesting, and approve it.
3. Return to Vantage, signed in, with your TaskFlow name and email on the dashboard.

That round trip is a complete OAuth 2.1 authorization code flow against your project's OAuth2 server.

### What each side did

- **TaskFlow** enabled the OAuth2 server, registered Vantage as a confidential client, and hosted a consent screen that authenticates its users and approves grants.
- **Vantage** redirected to the authorize endpoint, exchanged the returned code for tokens on its server, and read the user's profile from userinfo. The client secret never left its server.

Because the flow is standards-based, a real integrator does not have to use Appwrite or TanStack Start. Any OAuth or OpenID Connect client library pointed at your project's [discovery URL](/docs/products/auth/oauth-server/quick-start#discovery) works the same way.

The full source for both apps is on GitHub at [appwrite-community/oauth-guide-taskflow](https://github.com/appwrite-community/oauth-guide-taskflow).

### Next steps

- [Tokens](/docs/products/auth/oauth-server/tokens): Refresh access tokens, and revoke them on sign-out.
- [Scopes](/docs/products/auth/oauth-server/scopes): Add custom scopes to represent permissions in your product.
- [Device flow](/docs/products/auth/oauth-server/device-flow): Support TVs, CLIs, and other input-constrained devices.

---

## Tokens

URL: https://appwrite.io/docs/products/auth/oauth-server/tokens

When a client redeems an authorization code, the OAuth2 server issues an access token and refresh token. It also issues an ID token when the `openid` scope was granted. This page covers what each token does and how clients and resource servers validate, refresh, introspect, revoke, and end sessions.

### The three tokens

![The token endpoint issues access, refresh, and ID tokens, with refresh rotation and revocation](/images/docs/oauth-server/diagram-tokens.avif)

- **Access token.** A signed JWT that a client presents to a resource server when it calls an API on the user's behalf. It contains the authorization information the resource server needs to evaluate the request. The claims are explained in [Validate tokens](#validate).
- **Refresh token.** A value the client exchanges for a new access token when the current one expires, without sending the user through authorization again. Treat it as an opaque secret even though Appwrite currently encodes it as a JWT.
- **ID token.** A signed JWT from OpenID Connect that tells the client who authenticated. It is returned when the `openid` scope is granted and can include profile, email, or phone claims when those scopes were approved. A public client can use these verified claims to show the user's name or decide that its sign-in UI needs attention before calling an API. Resource servers authorize requests with the access token, not the ID token.

### Exchange a code for tokens

The client exchanges its authorization code at the token endpoint with `grantType: 'authorization_code'`. A confidential client sends its `clientSecret`, and a public client sends its `codeVerifier`. When the project requires PKCE for confidential clients, they send both.

```client-web
import { Client, Oauth2 } from 'appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

const oauth2 = new Oauth2(client);

const result = await oauth2.createToken({
    grantType: '<GRANT_TYPE>',
    code: '<CODE>', // optional
    refreshToken: '<REFRESH_TOKEN>', // optional
    deviceCode: '<DEVICE_CODE>', // optional
    clientId: '<CLIENT_ID>', // optional
    clientSecret: '<CLIENT_SECRET>', // optional
    codeVerifier: '<CODE_VERIFIER>', // optional
    redirectUri: 'https://example.com', // optional
    resource: '' // optional
});

console.log(result);
```
```client-flutter
import 'package:appwrite/appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

Oauth2 oauth2 = Oauth2(client);

Oauth2Token result = await oauth2.createToken(
    grantType: '<GRANT_TYPE>',
    code: '<CODE>', // optional
    refreshToken: '<REFRESH_TOKEN>', // optional
    deviceCode: '<DEVICE_CODE>', // optional
    clientId: '<CLIENT_ID>', // optional
    clientSecret: '<CLIENT_SECRET>', // optional
    codeVerifier: '<CODE_VERIFIER>', // optional
    redirectUri: 'https://example.com', // optional
    resource: '', // optional
);
```
```client-apple
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>") // Your project ID

let oauth2 = Oauth2(client)

let oauth2Token = try await oauth2.createToken(
    grantType: "<GRANT_TYPE>",
    code: "<CODE>", // optional
    refreshToken: "<REFRESH_TOKEN>", // optional
    deviceCode: "<DEVICE_CODE>", // optional
    clientId: "<CLIENT_ID>", // optional
    clientSecret: "<CLIENT_SECRET>", // optional
    codeVerifier: "<CODE_VERIFIER>", // optional
    redirectUri: "https://example.com", // optional
    resource: "" // optional
)
```
```client-android-kotlin
import io.appwrite.Client
import io.appwrite.coroutines.CoroutineCallback
import io.appwrite.services.Oauth2

val client = Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>") // Your project ID

val oauth2 = Oauth2(client)

val result = oauth2.createToken(
    grantType = "<GRANT_TYPE>", 
    code = "<CODE>", // (optional)
    refreshToken = "<REFRESH_TOKEN>", // (optional)
    deviceCode = "<DEVICE_CODE>", // (optional)
    clientId = "<CLIENT_ID>", // (optional)
    clientSecret = "<CLIENT_SECRET>", // (optional)
    codeVerifier = "<CODE_VERIFIER>", // (optional)
    redirectUri = "https://example.com", // (optional)
    resource = "", // (optional)
)
```
```client-android-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Oauth2;

Client client = new Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<YOUR_PROJECT_ID>"); // Your project ID

Oauth2 oauth2 = new Oauth2(client);

oauth2.createToken(
    "<GRANT_TYPE>", // grant_type 
    "<CODE>", // code (optional)
    "<REFRESH_TOKEN>", // refresh_token (optional)
    "<DEVICE_CODE>", // device_code (optional)
    "<CLIENT_ID>", // client_id (optional)
    "<CLIENT_SECRET>", // client_secret (optional)
    "<CODE_VERIFIER>", // code_verifier (optional)
    "https://example.com", // redirect_uri (optional)
    "", // resource (optional)
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        Log.d("Appwrite", result.toString());
    })
);
```
```client-react-native
import { Client, Oauth2 } from "react-native-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

const oauth2 = new Oauth2(client);

const result = await oauth2.createToken({
    grantType: '<GRANT_TYPE>',
    code: '<CODE>', // optional
    refreshToken: '<REFRESH_TOKEN>', // optional
    deviceCode: '<DEVICE_CODE>', // optional
    clientId: '<CLIENT_ID>', // optional
    clientSecret: '<CLIENT_SECRET>', // optional
    codeVerifier: '<CODE_VERIFIER>', // optional
    redirectUri: 'https://example.com', // optional
    resource: '' // optional
});

console.log(result);
```

The response includes the access token, refresh token, granted scopes, token type, and access-token lifetime. It includes an ID token when the `openid` scope was granted. `authorization_details` contains the approved rich authorization data when the request used it.

```json
{
  "access_token": "eyJ...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "eyJ...",
  "scope": "openid profile email",
  "authorization_details": null,
  "id_token": "eyJ..."
}
```

### Token lifetimes

Lifetimes default by client type and are configurable per project on the **OAuth2 server** settings.

| | Confidential | Public |
| --- | --- | --- |
| Access token | 8 hours | 1 hour |
| Refresh token | 365 days | 30 days |

Public clients get shorter lifetimes because their tokens live on user devices, where the risk of theft is higher, so the window of exposure is kept small.

### Refresh with rotation

To refresh, the client sends its current refresh token to the token endpoint. A successful response contains a new access token and a new refresh token.

Each code exchange starts its own token family, so one user can hold tokens for the same client on several devices at once. Each successful refresh replaces the stored refresh token within its family. The client must store the newest refresh token before making another request. If the client presents an older refresh token again, Appwrite treats it as reuse and deletes that whole family, so its access token and newest refresh token stop working. Other families for the same client and user keep working, as do tokens issued to other clients. The user's consent record survives, so the client can run the authorization flow again and receive a new code without a second consent prompt.

This public-client example sends JSON. A confidential client also includes its `client_secret`.

```curl
curl -X POST 'https://<REGION>.cloud.appwrite.io/v1/oauth2/<PROJECT_ID>/token' \
  -H 'Content-Type: application/json' \
  -d '{
    "grant_type": "refresh_token",
    "refresh_token": "<REFRESH_TOKEN>",
    "client_id": "<CLIENT_ID>"
  }'
```
```hurl
POST https://<REGION>.cloud.appwrite.io/v1/oauth2/<PROJECT_ID>/token
Content-Type: application/json
{
    "grant_type": "refresh_token",
    "refresh_token": "<REFRESH_TOKEN>",
    "client_id": "<CLIENT_ID>"
}
```

The response has the same shape as the original token response, with new values for `access_token`, `refresh_token`, and `expires_in`.

### Validate tokens

Access and ID tokens are `RS256` JWTs signed with your project's key. A JWT contains a header, payload, and signature. The payload is readable, but its claims are trustworthy only after the client or resource server verifies the signature, issuer, audience, and expiry.

The server publishes its public keys as a JWKS document:

```text
https://<REGION>.cloud.appwrite.io/v1/oauth2/<PROJECT_ID>/.well-known/jwks.json
```

A web client can use the `jose` package to read the discovery document, select the correct public key from the JWKS response, and verify both tokens:

```client-web
import { createRemoteJWKSet, jwtVerify } from 'jose';

const accessToken = '<ACCESS_TOKEN>';
const idToken = '<ID_TOKEN>';
const clientId = '<CLIENT_ID>';

const discoveryUrl =
    'https://<REGION>.cloud.appwrite.io/v1/oauth2/<PROJECT_ID>/.well-known/openid-configuration';
const metadata = await fetch(discoveryUrl).then((response) => response.json());
const jwks = createRemoteJWKSet(new URL(metadata.jwks_uri));
const projectAudience = metadata.issuer.replace('/oauth2/', '/');

const { payload: accessClaims } = await jwtVerify(accessToken, jwks, {
    issuer: metadata.issuer,
    audience: projectAudience
});

const { payload: idClaims } = await jwtVerify(idToken, jwks, {
    issuer: metadata.issuer,
    audience: clientId
});
```

A verified access-token payload contains the authorization context for API calls:

```json
{
  "iss": "<ISSUER>",
  "sub": "<USER_ID>",
  "aud": ["<PROJECT_API_AUDIENCE>"],
  "client_id": "<CLIENT_ID>",
  "scope": "openid profile email calendar.read",
  "auth_time": 1784052423,
  "iat": 1784052858,
  "exp": 1784056458,
  "jti": "<TOKEN_JTI>",
  "tokenId": "<TOKEN_ID>"
}
```

A verified ID-token payload identifies the signed-in user. Profile claims appear only when their matching scopes were granted:

```json
{
  "iss": "<ISSUER>",
  "sub": "<USER_ID>",
  "aud": "<CLIENT_ID>",
  "name": "Ada Lovelace",
  "email": "ada@example.com",
  "email_verified": true,
  "auth_time": 1784052423,
  "iat": 1784052858,
  "exp": 1784056458,
  "at_hash": "<ACCESS_TOKEN_HASH>"
}
```

A client may decode a token without verification to make a temporary UI choice, such as showing a sign-in screen before an API request. Only verified claims should control access or display trusted identity information.

To retrieve the current user's profile with an access token, call the userinfo endpoint. The token must carry the `openid` scope, otherwise the endpoint answers `401`:

```curl
curl 'https://<REGION>.cloud.appwrite.io/v1/oauth2/<PROJECT_ID>/userinfo' \
  -H 'Authorization: Bearer <ACCESS_TOKEN>'
```
```hurl
GET https://<REGION>.cloud.appwrite.io/v1/oauth2/<PROJECT_ID>/userinfo
Authorization: Bearer <ACCESS_TOKEN>
```

### Introspect a token

An OAuth access token is not an Appwrite session. Appwrite's OAuth endpoints consume tokens where the OAuth flow defines them: userinfo accepts an access token as a bearer credential, while the token, introspection, and revocation endpoints accept their respective tokens as request parameters.

Scopes in the `project:` namespace are the exception. Appwrite resolves those itself, so a token granted `project:databases.read` authorizes the matching Appwrite API operation when the client sends it as a bearer credential, and `project:all` grants every ordinary project scope. Scopes outside that namespace mean nothing to Appwrite and are yours to enforce.

To make an access token useful to your product:

1. Add custom scopes in the **Integration** card under **Auth > OAuth2 server > Server** that describe the operations your API exposes.
2. Host an API that acts as the resource server. Appwrite Functions and server-rendered Appwrite Sites are suitable places to run it.
3. Read the bearer access token from each incoming request.
4. From your server, call the introspection endpoint with an Appwrite API key that has the `oauth2.introspect` scope. Keep this API key on the server.
5. Require `active: true`, then check that `scope` contains every permission the API operation needs.
6. Perform the operation only after those checks pass.

Introspection verifies the token against its stored token family, so it detects expiry, revocation, refresh rotation, and refresh-token reuse. This is different from offline JWT verification, which cannot detect a token that was revoked before its `exp` time.

The resource server sends JSON:

```curl
curl -X POST 'https://<REGION>.cloud.appwrite.io/v1/oauth2/<PROJECT_ID>/introspect' \
  -H 'X-Appwrite-Project: <PROJECT_ID>' \
  -H 'X-Appwrite-Key: <API_KEY>' \
  -H 'Content-Type: application/json' \
  -d '{
    "token": "<ACCESS_TOKEN>",
    "token_type_hint": "access_token"
  }'
```
```hurl
POST https://<REGION>.cloud.appwrite.io/v1/oauth2/<PROJECT_ID>/introspect
X-Appwrite-Project: <PROJECT_ID>
X-Appwrite-Key: <API_KEY>
Content-Type: application/json
{
    "token": "<ACCESS_TOKEN>",
    "token_type_hint": "access_token"
}
```

An active access token returns its client, user, audience, expiry, and granted scopes:

```json
{
  "active": true,
  "scope": "calendar.read calendar.write",
  "client_id": "<CLIENT_ID>",
  "token_type": "Bearer",
  "sub": "<USER_ID>",
  "aud": ["<PROJECT_API_AUDIENCE>"],
  "iss": "<ISSUER>",
  "exp": 1784056458,
  "iat": 1784052858,
  "jti": "<TOKEN_JTI>",
  "token_use": "access_token"
}
```

An expired, revoked, malformed, or otherwise inactive token returns:

```json
{
  "active": false
}
```

A confidential OAuth client can also authenticate with its client ID and client secret to introspect one of its own tokens. Public clients must not receive a project API key or client secret.

### Revoke a token

A third-party client should revoke its token when the user disconnects the integration or the client no longer needs access. Revoking either the access token or the refresh token deletes the whole token family, so both stop working. Families issued to the same client and user from other sign-ins are unaffected, so revoke each one you want to end.

This public-client example sends JSON. A confidential client also includes its `client_secret`.

```curl
curl -X POST 'https://<REGION>.cloud.appwrite.io/v1/oauth2/<PROJECT_ID>/revoke' \
  -H 'Content-Type: application/json' \
  -d '{
    "token": "<TOKEN>",
    "token_type_hint": "access_token",
    "client_id": "<CLIENT_ID>"
  }'
```
```hurl
POST https://<REGION>.cloud.appwrite.io/v1/oauth2/<PROJECT_ID>/revoke
Content-Type: application/json
{
    "token": "<TOKEN>",
    "token_type_hint": "access_token",
    "client_id": "<CLIENT_ID>"
}
```

A successful revocation returns `200 OK` with an empty body. The endpoint returns the same response for an unknown token so callers cannot use it to discover valid tokens.

### Sign out from the authorization server

Revocation disconnects one client without ending the user's browser session on your project. OpenID Connect logout ends that Appwrite session and revokes the tokens issued to the app identified by the ID token. This is useful for official apps that share your authorization server and need signing out of one app to require a fresh project sign-in elsewhere.

Other clients' existing OAuth tokens remain valid. Revoke those clients separately if your security policy requires it.

Before using logout, add the destination to the app's **Post-logout redirect URIs** in **Auth > OAuth2 server > Apps**. The URI in the logout request must match a registered value exactly.

Send the browser to the logout endpoint with the ID token previously issued to the app:

```curl
curl -G 'https://<REGION>.cloud.appwrite.io/v1/oauth2/<PROJECT_ID>/logout' \
  --data-urlencode 'id_token_hint=<ID_TOKEN>' \
  --data-urlencode 'post_logout_redirect_uri=https://client.example.com/signed-out' \
  --data-urlencode 'state=<STATE>'
```
```hurl
GET https://<REGION>.cloud.appwrite.io/v1/oauth2/<PROJECT_ID>/logout
[Query]
id_token_hint: <ID_TOKEN>
post_logout_redirect_uri: https://client.example.com/signed-out
state: <STATE>
```

Appwrite verifies the ID token's signature, issuer, client, and user. It accepts an expired ID token as a logout hint, but still requires the token to be validly signed. After deleting the current project session, the user's consent for that client, and every token family issued under it, Appwrite redirects to the registered URI and returns `state` unchanged. If no post-logout redirect URI is supplied, the endpoint returns `204 No Content`.

---

## OAuth 2 login

URL: 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.

**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).

### Configure OAuth 2 login

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

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.

**Javascript**

```client-web
import { Client, Account, OAuthProvider } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<PROJECT_ID>');                 // 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)
});
```

**Flutter**

For Android, add the following activity inside the `<application>` tag in your `AndroidManifest.xml`. Replace `<PROJECT_ID>` with your actual Appwrite project ID.

```xml
<!-- Add this inside the <application> tag, along side the existing <activity> tags -->
<activity android:exported="true" android:name="com.linusu.flutter_web_auth_2.CallbackActivity" >
  <intent-filter android:label="flutter_web_auth_2">
    <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>
```

No other configuration is required for iOS.

```client-flutter
import 'package:appwrite/appwrite.dart';
import 'package:appwrite/enums.dart';

final client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<PROJECT_ID>');               // Your project ID

final account = Account(client);

// Go to OAuth provider login page
await account.createOAuth2Session({
    provider: OAuthProvider.github,
    scopes: ['repo', 'user']
});
```

**Apple**

For Apple, add the following URL scheme to your `Info.plist`.

```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, 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<UIOpenURLContext>) {
    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://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<PROJECT_ID>")                // Your project ID

let account = Account(client)

// Go to OAuth provider login page
try await account.createOAuth2Session(
    provider: .github,
    scopes: ['repo', 'user']
)
```

**Android**

For Android, add the following activity inside the `<application>` tag in your `AndroidManifest.xml`.
Replace `<PROJECT_ID>` with your actual Appwrite project ID.

```xml
<!-- 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>
```
```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://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<PROJECT_ID>")                  // Your project ID

val account = Account(client)

// Go to OAuth provider login page
account.createOAuth2Session(
    provider = OAuthProvider.GITHUB,
    scopes = listOf('repo', 'user')
)
```

**React Native**

If using Expo, set the URL scheme to `appwrite-callback-<PROJECT_ID>` in your `app.json` file.

```json
{
  "expo": {
    "scheme": "appwrite-callback-<PROJECT_ID>"
  }
}
```

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://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<PROJECT_ID>');                          // 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-<PROJECT_ID>://'

// 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
```

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

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.

**Tip**

Replace `[SESSION_ID]` with either `"current"` to get or update the active session, or with a specific session ID.

```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://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<PROJECT_ID>');               // Your project ID

final account = Account(client);

final session = await getSession(
    sessionId : "<SESSION_ID>"
);

// Provider information
print(session.provider);
print(session.providerUid);
print(session.providerAccessToken);
```
```client-apple
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<PROJECT_ID>") // Your project ID

let account = Account(client)

let session = try await account.getSession(
    sessionId: "<SESSION_ID>"
)

// 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://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<PROJECT_ID>") // Your project ID

val account = Account(client)

val response = account.getSession(
    sessionId = "<SESSION_ID>"
)

// Provider information
print(session.provider);
print(session.providerUid);
print(session.providerAccessToken);
```

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

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.

```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://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<PROJECT_ID>');                // Your project ID

Account account = Account(client);

final result = await account.updateSession(
    sessionId: '<SESSION_ID>'
);
```
```client-apple
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<PROJECT_ID>") // Your project ID

let account = Account(client)

let session = try await account.updateSession(
    sessionId: "<SESSION_ID>"
);
```
```client-android-kotlin
import io.appwrite.Client
import io.appwrite.services.Account

val client = Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<PROJECT_ID>")                 // Your project ID

val account = Account(client)

val response = account.updateSession(
    sessionId = "<SESSION_ID>"
);
```

**GraphQL**

OAuth 2 is not available through the GraphQL API. You can use the REST API or any Client SDK instead.

---

## Phone (SMS) login

URL: https://appwrite.io/docs/products/auth/phone-sms

**Note**

OTPs are billed per message, with rates varying by country. See the [phone OTP rates](/docs/advanced/billing/phone-otp#rates) for more information.

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

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.

```client-web
import { Client, Account, ID } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

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://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

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://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>");

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://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>");

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
    }
}
```

### 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.

```client-web
import { Client, Account, ID } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

const account = new Account(client);

const session = await account.createSession({
    userId: userId,
    secret: '<SECRET>'
});
```

```client-flutter
import 'package:appwrite/appwrite.dart';

final client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

final account = Account(client);

final session = await account.createSession(
    userId: userId,
    secret: '<SECRET>'
);
```
```client-apple
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>");

let account = Account(client);

let session = try await account.createSession(
    userId: userId,
    secret: "<SECRET>"
);
```
```client-android-kotlin
import io.appwrite.Client
import io.appwrite.services.Account
import io.appwrite.ID

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>");

val account = Account(client);

val session = account.createSession(
    userId = userId,
    secret = "<SECRET>"
);
```

```graphql
mutation {
    accountCreateSession(userId: "<USER_ID>", secret: "<SECRET>") {
        _id
        userId
        provider
        expire
    }
}
```

After the secret is verified, a session will be created.

---

## Preferences

URL: 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

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.

```client-web
import { Client, Account } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<PROJECT_ID>');                 // 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://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<PROJECT_ID>');                 // 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://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<PROJECT_ID>")                  // 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://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<PROJECT_ID>")                  // 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
        }
    }
}
```

#### Get user preferences

Retrieve stored preferences with the `getPrefs` method.

```client-web
import { Client, Account } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<PROJECT_ID>');                 // 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://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<PROJECT_ID>');                 // 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://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<PROJECT_ID>")                  // Your project ID

val account = Account(client)

val prefs = account.getPrefs()
```
```client-apple
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<PROJECT_ID>")                  // Your project ID

let account = Account(client)

let prefs = try await account.getPrefs()
```
```graphql
query {
    accountGetPrefs {
        data
    }
}
```

### 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.

[Learn more about Appwrite Teams](/docs/products/auth/teams)

#### Update team preferences

Store team-wide settings using the `updatePrefs` method with a team ID.

```client-web
import { Client, Teams } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<PROJECT_ID>');                 // Your project ID

const teams = new Teams(client);

const promise = teams.updatePrefs({
    teamId: '<TEAM_ID>',
    prefs: {
        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://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<PROJECT_ID>');                 // Your project ID

final teams = Teams(client);

final team = await teams.updatePrefs(
    teamId: '<TEAM_ID>',
    prefs: {
        "theme": "corporate",
        "notificationsEnabled": true,
        "defaultView": "kanban"
    }
);
```
```client-apple
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<PROJECT_ID>")                  // Your project ID

let teams = Teams(client)

let team = try await teams.updatePrefs(
    teamId: "<TEAM_ID>",
    prefs: [
        "theme": "corporate",
        "notificationsEnabled": true,
        "defaultView": "kanban"
    ]
)
```
```client-android-kotlin
import io.appwrite.Client
import io.appwrite.services.Teams

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<PROJECT_ID>")                  // Your project ID

val teams = Teams(client)

val team = teams.updatePrefs(
    teamId = "<TEAM_ID>",
    prefs = mapOf(
        "theme" to "corporate",
        "notificationsEnabled" to true,
        "defaultView" to "kanban"
    )
)
```

#### Get team preferences

Fetch team preferences by passing a team ID to the `getPrefs` method.

```client-web
import { Client, Teams } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<PROJECT_ID>');                 // Your project ID

const teams = new Teams(client);

const promise = teams.getPrefs({
    teamId: '<TEAM_ID>'
});

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://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<PROJECT_ID>');                 // Your project ID

final teams = Teams(client);

final prefs = await teams.getPrefs(
    teamId: '<TEAM_ID>'
);
```
```client-apple
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<PROJECT_ID>")                  // Your project ID

let teams = Teams(client)

let prefs = try await teams.getPrefs(
    teamId: "<TEAM_ID>"
)
```
```client-android-kotlin
import io.appwrite.Client
import io.appwrite.services.Teams

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<PROJECT_ID>")                  // Your project ID

val teams = Teams(client)

val prefs = teams.getPrefs(
    teamId = "<TEAM_ID>"
)
```

### Browser 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');
```

**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).

---

## Presences

URL: https://appwrite.io/docs/products/auth/presences

Authentication tells you **who a user is**. Presences tell you **whether they are around right now**. The Appwrite **Presences API** records a live status for each signed-in user and broadcasts every change over [Realtime](/docs/apis/realtime), so your app can render online indicators, "viewing this page" cues, typing signals, and collaboration banners without writing any socket plumbing.

A presence is a short-lived record attached to a user. It carries a `userId`, a `status` string, an optional `metadata` JSON object for richer context, and an `expiresAt` timestamp that controls automatic cleanup. Presences are written by either the user's own session or a server SDK, and read by any client with the right [permissions](/docs/advanced/security/permissions).

### Set the user's presence

Once a user is signed in, upsert their presence on the events that should mark them as active, for example on app launch, on a window focus, or on a heartbeat timer. `userId` is filled in automatically from the session, so you only need to pass the fields that change.

```client-web
import { Client, Presences, ID, Permission, Role } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

const presences = new Presences(client);

const presence = await presences.upsert({
    presenceId: ID.unique(),
    status: 'online',
    metadata: { page: '/dashboard' },
    permissions: [
        Permission.read(Role.users())
    ]
});
```

```client-flutter
import 'package:appwrite/appwrite.dart';

final client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

final presences = Presences(client);

final presence = await presences.upsert(
    presenceId: ID.unique(),
    status: 'online',
    metadata: { 'page': '/dashboard' },
    permissions: [
        Permission.read(Role.users()),
    ],
);
```

```client-apple
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

let presences = Presences(client)

let presence = try await presences.upsert(
    presenceId: ID.unique(),
    status: "online",
    metadata: ["page": "/dashboard"],
    permissions: [
        Permission.read(Role.users())
    ]
)
```

```client-android-kotlin
import io.appwrite.Client
import io.appwrite.ID
import io.appwrite.Permission
import io.appwrite.Role
import io.appwrite.services.Presences

val client = Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

val presences = Presences(client)

val presence = presences.upsert(
    presenceId = ID.unique(),
    status = "online",
    metadata = mapOf("page" to "/dashboard"),
    permissions = listOf(
        Permission.read(Role.users())
    )
)
```

Store the returned `$id` somewhere your client can reach again (for example a context object, a state store, or `localStorage`) so subsequent updates reuse the same record instead of creating a new one every time. The same call updates the existing presence in place when called with an existing `presenceId`.

### Update on activity changes

Most apps update presence on a few specific signals:

- **Window focus and blur** to flip between `online` and `away`.
- **Route changes** to update the `page` field in `metadata` and show "viewing this page".
- **Typing events** in a chat or comment box to set `status: 'typing'` and clear it when the user stops.
- **A heartbeat timer** (for example every 30 seconds) to push the `expiresAt` forward and keep the record alive while the user is active.

```client-web
async function setStatus(status, metadata = {}) {
    await presences.upsert({
        presenceId,
        status,
        metadata,
        permissions: [
            Permission.read(Role.users())
        ]
    });
}

window.addEventListener('focus', () => setStatus('online'));
window.addEventListener('blur',  () => setStatus('away'));
```

There is no fixed heartbeat interval enforced by the server, so pick whichever cadence matches your UX. Anything shorter than the `expiresAt` you choose will keep the presence alive without gaps.

### Show other users' presence

List the presences the current user can read to paint the initial "online now" view, a list of viewers on a page, or a typing dot in a chat. The list call honors the same [permissions](/docs/advanced/security/permissions) you set on each record, so each client only sees the statuses it is allowed to render.

```client-web
import { Client, Presences } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

const presences = new Presences(client);

const result = await presences.list();

const onlineUsers = new Map(
    result.presences.map(presence => [presence.userId, presence])
);
```

```client-flutter
import 'package:appwrite/appwrite.dart';

final client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

final presences = Presences(client);

final result = await presences.list();

final onlineUsers = {
    for (final presence in result.presences) presence.userId: presence
};
```

```client-apple
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

let presences = Presences(client)

let result = try await presences.list()

var onlineUsers: [String: Any] = [:]
for presence in result.presences {
    onlineUsers[presence.userId] = presence
}
```

```client-android-kotlin
import io.appwrite.Client
import io.appwrite.services.Presences

val client = Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

val presences = Presences(client)

val result = presences.list()

val onlineUsers = result.presences
    .associateBy { it.userId }
    .toMutableMap()
```

Then subscribe to the global `presences` channel to keep that snapshot live. Apply the same patch to the same `onlineUsers` map on every event, add or replace on upsert or update, remove on delete.

```client-web
import { Client, Realtime, Channel } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

const realtime = new Realtime(client);

await realtime.subscribe(Channel.presences(), response => {
    const presence = response.payload;
    if (response.events.includes('presences.*.delete')) {
        onlineUsers.delete(presence.userId);
    } else {
        onlineUsers.set(presence.userId, presence);
    }
});
```

```client-flutter
import 'package:appwrite/appwrite.dart';

final client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

final realtime = Realtime(client);

final subscription = realtime.subscribe([Channel.presences()]);

subscription.stream.listen((response) {
    final presence = response.payload;
    if (response.events.contains('presences.*.delete')) {
        onlineUsers.remove(presence['userId']);
    } else {
        onlineUsers[presence['userId']] = presence;
    }
});
```

```client-apple
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

let realtime = Realtime(client)

let subscription = realtime.subscribe(channels: [Channel.presences()]) { response in
    guard let payload = response.payload as? [String: Any],
          let userId = payload["userId"] as? String else { return }

    if (response.events?.contains("presences.*.delete") == true) {
        onlineUsers.removeValue(forKey: userId)
    } else {
        onlineUsers[userId] = payload
    }
}
```

```client-android-kotlin
import io.appwrite.Channel
import io.appwrite.Client
import io.appwrite.services.Realtime

val client = Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

val realtime = Realtime(client)

realtime.subscribe(Channel.presences()) { response ->
    val payload = response.payload as? Map<String, Any?> ?: return@subscribe
    val userId = payload["userId"] as? String ?: return@subscribe

    if (response.events.contains("presences.*.delete")) {
        onlineUsers.remove(userId)
    } else {
        onlineUsers[userId] = payload
    }
}
```

### Clear presence on sign out

A presence outlives the session that created it by default, so when a user signs out you should delete their presence record explicitly. This emits a `delete` event on the presence channels, so every subscribed client sees the user go offline immediately instead of waiting for the record to expire.

```client-web
await presences.delete({ presenceId });
await account.deleteSession({ sessionId: 'current' });
```

```client-flutter
await presences.delete(presenceId: presenceId);
await account.deleteSession(sessionId: 'current');
```

```client-apple
try await presences.delete(presenceId: presenceId)
try await account.deleteSession(sessionId: "current")
```

```client-android-kotlin
presences.delete(presenceId = presenceId)
account.deleteSession(sessionId = "current")
```

If a user closes the browser tab or loses connection without signing out, the record will still disappear on its own when `expiresAt` is reached, which is why short heartbeat windows work well for true "live" indicators.

### Scoping who can see a presence

Presences use the standard Appwrite [permissions system](/docs/advanced/security/permissions). Set read permissions on each record to match how your app already groups users:

- `Role.users()` for any signed-in user, useful for a global "X users online" counter.
- `Role.team('<TEAM_ID>')` for collaboration features that should only show statuses to teammates.
- `Role.user('<USER_ID>')` for one-to-one features such as DMs, where only the recipient should see the sender's typing state.

Pass a `permissions` array to `upsert()` to attach roles to a presence. For example, to share a typing indicator only with the recipient of a DM:

```client-web
import { Client, Presences, ID, Permission, Role } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

const presences = new Presences(client);

const presence = await presences.upsert({
    presenceId: ID.unique(),
    status: 'typing',
    permissions: [
        Permission.read(Role.user('<RECIPIENT_USER_ID>'))
    ]
});
```

```client-flutter
import 'package:appwrite/appwrite.dart';

final client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

final presences = Presences(client);

final presence = await presences.upsert(
    presenceId: ID.unique(),
    status: 'typing',
    permissions: [
        Permission.read(Role.user('<RECIPIENT_USER_ID>')),
    ],
);
```

```client-apple
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

let presences = Presences(client)

let presence = try await presences.upsert(
    presenceId: ID.unique(),
    status: "typing",
    permissions: [
        Permission.read(Role.user("<RECIPIENT_USER_ID>"))
    ]
)
```

```client-android-kotlin
import io.appwrite.Client
import io.appwrite.ID
import io.appwrite.Permission
import io.appwrite.Role
import io.appwrite.services.Presences

val client = Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

val presences = Presences(client)

val presence = presences.upsert(
    presenceId = ID.unique(),
    status = "typing",
    permissions = listOf(
        Permission.read(Role.user("<RECIPIENT_USER_ID>"))
    )
)
```

Presence read and subscribe events both honour these permissions, so a user will never receive a status update for a presence they could not have read with a direct GET.

If you do not pass a `permissions` array when upserting a presence, Appwrite defaults to giving read access only to the user who created it, so no other client can subscribe to it. To share a presence more broadly, you must set permissions explicitly.

### Where to next

- [Realtime: Presences](/docs/apis/realtime/presences). The full concept reference, including channel patterns, expiry behaviour, and server-side usage.
- [Realtime channels](/docs/apis/realtime/channels). See how `presences` fits alongside `account`, `teams`, and `rows`.
- [Permissions](/docs/advanced/security/permissions). Refresher on how `Role.team()` and `Role.user()` work.

---

## Start with Authentication

URL: 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.

#### 1. Signup

You can use the Appwrite [Client SDKs](/docs/sdks#client) to create an account using email and password.

```client-web
import { Client, Account, ID } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<PROJECT_ID>');                 // 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://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<PROJECT_ID>');                 // 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://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<PROJECT_ID>")                  // 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://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<PROJECT_ID>")                  // 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://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<PROJECT_ID>');                 // Your project ID

const account = new Account(client);

const user = await account.create({
    userId: ID.unique(), 
    email: 'email@example.com', 
    password: 'password'
});
```

#### 2. 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.

```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
});
```

#### 3. 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.

```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
}
```

#### 4. 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.

**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.

**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: <Protected />,
    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: <Login />,
  },
]);

export default router;
```

**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<Models.Preferences>,
  }),
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')
})
```

**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<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
    if (route.routeConfig?.path === "protected") {
      return this.checkLogin();
    }
    return of(true);
  }

  private checkLogin(): Observable<boolean | UrlTree> {
    return from(account.get()).pipe(
      map(() => true),
      catchError(() => of(this.router.createUrlTree(['/login'])))
    );
  }
}
```

**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');
	}
}
```

**Mobile and native**

With mobile apps, you can apply similar logic to check the authentication state before displaying a screen or view.

**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<void> 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<Account>().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()),
    )
  ],  
);
```

**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://<REGION>.cloud.appwrite.io/v1")
            .setProject("<PROJECT_ID>")
        
        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
}
```

**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://<REGION>.cloud.appwrite.io/v1"
    private const val PROJECT_ID = "<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<Map<String, Any>>? {
        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<User<Map<String, Any>>?>(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)
        }
    }
}
```

**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 (
    <UserProvider>
        <Router />
    </UserProvider >
  );
}
```

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 (
        <NavigationContainer>
            <Stack.Navigator>
                {user.current == null ? (
                    <Stack.Screen
                        name="Login"
                        component={LoginScreen}
                        options={{ title: 'Login' }}
                    />
                ) : (
                    <Stack.Screen
                        name="Protected"
                        component={ProtectedSCreen}
                        options={{ title: 'Protected' }}
                    />
                )}

            </Stack.Navigator>
        </NavigationContainer>
    );
}
```

---

## React library

URL: https://appwrite.io/docs/products/auth/react

The Appwrite React library is a thin layer over the [Web SDK](/docs/sdks) that exposes a provider and a small set of hooks for authentication operations and current user state. It works in both client-rendered React apps and server-rendered apps on Next.js and TanStack Start.

### Why use it

- **SSR auth without boilerplate.** Drop in one handler route per framework and skip the days normally spent writing cookie logic, session sync, and server/client hydration.
- **Consistent user state across server and client.** Server components, loaders, and client hooks return the same authenticated user, removing the need to reconcile auth state across the render boundary.
- **Server-side access when you need it.** Read the current user, create per-request session clients, or reach for an admin client without hand-wiring the Node SDK on every route.

### Install

Install the library along with the **Appwrite Web SDK** and **TanStack Query** packages.

```sh
npm install @appwrite.io/react appwrite @tanstack/react-query
```

For SSR apps on Next.js or TanStack Start, also install `node-appwrite`. The SSR handlers create sessions with a server API key.

```sh
npm install @appwrite.io/react appwrite node-appwrite @tanstack/react-query
```

### Pick your framework

- [Client-side React](/docs/quick-starts/react): For Vite and other client-rendered React apps.
- [Next.js](/docs/quick-starts/nextjs): For server-rendered apps on the Next.js App Router.
- [TanStack Start](/docs/quick-starts/tanstack-start): For server-rendered apps on TanStack Start.

### The provider

Every app starts with an `AppwriteProvider`. In a CSR app, only `endpoint` and `projectId` are required.

```tsx
import { AppwriteProvider } from "@appwrite.io/react";

<AppwriteProvider
  endpoint={import.meta.env.VITE_APPWRITE_ENDPOINT}
  projectId={import.meta.env.VITE_APPWRITE_PROJECT_ID}
>
  <App />
</AppwriteProvider>;
```

In an SSR app, pass an `ssr` prop with the session secret read from the server-side cookie and the path your handlers are mounted at.

```tsx
<AppwriteProvider
  endpoint={endpoint}
  projectId={projectId}
  ssr={{ session, basePath: "/api/appwrite" }}
>
  {children}
</AppwriteProvider>
```

When `ssr` is set, sign-in, sign-up, and sign-out mutations route through your handler. The underlying Web SDK is hydrated with the session secret on first render, so authenticated reads do not need a round trip.

### Hooks

The library exports one combined hook and four focused mutation hooks. All hooks share the same TanStack Query cache, so updating user state from any of them reflects everywhere.

#### useAuth

The primary hook for most apps. It bundles the current user, loading and error state, and handles for sign-in, sign-up, and sign-out into a single return value.

```tsx
import { useAuth } from "@appwrite.io/react";

const { user, isLoading, error, refresh, signIn, signUp, signOut } = useAuth();
```

| Field | Type | Description |
| --- | --- | --- |
| `user` | `Models.User \| null \| undefined` | The current user, or `null` if signed out. `undefined` while loading. |
| `isLoading` | `boolean` | True while the initial user fetch is in flight. |
| `error` | `Error \| null` | First error from the user query or any mutation. |
| `refresh` | `() => Promise<User \| null>` | Refetch the current user and return the resolved value. |
| `signIn` | `ReturnType<typeof useSignIn>` | Sign-in mutation handle. |
| `signUp` | `ReturnType<typeof useSignUp>` | Sign-up mutation handle. |
| `signOut` | `ReturnType<typeof useSignOut>` | Sign-out mutation handle. |

#### useUser

Read-only access to the current authenticated user. Use it in components that only need to display user state and do not trigger auth mutations.

```tsx
import { useUser } from "@appwrite.io/react";

const { user, isLoading, error, refresh } = useUser();
```

| Field | Type | Description |
| --- | --- | --- |
| `user` | `Models.User \| null \| undefined` | The current user, or `null` if signed out. `undefined` while loading. |
| `isLoading` | `boolean` | True while the user is being fetched. |
| `error` | `Error \| null` | Error from the fetch, if one occurred. |
| `refresh` | `() => Promise<User \| null>` | Refetch the current user and return the resolved value. |

#### useSignIn

Email and password and OAuth sign-in, with pending and error state scoped to the email and password flow.

```tsx
const { emailPassword, oAuth, isPending, error } = useSignIn();
```

| Field | Type | Description |
| --- | --- | --- |
| `emailPassword` | `function` | Trigger an email and password sign-in. |
| `oAuth` | `function` | Trigger an OAuth sign-in. |
| `isPending` | `boolean` | True while an email and password sign-in is in flight. |
| `error` | `Error \| null` | Error from the last email and password sign-in attempt. |

**Email and password**

```tsx
emailPassword({ email, password, onSuccess, onError });
```

| Parameter | Required | Description |
| --- | --- | --- |
| `email` | Yes | User's email address. |
| `password` | Yes | User's password. |
| `onSuccess` | No | Callback invoked with the signed-in user. |
| `onError` | No | Callback invoked with the thrown error. |

**OAuth**

```tsx
oAuth({ provider: OAuthProvider.Google, successUrl, failureUrl, scopes });
```

| Parameter | Required | Description |
| --- | --- | --- |
| `provider` | Yes | An `OAuthProvider` value re-exported by the library. |
| `successUrl` | No | URL to redirect to after successful login (CSR only). |
| `failureUrl` | No | URL to redirect to after a failed attempt. |
| `scopes` | No | Provider-specific OAuth scopes to request. |
| `onError` | No | Callback invoked if the request fails before redirect. |

In SSR mode, the success URL is fixed to the handler callback. The final redirect after that callback is controlled by the `redirects.success` config on the handler.

#### useSignUp

Create an account and start a session in one call. The hook also surfaces pending and error state for the in-flight request.

```tsx
const { emailPassword, isPending, error } = useSignUp();
```

| Field | Type | Description |
| --- | --- | --- |
| `emailPassword` | `function` | Create an account with email and password, then sign the user in. |
| `isPending` | `boolean` | True while the sign-up is in flight. |
| `error` | `Error \| null` | Error from the last sign-up attempt. |

##### Email and password

```tsx
emailPassword({ email, password, name, userId, onSuccess, onError });
```

| Parameter | Required | Description |
| --- | --- | --- |
| `email` | Yes | New user's email address. |
| `password` | Yes | New user's password. |
| `name` | No | Display name for the user. |
| `userId` | No | Custom user ID. Defaults to `ID.unique()`. |
| `onSuccess` | No | Callback invoked with the created user. |
| `onError` | No | Callback invoked with the thrown error. |

#### useSignOut

End the current session, drop cached auth queries, and reset the local Web SDK client.

```tsx
const { signOut, isPending, error } = useSignOut();
```

| Field | Type | Description |
| --- | --- | --- |
| `signOut` | `function` | End the current user's session. |
| `isPending` | `boolean` | True while the sign-out is in flight. |
| `error` | `Error \| null` | Error from the last sign-out attempt. |

##### Sign out

```tsx
signOut({ onSuccess, onError });
```

| Parameter | Required | Description |
| --- | --- | --- |
| `onSuccess` | No | Callback invoked after the session is destroyed. |
| `onError` | No | Callback invoked with the thrown error. |

After a successful sign-out, all cached auth queries are dropped and the local Web SDK client is reset. On Next.js, call `router.refresh()` from `onSuccess` to re-render server components with the cleared cookie. On TanStack Start, call `router.invalidate()`.

#### useAppwrite

Escape hatch to the underlying provider context. Use it when you need direct access to a Web SDK service the higher-level hooks do not wrap (`tablesDB`, `storage`, `messaging`, `realtime`, and so on).

```tsx
import { useAppwrite } from "@appwrite.io/react";

const { client, account, tablesDB, storage, teams, ssr } = useAppwrite();
```

| Field | Type | Description |
| --- | --- | --- |
| `client` | `Client` | The Appwrite Web SDK `Client` configured by the provider. |
| `account` | `Account` | Web SDK `Account` service. |
| `avatars` | `Avatars` | Web SDK `Avatars` service. |
| `functions` | `Functions` | Web SDK `Functions` service. |
| `graphql` | `Graphql` | Web SDK `Graphql` service. |
| `locale` | `Locale` | Web SDK `Locale` service. |
| `messaging` | `Messaging` | Web SDK `Messaging` service. |
| `presences` | `Presences` | Web SDK `Presences` service. |
| `realtime` | `Realtime` | Web SDK `Realtime` service. |
| `storage` | `Storage` | Web SDK `Storage` service. |
| `tablesDB` | `TablesDB` | Web SDK `TablesDB` service. |
| `teams` | `Teams` | Web SDK `Teams` service. |
| `authenticated` | `boolean` | Whether the provider currently considers a user signed in. |
| `setAuthenticated` | `Dispatch<SetStateAction<boolean>>` | Update the authenticated flag manually. |
| `ssr.enabled` | `boolean` | Whether the provider was mounted with the `ssr` prop. |
| `ssr.basePath` | `string` | Mount path of the handler routes. |
| `ssr.session` | `string \| null` | Session secret read from the server cookie. |

Throws if called outside an `AppwriteProvider`.

### Server helpers

The library ships server entrypoints scoped per framework. Each helper exposes the same surface, differing only in how it reads the request cookie.

```ts
// Next.js (App Router)
import { createNextServerHelpers } from "@appwrite.io/react/server/next";

// TanStack Start
import { createTanStackServerHelpers } from "@appwrite.io/react/server/tanstack";
```

Both return an object with:

| Method | Returns | Use it for |
| --- | --- | --- |
| `readSessionCookie()` | `string \| undefined` (sync on TanStack, async on Next.js) | The session secret. Pass it into `AppwriteProvider`'s `ssr.session` prop. |
| `getLoggedInUser()` | `Models.User \| null` | The current user, fetched via the cookie. Returns `null` on 401. |
| `getSession()` | `Models.Session \| null` | The current session row. |
| `createSessionClient()` | `NodeSessionServer \| null` | A `node-appwrite` client authenticated as the cookie's user, or `null` if no cookie. |
| `createAdminClient()` | `AdminServer` | A `node-appwrite` admin client authenticated with the API key passed to the helper. Only available when `apiKey` is set in the helper config. |

#### Session client

For per-request operations scoped to the current user (reading their data, calling APIs on their behalf), call `createSessionClient` from the framework helper. It returns a `node-appwrite` client already authenticated with the session cookie, ready to use in server components, loaders, and server functions.

```ts
import { createNextServerHelpers } from "@appwrite.io/react/server/next";

const helpers = createNextServerHelpers({
  endpoint: process.env.NEXT_PUBLIC_APPWRITE_ENDPOINT!,
  projectId: process.env.NEXT_PUBLIC_APPWRITE_PROJECT_ID!,
});

const session = await helpers.createSessionClient();

if (session) {
  const user = await session.account.get();
}
```

#### Admin client

For privileged operations (creating users, listing sessions, managing teams), call `createAdminClient` on the framework helper. Pass `apiKey` in the helper config to enable it, then call the method anywhere on the server. The returned object exposes every `node-appwrite` service: `account`, `users`, `tablesDB`, `storage`, `teams`, `functions`, `messaging`, and more.

```ts
import { createNextServerHelpers } from "@appwrite.io/react/server/next";

const helpers = createNextServerHelpers({
  endpoint: process.env.NEXT_PUBLIC_APPWRITE_ENDPOINT!,
  projectId: process.env.NEXT_PUBLIC_APPWRITE_PROJECT_ID!,
  apiKey: process.env.APPWRITE_API_KEY!,
});

const admin = helpers.createAdminClient();
const users = await admin.users.list();
```

If you would rather not pass the API key into the helper, the library also exports a standalone `createAdminClient` from `@appwrite.io/react/server` that takes the key directly.

```ts
import { createAdminClient } from "@appwrite.io/react/server";

const admin = createAdminClient({
  endpoint: process.env.NEXT_PUBLIC_APPWRITE_ENDPOINT!,
  projectId: process.env.NEXT_PUBLIC_APPWRITE_PROJECT_ID!,
  apiKey: process.env.APPWRITE_API_KEY!,
});

const users = await admin.users.list();
```

Never import any `@appwrite.io/react/server/*` module from client code: the entrypoints are marked `server-only` and will throw if bundled into the browser.

### Handler routes

The SSR mutations (`sign-in`, `sign-up`, `sign-out`, `oauth/callback`, `oauth/failure`) live behind a handler route you mount once per app. The handler reads the request, talks to Appwrite with the server API key, and writes the session cookie back.

**Next.js**

```ts
// app/api/appwrite/[...appwrite]/route.ts
import { createAppwriteHandlers } from "@appwrite.io/react/handlers/next";

export const { GET, POST } = createAppwriteHandlers({
  endpoint: process.env.NEXT_PUBLIC_APPWRITE_ENDPOINT!,
  projectId: process.env.NEXT_PUBLIC_APPWRITE_PROJECT_ID!,
  apiKey: process.env.APPWRITE_API_KEY!,
  basePath: "/api/appwrite",
});
```

**TanStack Start**

```ts
// src/routes/api/appwrite/$.ts
import { createFileRoute } from "@tanstack/react-router";
import { createAppwriteHandlers } from "@appwrite.io/react/handlers/tanstack";

export const Route = createFileRoute("/api/appwrite/$")({
  server: {
    handlers: createAppwriteHandlers({
      endpoint: import.meta.env.VITE_APPWRITE_ENDPOINT,
      projectId: import.meta.env.VITE_APPWRITE_PROJECT_ID,
      apiKey: process.env.APPWRITE_API_KEY!,
      basePath: "/api/appwrite",
    }),
  },
});
```

#### Cookie and redirect options

Customize how sessions persist in the browser and where users land after an OAuth round trip.

```ts
createAppwriteHandlers({
  endpoint: process.env.NEXT_PUBLIC_APPWRITE_ENDPOINT!,
  projectId: process.env.NEXT_PUBLIC_APPWRITE_PROJECT_ID!,
  apiKey: process.env.APPWRITE_API_KEY!,
  basePath: "/api/appwrite",
  cookieName: "my-app-session",
  cookieOptions: {
    sameSite: "strict",
    domain: ".example.com",
  },
  redirects: {
    success: "/dashboard",
    failure: "/login?error=oauth",
  },
});
```

| Option | Default | Description |
| --- | --- | --- |
| `cookieName` | `appwrite-session-<projectId>` | Name of the HTTP-only session cookie. |
| `cookieOptions.secure` | `true` | Set the `Secure` flag on the cookie. Modern browsers accept this on `localhost` over HTTP. |
| `cookieOptions.sameSite` | `"lax"` | SameSite policy. |
| `cookieOptions.httpOnly` | `true` | HttpOnly flag. |
| `cookieOptions.path` | `"/"` | Cookie path. |
| `cookieOptions.domain` | unset | Restrict cookie to a domain. |
| `redirects.success` | `"/"` | URL to redirect to after a successful OAuth login. |
| `redirects.failure` | `"/"` | URL to redirect to after a failed OAuth attempt. |

### Required scopes

Your server API key needs three scopes for the SSR handler to function:

- `users.write` (sign-up creates a user)
- `users.read` (sign-up confirms the created user)
- `sessions.write` (sign-in, sign-out, and OAuth callbacks create or delete sessions)

### Next steps

- [Start with React](/docs/quick-starts/react)
- [Start with Next.js](/docs/quick-starts/nextjs)
- [Start with TanStack Start](/docs/quick-starts/tanstack-start)
- [Account API reference](/docs/references/cloud/client-web/account)

---

## Security

URL: https://appwrite.io/docs/products/auth/security

Appwrite provides many security features to keep both your Appwrite project and your user's information secure.

### Persistence

Appwrite handles the persistence of the session in a consistent way across SDKs. After authenticating with an SDK, the SDK will persist the session so that the user will not need to log in again the next time they open the app. The mechanism for persistence depends on the SDK.

**Best Practice**

Only keep user sessions active as long as needed and maintain exactly **one** instance of the Client SDK in your app to avoid conflicting session data.

| | Framework | Storage method |
| :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------: | :--------------------------------------------------------------------------------------------------: |
| ![Javascript logo](/images/platforms/light/javascript.svg) | Web | Uses a secure session cookie and falls back to local storage when a session cookie is not available. |
| ![Javascript logo](/images/platforms/light/flutter.svg) | Flutter | Uses a session cookie stored in Application Documents through the **path_provider** package. |
| ![Javascript logo](/images/platforms/light/apple.svg) | Apple | Uses a session cookie stored in **UserDefaults**. |
| ![Javascript logo](/images/platforms/light/android.svg) | Android | Uses a session cookie stored in **SharedPreferences**. |

### Session limits

In Appwrite versions 1.2 and above, you can limit the number of active sessions created per user to prevent the accumulation of unused but active sessions. New sessions created by the same user past the session limit delete the oldest session.

You can change the session limit in the **Security** tab of the Auth Service in your Appwrite Console. The default session limit is 10 with a maximum configurable limit of 100.

### Permissions

Security is very important to protect users' data and privacy.
Appwrite uses a [permissions model](/docs/advanced/security/permissions) coupled with user sessions to ensure users need correct permissions to access resources.
With all Appwrite services, including databases and storage, access is granted at the table, bucket, row, or file level.
These permissions are enforced for client SDKs and server SDKs when using JWT, but are ignored when using a server SDK with an API key.

### Password strength

Password strength lets you set the minimum requirements a password must meet when a user creates an account or changes their password. Enforcing these rules makes passwords harder to guess and brute-force.

You can configure two kinds of requirements:

- **Minimum length**: the smallest number of characters a password is allowed to have.
- **Character requirements**: require any combination of an uppercase letter, a lowercase letter, a number, and a special character. Each requirement is an independent toggle, so you can enforce as few or as many as your app needs.

Passwords that don't meet the configured requirements are rejected when a user signs up and whenever they change their password. To configure password strength, navigate to **Auth** > **Security** > **Password strength**, set the minimum length and character requirements, then click **Update**.

### Password history

Password history prevents users from reusing recent passwords. This protects user accounts from security risks by enforcing a new password every time it's changed.

Password history can be enabled in the Auth service's **Security** tab on the Appwrite Console. You can choose how many previous passwords to remember, up to a maximum of 20, and block users from reusing them.

### Password dictionary

Password dictionary protects users from using bad passwords. It compares the user's password to the [10,000 most common passwords](https://github.com/danielmiessler/SecLists/blob/master/Passwords/Common-Credentials/10k-most-common.txt) and throws an error if there's a match. Together with [rate limits](/docs/advanced/security/rate-limits), password dictionary will significantly reduce the chance of a malicious actor guessing user passwords.

Password dictionary can be enabled in the Auth service's **Security** tab on the Appwrite Console.

### Password hashing

Appwrite protects passwords by using the [Argon2](https://github.com/P-H-C/phc-winner-argon2) password-hashing algorithm.

Argon 2 is a resilient and secure password hashing algorithm that is also the winner of the [Password Hashing Competition](https://www.password-hashing.net/).

Appwrite combines Argon 2 with the use of techniques such as salting, adjustable work factors, and memory hardness to securely handle passwords.

If an user is imported into Appwrite with hash differnt than Argon2, the password will be re-hashed on first successful user's sign in. This ensures all passwords are stored as securely as possible.

### Personal data

Encourage passwords that are hard to guess by disallowing users to pick passwords that contain personal data.
Personal data includes the user's name, email, and phone number.

Disallowing personal data can be enabled in the Auth service's **Security** tab on the Appwrite Console.

### Email policies

Email policies let you restrict which email addresses can sign up for your project. You can independently block free email providers, aliased addresses, and disposable email services to keep throwaway accounts, signup spam, and bot registrations out of your user base. Policies run at sign-up and on email updates, and existing users can still sign in even if their address would not pass the current policy.

Email policies can be enabled in the Auth service's **Security** tab on the Appwrite Console, or programmatically through the Project service. Learn more in the [Email policies](/docs/products/auth/email-policies) docs.

### Session alerts

Enable email alerts for your users so that whenever a new session is created for their account, they will be alerted with details about the sign-in. This helps users quickly spot unauthorized access and take action to secure their account.

#### When alerts are not sent

Session alerts are intentionally skipped in a few situations to avoid redundant or confusing emails:

- **First session after sign-up**: the very first sign-in a user makes after creating their account does not trigger an alert. A brand-new account doesn't yet hold anything worthy of protection, so alerting at this stage adds no real security value. It also prevents a double-email situation in flows where your project may already be sending a welcome or verification email.
- **[Magic URL](/docs/products/auth/magic-url), [Email OTP](/docs/products/auth/email-otp), and [OAuth2](/docs/products/auth/oauth2) sign-ins**: these authentication methods already verify the user's access to the sign-in channel (their inbox or identity provider), so no additional alert is needed.
- **No email address on file**: users who have not set an email address on their account will not receive alerts.

To toggle session alerts, navigate to **Auth** > **Security** > **Session alerts**.

### 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

### Mock phone numbers

Creating and using mock phone numbers allows users to test SMS authentication without needing an actual phone number. This can be useful for testing edge cases where a user doesn't have a phone number but needs to sign in to your application using SMS.

To create a mock phone number, navigate to **Auth** > **Security** > Mock Phone Numbers. After defining a mock phone number, you need to define a specific OTP code that will be used for SMS sign-in instead of the SMS secret code sent to a real phone number.

---

## SSR login

URL: 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

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.

![CSR vs SSR flow diagram](/images/docs/auth/ssr/ssr.avif)

### Initialize clients

**Server SDK required**

Server-side rendering requires a [Server SDK](/docs/sdks#server) instead of a Client SDK.

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 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).

To initialize the admin client, we'll need to first [generate an API key](/docs/partners/project/api-keys#create-api-key).
The API key should have the following scope in order to perform authentication:

| Category | Required scopes | Purpose |
|-----------|---------------------|---------|
| Sessions | `sessions.write` | Allows API key to create, update, and delete sessions. |

```server-nodejs
import { Client } from "node-appwrite"; // Using the server SDK

const adminClient = 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
```
```php
use Appwrite\Client;
use Appwrite\Services\Account;

$adminClient = (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


```
```python
from appwrite.client import Client

admin_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
            )


```
```rust
use appwrite::Client;

let admin_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
```

It is important to use an API key, as this will allow your server requests to bypass [rate limits](/docs/advanced/security/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

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_<PROJECT_ID>` as the [cookie name](/docs/apis/rest#client-integration) and a [custom domain](/docs/products/network/custom-domains) for your Appwrite endpoint if you want the session to work client-side as well.

```server-nodejs
const sessionClient = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<PROJECT_ID>');            // Your project ID

const session = req.cookies['a_session_<PROJECT_ID>']; // Get the session cookie from the request
if (session) {
    sessionClient.setSession(session);
}
```
```php
$sessionClient = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    ->setProject('<PROJECT_ID>');            // Your project ID

$session = $_COOKIE['a_session_<PROJECT_ID>']; // 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://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint
                  .set_project('<PROJECT_ID>') # Your project ID
                )

### Get the session cookie from the request
session = request.cookies.get('a_session_<PROJECT_ID>')
if session:
    session_client.set_session(session)

```
```rust
use appwrite::Client;

let session_client = Client::new()
    .set_endpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .set_project("<PROJECT_ID>");            // Your project ID

let session = req.cookie("a_session_<PROJECT_ID>"); // Get the session cookie from the request
if let Some(session) = session {
    session_client.set_session(session);
}
```

### Creating email/password 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.

```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_<PROJECT_ID>', 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
<?php
// Initialize admin client here
// ...

// Get email and password from request
$email = $_POST['email'];
$password = $_POST['password'];

try {
    $account = new Account($adminClient);

    // Create the session using the Appwrite client
    $session = $account->createEmailPasswordSession($email, $password);

    // Set the session cookie
    setcookie('a_session_<PROJECT_ID>', $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=email, password=password)
        resp = make_response(jsonify({'success': True}))

        # Set the session cookie
        resp.set_cookie('a_session_<PROJECT_ID>',
                        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
```
```rust
use appwrite::Client;
use appwrite::services::account::Account;

// Initialize admin client here
// ...

async fn login(admin_client: &Client, email: &str, password: &str) -> Result<(), Box<dyn std::error::Error>> {
    let account = Account::new(admin_client);

    // Create the session using the Appwrite client
    let session = account.create_email_password_session(
        email,
        password,
    ).await?;

    // Set the session cookie using the session secret
    // Use your framework's cookie API to set:
    // name: "a_session_<PROJECT_ID>"
    // value: session.secret
    // httpOnly: true, secure: true, sameSite: strict
    // expires: session.expire

    Ok(())
}
```

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

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.

```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_<PROJECT_ID>'];

    // 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
<?php
// Initialize the session client here

// First, read the session cookie from the request
$session = $_COOKIE['a_session_<PROJECT_ID>'];

// 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_<PROJECT_ID>')

    # 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

```
```rust
use appwrite::Client;
use appwrite::services::account::Account;

// Initialize the session client here

// First, read the session cookie from the request
// This depends on your HTTP framework (e.g. actix-web, axum, rocket)
let session = req.cookie("a_session_<PROJECT_ID>");

// If the session cookie is not present, return an error
if session.is_none() {
    // return 401 Unauthorized
}

// Pass the session cookie to the Appwrite client
let session_client = Client::new()
    .set_endpoint("https://<REGION>.cloud.appwrite.io/v1")
    .set_project("<PROJECT_ID>");
session_client.set_session(session.unwrap());

// Now, you can make authenticated requests to the Appwrite API
let account = Account::new(&session_client);
let user = account.get().await?;
```

### Rate limits
Unauthenticated requests are subject to [rate limits](/docs/advanced/security/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
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:

| 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. |

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.

```server-nodejs
const sdk = require('node-appwrite');

// Init SDK
const client = new sdk.Client();

const account = new sdk.Account(client);

client
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<PROJECT_ID>') // Your project ID
;

const promise = account.createAnonymousSession();

promise.then(function (response) {
    console.log(response);
}, function (error) {
    console.log(error);
});
```
```php
<?php

use Appwrite\Client;
use Appwrite\Services\Account;

$client = new Client();

$client
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    ->setProject('<PROJECT_ID>') // 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://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint
            .set_project('<PROJECT_ID>') # Your project ID
        )

account = Account(client)

result = account.create_anonymous_session()
```
```rust
use appwrite::Client;
use appwrite::services::account::Account;

let client = Client::new()
    .set_endpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .set_project("<PROJECT_ID>"); // Your project ID

let account = Account::new(&client);

let result = account.create_anonymous_session().await?;
```

### 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.

```server-nodejs
client.setForwardedUserAgent(req.headers['user-agent']);
```
```php
<?php
$client->setForwardedUserAgent($_SERVER['HTTP_USER_AGENT']);
```
```python
client.set_forwarded_user_agent(request.headers.get('user-agent'))
```
```rust
// This depends on your HTTP framework (e.g. actix-web, axum, rocket)
let user_agent = req.header("user-agent");
client.add_header("x-forwarded-user-agent", user_agent);
```

### 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.

```server-nodejs
import { Client, Account, OAuthProvider } from "node-appwrite"; // Using the server SDK

const adminClient = 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

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
<?php
use Appwrite\Client;
use Appwrite\Services\Account;
use Appwrite\Enums\OAuthProvider;

$adminClient = (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

$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://<REGION>.cloud.appwrite.io/v1')
                .set_project('<PROJECT_ID>')
                .set_key('<API_KEY>')
            )

@app.get('/oauth')
def oauth():
    account = Account(admin_client)

    redirect_url = account.create_o_auth2_token(
        provider=OAuthProvider.Github,
        success='https://example.com/oauth/success',
        failure='https://example.com/oauth/failure',
    )

    return redirect(redirect_url)
```
```rust
use appwrite::Client;
use appwrite::services::account::Account;
use appwrite::enums::OAuthProvider;

let admin_client = Client::new()
    .set_endpoint("https://<REGION>.cloud.appwrite.io/v1")
    .set_project("<PROJECT_ID>")
    .set_key("<API_KEY>");

let account = Account::new(&admin_client);

let redirect_url = account.create_o_auth2_token(
    OAuthProvider::Github,                            // Provider
    Some("https://example.com/oauth/success"),        // Success URL
    Some("https://example.com/oauth/failure"),        // Failure URL
    None,                                             // Scopes
).await?;

// Redirect the user to redirect_url
```

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.

```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_<PROJECT_ID>', session.secret, { // Use the session secret as the cookie value
            httpOnly: true,
            secure: true,
            sameSite: 'strict',
            maxAge: session.expire,
            path: '/',
        });

        res.status(200).json({ success: true });
    } catch (e) {
        res.status(400).json({ success: false, error: e.message });
    }
});
```
```php
<?php
use Appwrite\Client;
use Appwrite\Services\Account;

$adminClient = (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

$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_<PROJECT_ID>', $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=user_id, secret=secret)

        # Set the session cookie
        res = make_response(jsonify({'success': True}))

        #  Set session cookie
        res.set_cookie(
            'a_session_<PROJECT_ID>',
            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
```
```rust
use appwrite::Client;
use appwrite::services::account::Account;

let admin_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

let account = Account::new(&admin_client);

// Get the userId and secret from the URL parameters
// This depends on your HTTP framework (e.g. actix-web, axum, rocket)
let user_id = req.query("userId");
let secret = req.query("secret");

// Create the session using the Appwrite client
let session = account.create_session(
    &user_id,
    &secret,
).await?;

// Set the session cookie using the session secret
// Use your framework's cookie API to set:
// name: "a_session_<PROJECT_ID>"
// value: session.secret
// httpOnly: true, secure: true, sameSite: strict
// expires: session.expire
```

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
If you'd like to see SSR authentication implemented in a full auth example, see these tutorials.

- [Next.js SSR](/docs/tutorials/nextjs-ssr-auth)
- [SvelteKit SSR](/docs/tutorials/sveltekit-ssr-auth)
- [Nuxt SSR](/docs/tutorials/nuxt-ssr-auth)
- [Astro SSR](/docs/tutorials/astro-ssr-auth)

---

## Sign in with Appwrite

URL: https://appwrite.io/docs/products/auth/sign-in-with-appwrite

Sign in with Appwrite lets users log in to your app with their Appwrite account. It works like other OAuth2 providers such as Google or GitHub. Appwrite is the identity provider. This is a good fit when your users are developers, for example when you build developer tools, dashboards, or education platforms.

The provider requests the `openid`, `profile`, and `email` scopes. Appwrite uses them to read the user's ID, name, and email, and to create the session.

**Access to Appwrite resources**

This page covers login only. To access your users' Appwrite projects and organizations with scoped tokens, see [Sign in with Appwrite for apps](/docs/partners/apps). To make your own product an OAuth2 provider, see the [OAuth2 server](/docs/products/auth/oauth-server) documentation.

### Enable the provider

1. In the Appwrite Console, open your project.
2. Navigate to **Auth** > **Social providers**.
3. Open the **Appwrite** provider.
4. Turn on the **Enabled** toggle.

![Social providers page with the Appwrite provider enabled](/images/docs/auth/sign-in-with-appwrite/providers.avif)

### Configure credentials

![Appwrite OAuth2 settings dialog with quick setup and credentials](/images/docs/auth/sign-in-with-appwrite/provider-settings.avif)

The provider authenticates through an Appwrite app, which acts as the OAuth2 client. The **Appwrite OAuth2 settings** dialog gives you two ways to configure it.

**Quick setup** creates the app for you:

1. In the **Create app** tab, enter an app name.
2. Click **Create and fill credentials**.
3. Click **Update** to save the provider settings.

Appwrite creates the app in your organization, registers the redirect URI, and fills the **Client ID** and **Client Secret** fields. To reuse an app you registered before, use the **Select app** tab instead.

You can also enter credentials manually:

1. [Register an app](/docs/partners/apps/registration) in your organization.
2. Add the redirect URI shown in the dialog to the app.
3. Paste the app's client ID and secret into the fields.
4. Click **Update**.

### Initialize the sign-in

Start the flow from your app with the Appwrite provider. The user is redirected to Appwrite to approve the request, then redirected back to your app with an active session.

**Javascript**

```client-web
import { Client, Account, OAuthProvider } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<PROJECT_ID>');                          // Your project ID

const account = new Account(client);

// Go to the Appwrite consent screen
account.createOAuth2Session({
    provider: OAuthProvider.Appwrite,
    success: 'https://example.com/success', // redirect here on success
    failure: 'https://example.com/failed',  // redirect here on failure
});
```

**Flutter**

For Android, add the following activity inside the `<application>` tag in your `AndroidManifest.xml`. Replace `<PROJECT_ID>` with your actual Appwrite project ID.

```xml
<!-- Add this inside the <application> tag, along side the existing <activity> tags -->
<activity android:exported="true" android:name="com.linusu.flutter_web_auth_2.CallbackActivity" >
  <intent-filter android:label="flutter_web_auth_2">
    <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>
```

No other configuration is required for iOS.

```client-flutter
import 'package:appwrite/appwrite.dart';
import 'package:appwrite/enums.dart';

final client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<PROJECT_ID>');               // Your project ID

final account = Account(client);

// Go to the Appwrite consent screen
await account.createOAuth2Session(
    provider: OAuthProvider.appwrite,
);
```

**Apple**

For Apple, add the following URL scheme to your `Info.plist`.

```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, 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<UIOpenURLContext>) {
    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://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<PROJECT_ID>")                // Your project ID

let account = Account(client)

// Go to the Appwrite consent screen
try await account.createOAuth2Session(
    provider: .appwrite
)
```

**Android**

For Android, add the following activity inside the `<application>` tag in your `AndroidManifest.xml`.
Replace `<PROJECT_ID>` with your actual Appwrite project ID.

```xml
<!-- 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>
```
```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://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<PROJECT_ID>")                  // Your project ID

val account = Account(client)

// Go to the Appwrite consent screen
account.createOAuth2Session(
    provider = OAuthProvider.APPWRITE
)
```

**React Native**

If using Expo, set the URL scheme to `appwrite-callback-<PROJECT_ID>` in your `app.json` file.

```json
{
  "expo": {
    "scheme": "appwrite-callback-<PROJECT_ID>"
  }
}
```

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 "react-native-appwrite";
import { makeRedirectUri } from 'expo-auth-session'
import * as WebBrowser from 'expo-web-browser';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<PROJECT_ID>');                          // Your project ID

const account = new Account(client);

// Create deep link that works across Expo environments
// Ensure localhost is used for the hostname to avoid a validation error for success/failure URLs
const deepLink = new URL(makeRedirectUri({ preferLocalhost: true }));
const scheme = `${deepLink.protocol}//`; // e.g. 'exp://' or 'appwrite-callback-<PROJECT_ID>://'

// Start OAuth flow
const loginUrl = await account.createOAuth2Token({
    provider: OAuthProvider.Appwrite,
    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
```

### What users see

Appwrite asks the user to sign in to their Appwrite account if they have no active session. The consent screen then shows your app's name and the requested permissions. When the user clicks **Authorize**, Appwrite redirects them back to your app and the session is active. Users can revoke access from their Appwrite account at any time.

![Consent screen asking the user to authorize the app](/images/docs/auth/sign-in-with-appwrite/consent.avif)

Like all OAuth2 logins, a successful sign-in creates an [identity](/docs/products/auth/identities) for the user. To read provider details from the session or refresh tokens, see [OAuth2 login](/docs/products/auth/oauth2).

---

## Team invites

URL: 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.

```client-web
import { Client, Teams } from "appwrite";

const client = new Client()
    .setEndpoint('https://cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

const teams = new Teams(client);

// Create membership with email invite
const membership = await teams.createMembership({
    teamId: '<TEAM_ID>',
    roles: ['developer'],
    email: 'user@example.com',
    url: 'https://yourapp.com/accept-invite'
});
```
```client-flutter
import 'package:appwrite/appwrite.dart';

final client = Client()
    .setEndpoint('https://cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

final teams = Teams(client);

// Create membership with email invite
final membership = await teams.createMembership(
    teamId: '<TEAM_ID>',
    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("<PROJECT_ID>") // Your project ID

let teams = Teams(client)

// Create membership with email invite
let membership = try await teams.createMembership(
    teamId: "<TEAM_ID>",
    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("<PROJECT_ID>") // Your project ID

val teams = Teams(client)

// Create membership with email invite
val response = teams.createMembership(
    teamId = "<TEAM_ID>",
    roles = listOf("developer"),
    email = "user@example.com",
    url = "https://yourapp.com/accept-invite" // redirect after email click
)
```

#### 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

```client-web
import { Client, Teams } from "appwrite";

const client = new Client()
    .setEndpoint('https://cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

const teams = new Teams(client);

// Accept the invitation using the membership ID and secret
const response = await teams.updateMembershipStatus({
    teamId: '<TEAM_ID>',
    membershipId: '<MEMBERSHIP_ID>',
    userId: '<USER_ID>',
    secret: '<SECRET>'
});
```
```client-flutter
import 'package:appwrite/appwrite.dart';

final client = Client()
    .setEndpoint('https://cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

final teams = Teams(client);

// Accept the invitation using the membership ID and secret
final response = await teams.updateMembershipStatus(
    teamId: '<TEAM_ID>',
    membershipId: '<MEMBERSHIP_ID>',
    userId: '<USER_ID>',
    secret: '<SECRET>'
);
```
```client-apple
import Appwrite

let client = Client()
    .setEndpoint("https://cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

let teams = Teams(client)

// Accept the invitation using the membership ID and secret
let response = try await teams.updateMembershipStatus(
    teamId: "<TEAM_ID>",
    membershipId: "<MEMBERSHIP_ID>",
    userId: "<USER_ID>",
    secret: "<SECRET>"
)
```
```client-android-kotlin
import io.appwrite.Client
import io.appwrite.services.Teams

val client = Client(context)
    .setEndpoint("https://cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

val teams = Teams(client)

// Accept the invitation using the membership ID and secret
val response = teams.updateMembershipStatus(
    teamId = "<TEAM_ID>",
    membershipId = "<MEMBERSHIP_ID>",
    userId = "<USER_ID>",
    secret = "<SECRET>"
)
```

### 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.

```server-nodejs
const sdk = require('node-appwrite');

const client = new sdk.Client()
    .setEndpoint('https://cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<API_KEY>');

const teams = new sdk.Teams(client);

// Create membership directly with userId
const membership = await teams.createMembership({
    teamId: '<TEAM_ID>',
    roles: ['developer'],
    userId: '<USER_ID>',
    name: 'John Doe' // optional
});
```
```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('<PROJECT_ID>')
client.set_key('<API_KEY>')

teams = Teams(client)

### Create membership directly with userId
membership = teams.create_membership(
    team_id='<TEAM_ID>',
    roles=['developer'],
    user_id='<USER_ID>',
    name='John Doe'  # optional
)
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<PROJECT_ID>") // Your project ID
    .setKey("<API_KEY>") // Your secret API key

let teams = Teams(client)

// Create membership directly with userId
let membership = try await teams.createMembership(
    teamId: "<TEAM_ID>",
    roles: ["developer"],
    userId: "<USER_ID>",
    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("<PROJECT_ID>") // Your project ID
    .setKey("<API_KEY>") // Your secret API key

val teams = Teams(client)

// Create membership directly with userId
val response = teams.createMembership(
    teamId = "<TEAM_ID>",
    roles = listOf("developer"),
    userId = "<USER_ID>",
    name = "John Doe" // optional
)
```
```server-rust
use appwrite::Client;
use appwrite::services::teams::Teams;

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

    let teams = Teams::new(&client);

    // Create membership directly with userId
    let membership = teams.create_membership(
        "<TEAM_ID>",                                 // teamId
        vec!["developer"],                           // roles
        None,                                        // email (optional)
        Some("<USER_ID>"),                           // userId (optional)
        None,                                        // phone (optional)
        None,                                        // url (optional)
        Some("John Doe"),                            // name (optional)
    ).await?;

    println!("{:?}", membership);
    Ok(())
}
```

### 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:

```client-web
import { Client, Teams } from "appwrite";

const client = new Client()
    .setEndpoint('https://cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

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: '<TEAM_ID>'
});

// Find membership for specific user
const userMembership = response.memberships.find(
    membership => membership.userId === '<USER_ID>'
);

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('<PROJECT_ID>');

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: '<TEAM_ID>'
);

// Find membership for specific user
final userMembership = response.memberships.firstWhere(
    (membership) => membership.userId == '<USER_ID>',
    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("<PROJECT_ID>")

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: "<TEAM_ID>"
)

// Find membership for specific user
if let userMembership = response.memberships.first(where: { $0.userId == "<USER_ID>" }) {
    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("<PROJECT_ID>")

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 == "<USER_ID>"
}

userMembership?.let {
    println(it.confirm) // false = invited, true = joined
    println(it.roles) // ['developer', etc]
}
```

##### Server-side
Server-side requires iterating through teams and memberships since the data isn't filtered for a specific user:

```server-nodejs
const sdk = require('node-appwrite');

const client = new sdk.Client()
    .setEndpoint('https://cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<API_KEY>');

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 === '<USER_ID>'
    );

    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('<PROJECT_ID>')
client.set_key('<API_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=team['$id'])

    // Find membership for specific user
    user_membership = next(
        (m for m in response['memberships'] if m['userId'] == '<USER_ID>'),
        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("<PROJECT_ID>")
    .setKey("<API_KEY>")

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 == "<USER_ID>" }) {
        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("<PROJECT_ID>")
    .setKey("<API_KEY>")

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 == "<USER_ID>"
    }

    userMembership?.let {
        println("Team: ${team.name}")
        println("Joined: ${it.joined}") # null if invited, timestamp if joined
        println("Roles: ${it.roles}")
    }
}
```
```server-rust
use appwrite::Client;
use appwrite::services::teams::Teams;

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

    let teams = Teams::new(&client);

    // Get all teams
    let teams_list = teams.list(
        None,                                        // queries (optional)
        None,                                        // search (optional)
        None,                                        // total (optional)
    ).await?;

    // Iterate through teams to find memberships
    for team in &teams_list.teams {
        let response = teams.list_memberships(
            &team.id,                                // teamId
            None,                                    // queries (optional)
            None,                                    // search (optional)
            None,                                    // total (optional)
        ).await?;

        // Find membership for specific user
        if let Some(membership) = response.memberships.iter().find(|m| m.user_id == "<USER_ID>") {
            println!("Team: {}", team.name);
            println!("Joined: {:?}", membership.joined); // None if invited, timestamp if joined
            println!("Roles: {:?}", membership.roles);
        }
    }

    Ok(())
}
```

#### Remove members

Team owners can remove members or users can leave teams:

```client-web
import { Client, Teams } from "appwrite";

const client = new Client()
    .setEndpoint('https://cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

const teams = new Teams(client);

await teams.deleteMembership({
    teamId: '<TEAM_ID>',
    membershipId: '<MEMBERSHIP_ID>'
});
```
```client-flutter
import 'package:appwrite/appwrite.dart';

final client = Client()
    .setEndpoint('https://cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

final teams = Teams(client);

await teams.deleteMembership(
    teamId: '<TEAM_ID>',
    membershipId: '<MEMBERSHIP_ID>'
);
```
```client-apple
import Appwrite

let client = Client()
    .setEndpoint("https://cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

let teams = Teams(client)

try await teams.deleteMembership(
    teamId: "<TEAM_ID>",
    membershipId: "<MEMBERSHIP_ID>"
)
```
```client-android-kotlin
import io.appwrite.Client
import io.appwrite.services.Teams

val client = Client(context)
    .setEndpoint("https://cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

val teams = Teams(client)

teams.deleteMembership(
    teamId = "<TEAM_ID>",
    membershipId = "<MEMBERSHIP_ID>"
)
```

### 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:

```client-web
import { Client, Teams } from "appwrite"

const client = new Client()
    .setEndpoint('https://cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')

const teams = new Teams(client)

// Update member roles
await teams.updateMembership({
    teamId: '<TEAM_ID>',
    membershipId: '<MEMBERSHIP_ID>',
    roles: ['admin', 'developer']
})
```
```client-flutter
import 'package:appwrite/appwrite.dart'

final client = Client()
    .setEndpoint('https://cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')

final teams = Teams(client)

// Update member roles
await teams.updateMembership(
    teamId: '<TEAM_ID>',
    membershipId: '<MEMBERSHIP_ID>',
    roles: ['admin', 'developer']
)
```
```client-apple
import Appwrite

let client = Client()
    .setEndpoint("https://cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

let teams = Teams(client)

// Update member roles
try await teams.updateMembership(
    teamId: "<TEAM_ID>",
    membershipId: "<MEMBERSHIP_ID>",
    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("<PROJECT_ID>")

val teams = Teams(client)

// Update member roles
teams.updateMembership(
    teamId = "<TEAM_ID>",
    membershipId = "<MEMBERSHIP_ID>",
    roles = listOf("admin", "developer")
)
```

#### Check role access

You can verify if a user has specific roles:

```client-web
import { Client, Teams } from "appwrite"

const client = new Client()
    .setEndpoint('https://cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')

const teams = new Teams(client)

// Get team memberships
const response = await teams.listMemberships({
    teamId: '<TEAM_ID>'
});

// Check if user has specific role
const membership = response.memberships.find(m => m.userId === '<USER_ID>')
const isAdmin = membership?.roles.includes('admin') ?? false
```
```client-flutter
import 'package:appwrite/appwrite.dart'

final client = Client()
    .setEndpoint('https://cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')

final teams = Teams(client)

// Get team memberships
final response = await teams.listMemberships(
    teamId: '<TEAM_ID>'
)

// Check if user has specific role
final membership = response.memberships.firstWhere(
    (m) => m.userId == '<USER_ID>',
    orElse: () => null
)
final isAdmin = membership?.roles.contains('admin') ?? false
```
```client-apple
import Appwrite

let client = Client()
    .setEndpoint("https://cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

let teams = Teams(client)

// Get team memberships
let response = try await teams.listMemberships(
    teamId: "<TEAM_ID>"
)

// Check if user has specific role
let membership = response.memberships.first { $0.userId == "<USER_ID>" }
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("<PROJECT_ID>")

val teams = Teams(client)

// Get team memberships
val response = teams.listMemberships(
    teamId = "<TEAM_ID>"
)

// Check if user has specific role
val membership = response.memberships.find { it.userId == "<USER_ID>" }
val isAdmin = membership?.roles?.contains("admin") ?: false
```

See how to grant row and file access to team roles in the [permissions](/docs/advanced/security/permissions#example-2-team-roles) guide.

[Learn more about team management](/docs/products/auth/teams)

---

## Teams

URL: 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.

[Learn about using Teams for multi-tenancy](/docs/products/auth/multi-tenancy)

### Create team
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**.

```client-web
import { Client, Teams } from "appwrite";

const client = new Client();

const teams = new Teams(client);

client
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<PROJECT_ID>') // Your project ID
;

const promise = teams.create({
    teamId: 'teachers',
    name: 'Teachers',
    roles: ['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://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<PROJECT_ID>') // 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://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<PROJECT_ID>") // 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://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<PROJECT_ID>") // Your project ID

val teams = Teams(client)

val response = teams.create(
    teamId = "teachers",
    name = "Teachers",
    roles = listOf("maths", "sciences", "arts", "literature")
)
```

### Invite a member

You can invite members to a team by creating team memberships. For example, inviting "David" a math teacher, to the teachers team.

```client-web
import { Client, Teams } from "appwrite";

const client = new Client();

const teams = new Teams(client);

client
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<PROJECT_ID>') // Your project ID
;

const promise = teams.createMembership({
    teamId: 'teachers',
    roles: ["maths"],
    email: "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://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<PROJECT_ID>') // 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://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<PROJECT_ID>") // 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://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<PROJECT_ID>") // Your project ID

val teams = Teams(client)

val response = teams.createMembership(
    teamId = "teachers",
    roles = listOf("maths"),
    email = "david@example.com"
)
```

### Using the CLI

**Before proceeding**

Ensure you [**install**](/docs/tooling/command-line/installation#getting-started) the CLI, [**log in**](/docs/tooling/command-line/installation#login) to your Appwrite account, and [**initialize**](/docs/tooling/command-line/installation#initialization) your Appwrite project.

Use the CLI command `appwrite teams create-membership [options]` to invite a new member into your team.

```sh
appwrite teams create-membership --team-id "<TEAM_ID>" --roles --phone "+12065550100" --name "<NAME>" --user-id "<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.

[Learn more about the CLI teams commands](/docs/tooling/command-line/teams#commands)

### Permissions

You can grant permissions to all members of a team using the `Role.team(<TEAM_ID>)` role or
individual roles in the team using the `Role.team(<TEAM_ID>, [<ROLE_1>, <ROLE_2>, ...])` role.
| Description | Role |
| ------------------------------------------- | ------------------------------------------- |
| All members | `Role.team(<TEAM_ID>)`|
| Select roles | `Role.team(<TEAM_ID>, [<ROLE_1>, <ROLE_2>, ...])`|

[Learn more about permissions](/docs/advanced/security/permissions)

### 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

URL: 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
You can find different usage of tokens in the Appwrite.

- [Custom token login](/docs/products/auth/custom-token)
- [Email OTP login](/docs/products/auth/email-otp)
- [Email magic URL](/docs/products/auth/magic-url)
- [Phone (SMS) OTP](/docs/products/auth/phone-sms)

---

## Manage users

URL: 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 and 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).

Need to troubleshoot from a user's point of view? Use [user impersonation](/docs/products/auth/impersonation) to let trusted operators temporarily act as another user without sharing credentials.

**Account vs Users API**

The Account API is the API you should use in your **client applications** with [Client SDKs](/docs/sdks#client) like web, Flutter, mobile, and native apps.
Account API creates sessions, which represent an authenticated user and is attached to a user's [account](/docs/products/auth/accounts).
Sessions respect [permissions](/docs/advanced/security/permissions), which means users can only access resources if they have been granted the correct permissions.

The Users API is a dedicated API for managing users from an admin's perspective.
It should be used with backend or server-side applications with [Server SDKs](/docs/sdks#server). Users API uses API keys instead of sessions.
This means they're not restricted by permissions, but by the scopes granted to the API key used.

The users API can be used to create users, import users, update user info, get user audit logs, and remove users.

[Learn more in the Users API references](/docs/references/cloud/server-nodejs/users)

---

## Verify user

URL: 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

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.

```client-web
import { Client, Account } from "appwrite";

const client = new Client()
    .setEndpoint('https://cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>') // 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('<PROJECT_ID>');

  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("<PROJECT_ID>")

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("<PROJECT_ID>")

val account = Account(client)

val response = account.createVerification(
    url = "https://example.com/verify"
)
```

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.

```client-web
import { Client, Account } from "appwrite";

const client = new Client()
    .setEndpoint('https://cloud.appwrite.io/v1')
    .setProject('<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
});
```

```client-flutter
import 'package:appwrite/appwrite.dart';

void main() {
  Client client = Client()
    .setEndpoint('https://cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

  Account account = Account(client);

  Future result = account.updateVerification(
    userId: '<USER_ID>',
    secret: '<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("<PROJECT_ID>")

let account = Account(client)

let response = try await account.updateVerification(
    userId: "<USER_ID>",
    secret: "<SECRET>"
)
```

```client-android-kotlin
import io.appwrite.Client
import io.appwrite.services.Account

val client = Client(context)
    .setEndpoint("https://cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

val account = Account(client)

val response = account.updateVerification(
    userId = "<USER_ID>",
    secret = "<SECRET>"
)
```

### Verify phone

To verify a phone number, first ensure the user is logged in and has a phone number set on their account.

```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"
)
```

Then initiate verification by calling `createPhoneVerification`.

```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()
```

After the user receives the verification code, complete verification by calling `updatePhoneVerification`.

```client-web
const response = await account.updatePhoneVerification({
    userId: '[USER_ID]',
    secret: '[SECRET]'
});
```

```client-flutter
Future result = account.updatePhoneVerification(
    userId: '<USER_ID>',
    secret: '<SECRET>'
);

result.then((response) {
    print(response);
}).catchError((error) {
    print(error.response);
});
```

```client-apple
let response = try await account.updatePhoneVerification(
    userId: "<USER_ID>",
    secret: "<SECRET>"
)
```

```client-android-kotlin
val response = account.updatePhoneVerification(
    userId = "<USER_ID>",
    secret = "<SECRET>"
)
```

### 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

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

URL: 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.

[Get started with Avatars in minutes](/docs/products/avatars/quick-start)

### Capabilities

Appwrite Avatars supports multiple image generation and manipulation features to enhance your application's visual elements.

- [User initials](/docs/products/avatars/initials): Generate avatar images from user names or initials with customizable colors and sizes.
- [QR codes](/docs/products/avatars/qr-codes): Create QR codes for authentication, sharing, and other use cases with customizable size and margin.
- [Country flags](/docs/products/avatars/flags): Fetch country flag icons for displaying user locations and regional information.
- [Browser icons](/docs/products/avatars/browsers): Retrieve browser icons for displaying user agent information and device compatibility.
- [Payment methods](/docs/products/avatars/payment-methods): Get payment method logos for checkout flows and transaction displays.
- [Favicons](/docs/products/avatars/favicons): Fetch favicons from remote websites for link previews and bookmark displays.
- [Screenshots](/docs/products/avatars/screenshots): Capture webpage screenshots with customizable viewport, theme, and browser settings.
- [Image proxy](/docs/products/avatars/image-manipulation): Transform remote images with resizing, cropping, and quality adjustments.

### 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

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

URL: 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

Retrieve a browser icon by browser code.

```client-web
import { Client, Avatars, Browser } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

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://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

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://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

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://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

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://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

const avatars = new Avatars(client);

const result = avatars.getBrowser({
    code: 'chrome',
    width: 100,
    height: 100
});

console.log(result); // Resource URL
```

### 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

Common browser codes include:

```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
)
```

### 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

URL: 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

Retrieve a favicon from a remote website URL.

```client-web
import { Client, Avatars } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

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://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

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://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

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://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

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://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

const avatars = new Avatars(client);

const result = avatars.getFavicon({
    url: 'https://example.com',
    width: 100,
    height: 100
});

console.log(result); // Resource URL
```

### 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

The service automatically attempts to retrieve favicons from common locations on the target website, including standard favicon paths and HTML meta tags.

```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
)
```

### 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

URL: 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

Retrieve a country flag icon by its [ISO 3166-1](https://en.wikipedia.org/wiki/ISO_3166-1) country code.

```client-web
import { Client, Avatars, Flag } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

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://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

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://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

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://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

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://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

const avatars = new Avatars(client);

const result = avatars.getFlag({
    code: 'US',
    width: 100,
    height: 100
});

console.log(result); // Resource URL
```

### 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

Use ISO 3166-1 alpha-2 country codes to specify the country. These are two-letter codes that uniquely identify countries.

```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
)
```

### 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

URL: 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

Fetch and transform an image from a remote URL with various transformation options.

```client-web
import { Client, Avatars } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

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://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

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://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

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://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

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://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

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
```

### 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

Resize images to specific dimensions while maintaining aspect ratio. When only width or height is provided, the other dimension is calculated automatically.

```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
)
```

### Cropping

When both width and height are specified, you can control how the image is cropped using the gravity parameter.

```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"
)
```

### Quality and format

Adjust image quality and output format to optimize file size and performance.

```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"
)
```

### 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

URL: 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 an avatar image from a user's name. The service automatically extracts initials from the name and displays them on a colored background.

```client-web
import { Client, Avatars } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

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://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

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://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

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://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

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://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

const avatars = new Avatars(client);

const result = avatars.getInitials({
    name: 'John Doe',
    width: 200,
    height: 200,
    background: '000000'
});

console.log(result); // Resource URL
```

### 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

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.

```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
)
```

### 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

URL: 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

Retrieve a payment method or credit card logo by code.

```client-web
import { Client, Avatars, CreditCard } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

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://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

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://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

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://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

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://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

const avatars = new Avatars(client);

const result = avatars.getCreditCard({
    code: 'visa',
    width: 100,
    height: 100
});

console.log(result); // Resource URL
```

### 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

Common payment method codes include:

```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
)
```

### 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

URL: 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 a QR code image from a text string. The QR code can be scanned by any standard QR code reader.

```client-web
import { Client, Avatars } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

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://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

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://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

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://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

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://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

const avatars = new Avatars(client);

const result = avatars.getQR({
    text: 'https://example.com',
    size: 300,
    margin: 1,
    download: false
});

console.log(result); // Resource URL
```

### 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

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.

```client-web
// After creating an MFA authenticator
const authenticator = await account.createMfaAuthenticator({
    type: '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
)
```

### 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.

```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
)
```

### 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

URL: 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

First, initialize the Appwrite client with your project endpoint and project ID.

```client-web
import { Client, Avatars } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

const avatars = new Avatars(client);
```
```client-flutter
import 'package:appwrite/appwrite.dart';

final client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

final avatars = Avatars(client);
```
```client-apple
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

let avatars = Avatars(client)
```
```client-android-kotlin
import io.appwrite.Client
import io.appwrite.services.Avatars

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

val avatars = Avatars(client)
```
```client-react-native
import { Client, Avatars } from 'react-native-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

const avatars = new Avatars(client);
```

### Generate user 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.

```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
```

### Generate QR code

Create a QR code from any text string. This is commonly used for two-factor authentication, sharing links, or encoding data.

```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
```

### Get country flag

Retrieve a country flag icon by country code. This is useful for displaying user locations or regional information.

```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
```

### 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

URL: 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

Capture a screenshot of a remote webpage with customizable browser settings and viewport options.

```client-web
import { Client, Avatars } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

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://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

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://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

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://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

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://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

const avatars = new Avatars(client);

const result = avatars.getScreenshot({
    url: 'https://example.com'
});

console.log(result); // Resource URL
```

### 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

Control the browser viewport to capture web pages as they would appear on different devices and screen sizes.

```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
)
```

### Browser customization

Customize browser settings like theme, user agent, locale, and timezone to simulate different browser environments.

```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
)
```

### Geolocation simulation

Simulate different geographic locations to see how web pages render with location-based content and features.

```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
)
```

### Wait time before capture

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.

```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
)
```

### 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.

```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()
)
```

### 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

URL: https://appwrite.io/docs/products/databases

Appwrite Databases provide performant and scalable storage for your application, business, and user data. Choose the database that fits your use case, from managed APIs with permissions and realtime to dedicated native engines you connect to directly.

**Looking for file storage?**

Databases store data. If you need to store files like images, PDFs, or videos, use [Appwrite Storage](/docs/products/storage).

### Appwrite databases

Managed databases with an Appwrite API on top, including permissions, indexes, queries, and realtime. Available on shared and dedicated infrastructure.

- [TablesDB](/docs/products/databases/tablesdb): Structured, relational data with typed columns, rows, relationships, and indexes.
- [DocumentsDB](/docs/products/databases/documentsdb): Schemaless document storage for flexible, JSON-style data.
- [VectorsDB](/docs/products/databases/vectorsdb): Store embeddings and run similarity search to power AI features.

### Shared and dedicated databases

An Appwrite database runs either on a shared pool that Appwrite manages for you, or on compute provisioned for your project alone.

If you leave `specification` out when creating a database, it lands on the shared pool and is ready to use as soon as the call returns. If you pass a specification, Appwrite provisions a dedicated database on that tier, which you can then resize, replicate, and fail over. Collections and rows or documents work through the same methods either way.

### Native databases

Native database engines provisioned for your project with direct connection access and no abstraction layer. Available on dedicated infrastructure.

- [Managed PostgreSQL](/docs/products/databases/postgresql): Hosted PostgreSQL with direct connections, backups, pgvector, and full SQL.
- [MySQL](/docs/products/databases/mysql): A dedicated, native MySQL database you connect to directly.

---

## DocumentsDB

URL: https://appwrite.io/docs/products/databases/documentsdb

Appwrite DocumentsDB lets you store and query schemaless documents.
Collections hold documents as flexible JSON, so you can add fields as your data evolves without defining a schema up front.

**Looking for file storage?**

Databases store data, if you need to store files like images, PDFs or videos, use [Appwrite Storage](/docs/products/storage).

You can organize data into databases, collections, and documents. You can also paginate, order, and query documents.

[Quick start](/docs/products/databases/documentsdb/quick-start)

### Concepts

- [Databases](/docs/products/databases/documentsdb/databases): Create and manage databases, the largest organizational unit.
- [Collections](/docs/products/databases/documentsdb/collections): Group documents in schemaless collections.
- [Documents](/docs/products/databases/documentsdb/documents): Create, read, update, and delete JSON documents.
- [Permissions](/docs/products/databases/documentsdb/permissions): Control access at the collection and document level.
- [Queries](/docs/products/databases/documentsdb/queries): Filter documents with comparison, string, and logical operators.
- [Order](/docs/products/databases/documentsdb/order): Sort results by one or more fields.
- [Backups](/docs/products/databases/documentsdb/backups): Schedule backup policies and restore from any backup.

### Guides

- [Pagination](/docs/products/databases/documentsdb/pagination): Page through results with offset or cursor pagination.
- [Transactions](/docs/products/databases/documentsdb/transactions): Stage operations and commit or roll them back atomically.
- [Bulk operations](/docs/products/databases/documentsdb/bulk-operations): Create, update, upsert, or delete many documents in one request.
- [Atomic numeric operations](/docs/products/databases/documentsdb/atomic-numeric-operations): Increment and decrement numeric fields without race conditions.
- [Timestamp overrides](/docs/products/databases/documentsdb/timestamp-overrides): Set custom creation and update timestamps during imports.

---

## Atomic numeric operations

URL: https://appwrite.io/docs/products/databases/documentsdb/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.

These operations work on numeric document fields, whether the value is an integer or a floating-point number.

### 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 `incrementDocumentAttribute()` with the attribute key and the value to increment by
2. Server applies atomically → `likes: 43`

### 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

Use the `incrementDocumentAttribute` and `decrementDocumentAttribute` methods to perform atomic numeric operations. The server will apply these changes atomically under concurrency control.

#### Increment a field

```client-web
import { Client, DocumentsDB } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

const documentsDB = new DocumentsDB(client);

const result = await documentsDB.incrementDocumentAttribute({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>',
    attribute: 'likes', // attribute
    value: 1 // value
});
```
```client-flutter
import 'package:appwrite/appwrite.dart';

final client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

final documentsDB = DocumentsDB(client);

final document = await documentsDB.incrementDocumentAttribute(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>',
    attribute: 'likes',
    value: 1
);
```
```client-apple
import Appwrite
import AppwriteModels

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

let documentsDB = DocumentsDB(client)

let document = try await documentsDB.incrementDocumentAttribute(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    documentId: "<DOCUMENT_ID>",
    attribute: "likes",
    value: 1
)
```
```client-android-kotlin
import io.appwrite.Client
import io.appwrite.services.DocumentsDB

val client = Client(applicationContext)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

val documentsDB = DocumentsDB(client)

val document = documentsDB.incrementDocumentAttribute(
    databaseId = "<DATABASE_ID>",
    collectionId = "<COLLECTION_ID>",
    documentId = "<DOCUMENT_ID>",
    attribute = "likes",
    value = 1
)
```
```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('<API_KEY>'); // Your secret API key

const documentsDB = new sdk.DocumentsDB(client);

const result = await documentsDB.incrementDocumentAttribute({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>',
    attribute: 'likes', // attribute
    value: 1 // value
});
```
```server-python
from appwrite.client import Client
from appwrite.services.documents_db import DocumentsDB

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('<API_KEY>') # Your secret API key

documents_db = DocumentsDB(client)

result = documents_db.increment_document_attribute(
    database_id = '<DATABASE_ID>',
    collection_id = '<COLLECTION_ID>',
    document_id = '<DOCUMENT_ID>',
    attribute = 'likes', # attribute
    value = 1 # value
)
```
```rust
use appwrite::Client;
use appwrite::services::documents_db::DocumentsDB;

let client = Client::new()
    .set_endpoint("https://<REGION>.cloud.appwrite.io/v1")
    .set_project("<PROJECT_ID>")
    .set_key("<API_KEY>");

let documents_db = DocumentsDB::new(&client);

let result = documents_db.increment_document_attribute(
    "<DATABASE_ID>",
    "<COLLECTION_ID>",
    "<DOCUMENT_ID>",
    "likes",       // attribute
    Some(1.0),     // value
    None,          // max
    None,          // transaction_id
).await?;
```
```graphql
mutation {
    documentsDBIncrementDocumentAttribute(
        databaseId: "<DATABASE_ID>",
        collectionId: "<COLLECTION_ID>",
        documentId: "<DOCUMENT_ID>",
        attribute: "likes",
        value: 1
    ) {
        _id
        _collectionId
        _databaseId
        _createdAt
        _updatedAt
        _permissions
        data
    }
}
```

#### Decrement a field

Use the `decrementDocumentAttribute` method to decrease numeric fields:

```client-web
import { Client, DocumentsDB } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

const documentsDB = new DocumentsDB(client);

const result = await documentsDB.decrementDocumentAttribute({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>',
    attribute: 'credits', // attribute
    value: 5 // value
});
```
```client-flutter
import 'package:appwrite/appwrite.dart';

final client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

final documentsDB = DocumentsDB(client);

final document = await documentsDB.decrementDocumentAttribute(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>',
    attribute: 'credits',
    value: 5
);
```
```client-apple
import Appwrite
import AppwriteModels

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

let documentsDB = DocumentsDB(client)

let document = try await documentsDB.decrementDocumentAttribute(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    documentId: "<DOCUMENT_ID>",
    attribute: "credits",
    value: 5
)
```
```client-android-kotlin
import io.appwrite.Client
import io.appwrite.services.DocumentsDB

val client = Client(applicationContext)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

val documentsDB = DocumentsDB(client)

val document = documentsDB.decrementDocumentAttribute(
    databaseId = "<DATABASE_ID>",
    collectionId = "<COLLECTION_ID>",
    documentId = "<DOCUMENT_ID>",
    attribute = "credits",
    value = 5
)
```
```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('<API_KEY>'); // Your secret API key

const documentsDB = new sdk.DocumentsDB(client);

const result = await documentsDB.decrementDocumentAttribute({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>',
    attribute: 'credits', // attribute
    value: 5 // value
});
```
```server-python
from appwrite.client import Client
from appwrite.services.documents_db import DocumentsDB

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('<API_KEY>') # Your secret API key

documents_db = DocumentsDB(client)

result = documents_db.decrement_document_attribute(
    database_id = '<DATABASE_ID>',
    collection_id = '<COLLECTION_ID>',
    document_id = '<DOCUMENT_ID>',
    attribute = 'credits', # attribute
    value = 5 # value
)
```
```rust
use appwrite::Client;
use appwrite::services::documents_db::DocumentsDB;

let client = Client::new()
    .set_endpoint("https://<REGION>.cloud.appwrite.io/v1")
    .set_project("<PROJECT_ID>")
    .set_key("<API_KEY>");

let documents_db = DocumentsDB::new(&client);

let result = documents_db.decrement_document_attribute(
    "<DATABASE_ID>",
    "<COLLECTION_ID>",
    "<DOCUMENT_ID>",
    "credits",     // attribute
    Some(5.0),     // value
    None,          // min
    None,          // transaction_id
).await?;
```
```graphql
mutation {
    documentsDBDecrementDocumentAttribute(
        databaseId: "<DATABASE_ID>",
        collectionId: "<COLLECTION_ID>",
        documentId: "<DOCUMENT_ID>",
        attribute: "credits",
        value: 5
    ) {
        _id
        _collectionId
        _databaseId
        _createdAt
        _updatedAt
        _permissions
        data
    }
}
```

### 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. If an operation would move the value past the bound, the request is rejected.

#### Example with constraints

```client-web
// Increment with maximum constraint
const result = await documentsDB.incrementDocumentAttribute({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>',
    attribute: 'credits', // attribute
    value: 100, // value
    max: 1000 // max (optional)
});

// Decrement with minimum constraint
const result2 = await documentsDB.decrementDocumentAttribute({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>',
    attribute: 'credits', // attribute
    value: 50, // value
    min: 0 // min (optional)
});
```
```client-flutter
// Increment with maximum constraint
final document = await documentsDB.incrementDocumentAttribute(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>',
    attribute: 'credits',
    value: 100,
    max: 1000
);

// Decrement with minimum constraint
final document2 = await documentsDB.decrementDocumentAttribute(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>',
    attribute: 'credits',
    value: 50,
    min: 0
);
```
```client-apple
// Increment with maximum constraint
let document = try await documentsDB.incrementDocumentAttribute(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    documentId: "<DOCUMENT_ID>",
    attribute: "credits",
    value: 100,
    max: 1000
)

// Decrement with minimum constraint
let document2 = try await documentsDB.decrementDocumentAttribute(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    documentId: "<DOCUMENT_ID>",
    attribute: "credits",
    value: 50,
    min: 0
)
```
```client-android-kotlin
// Increment with maximum constraint
val document = documentsDB.incrementDocumentAttribute(
    databaseId = "<DATABASE_ID>",
    collectionId = "<COLLECTION_ID>",
    documentId = "<DOCUMENT_ID>",
    attribute = "credits",
    value = 100,
    max = 1000
)

// Decrement with minimum constraint
val document2 = documentsDB.decrementDocumentAttribute(
    databaseId = "<DATABASE_ID>",
    collectionId = "<COLLECTION_ID>",
    documentId = "<DOCUMENT_ID>",
    attribute = "credits",
    value = 50,
    min = 0
)
```
```server-nodejs
// Increment with maximum constraint
const result = await documentsDB.incrementDocumentAttribute({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>',
    attribute: 'credits', // attribute
    value: 100, // value
    max: 1000 // max (optional)
});

// Decrement with minimum constraint
const result2 = await documentsDB.decrementDocumentAttribute({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>',
    attribute: 'credits', // attribute
    value: 50, // value
    min: 0 // min (optional)
});
```
```server-python
### Increment with maximum constraint
result = documents_db.increment_document_attribute(
    database_id = '<DATABASE_ID>',
    collection_id = '<COLLECTION_ID>',
    document_id = '<DOCUMENT_ID>',
    attribute = 'credits', # attribute
    value = 100, # value
    max = 1000 # max (optional)
)

### Decrement with minimum constraint
result2 = documents_db.decrement_document_attribute(
    database_id = '<DATABASE_ID>',
    collection_id = '<COLLECTION_ID>',
    document_id = '<DOCUMENT_ID>',
    attribute = 'credits', # attribute
    value = 50, # value
    min = 0 # min (optional)
)
```
```rust
// Increment with maximum constraint
let result = documents_db.increment_document_attribute(
    "<DATABASE_ID>",
    "<COLLECTION_ID>",
    "<DOCUMENT_ID>",
    "credits",       // attribute
    Some(100.0),     // value
    Some(1000.0),    // max (optional)
    None,            // transaction_id
).await?;

// Decrement with minimum constraint
let result2 = documents_db.decrement_document_attribute(
    "<DATABASE_ID>",
    "<COLLECTION_ID>",
    "<DOCUMENT_ID>",
    "credits",       // attribute
    Some(50.0),      // value
    Some(0.0),       // min (optional)
    None,            // transaction_id
).await?;
```

### Follow best practices

#### 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

For complex updates that include both atomic operations and regular field changes, you'll need to use separate API calls:

```client-web
// First, increment the likes atomically
const likeResult = await documentsDB.incrementDocumentAttribute({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>',
    attribute: 'likes', // attribute
    value: 1 // value
});

// Then, update other fields
const updateResult = await documentsDB.updateDocument({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>',
    data: {
        lastLikedBy: userId,
        lastLikedAt: new Date().toISOString()
    }
});
```
```client-flutter
// First, increment the likes atomically
final likeResult = await documentsDB.incrementDocumentAttribute(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>',
    attribute: 'likes',
    value: 1
);

// Then, update other fields
final updateResult = await documentsDB.updateDocument(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>',
    data: {
        'lastLikedBy': userId,
        'lastLikedAt': DateTime.now().toIso8601String()
    }
);
```
```client-apple
// First, increment the likes atomically
let likeResult = try await documentsDB.incrementDocumentAttribute(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    documentId: "<DOCUMENT_ID>",
    attribute: "likes",
    value: 1
)

// Then, update other fields
let updateResult = try await documentsDB.updateDocument(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    documentId: "<DOCUMENT_ID>",
    data: [
        "lastLikedBy": userId,
        "lastLikedAt": ISO8601DateFormatter().string(from: Date())
    ]
)
```
```client-android-kotlin
// First, increment the likes atomically
val likeResult = documentsDB.incrementDocumentAttribute(
    databaseId = "<DATABASE_ID>",
    collectionId = "<COLLECTION_ID>",
    documentId = "<DOCUMENT_ID>",
    attribute = "likes",
    value = 1
)

// Then, update other fields
val updateResult = documentsDB.updateDocument(
    databaseId = "<DATABASE_ID>",
    collectionId = "<COLLECTION_ID>",
    documentId = "<DOCUMENT_ID>",
    data = mapOf(
        "lastLikedBy" to userId,
        "lastLikedAt" to Instant.now().toString()
    )
)
```
```server-nodejs
// First, increment the likes atomically
const likeResult = await documentsDB.incrementDocumentAttribute({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>',
    attribute: 'likes', // attribute
    value: 1 // value
});

// Then, update other fields
const updateResult = await documentsDB.updateDocument({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>',
    data: {
        lastLikedBy: userId,
        lastLikedAt: new Date().toISOString()
    }
});
```
```server-python
### First, increment the likes atomically
like_result = documents_db.increment_document_attribute(
    database_id = '<DATABASE_ID>',
    collection_id = '<COLLECTION_ID>',
    document_id = '<DOCUMENT_ID>',
    attribute = 'likes', # attribute
    value = 1 # value
)

### Then, update other fields
update_result = documents_db.update_document(
    database_id = '<DATABASE_ID>',
    collection_id = '<COLLECTION_ID>',
    document_id = '<DOCUMENT_ID>',
    data = {
        'lastLikedBy': user_id,
        'lastLikedAt': datetime.now().isoformat()
    }
)
```
```rust
use serde_json::json;

// First, increment the likes atomically
let like_result = documents_db.increment_document_attribute(
    "<DATABASE_ID>",
    "<COLLECTION_ID>",
    "<DOCUMENT_ID>",
    "likes",       // attribute
    Some(1.0),     // value
    None,          // max
    None,          // transaction_id
).await?;

// Then, update other fields
let update_result = documents_db.update_document(
    "<DATABASE_ID>",
    "<COLLECTION_ID>",
    "<DOCUMENT_ID>",
    Some(json!({
        "lastLikedBy": user_id,
        "lastLikedAt": chrono::Utc::now().to_rfc3339()
    })),
    None,          // permissions
    None,          // transaction_id
).await?;
```

### Use transactions

Atomic numeric operations accept `transactionId`. When provided, increments/decrements are staged and applied on commit.

```client-web
await documentsDB.incrementDocumentAttribute({
  databaseId: '<DATABASE_ID>',
  collectionId: '<COLLECTION_ID>',
  documentId: '<DOCUMENT_ID>',
  attribute: 'likes',
  value: 1,
  transactionId: '<TRANSACTION_ID>'
});
```
```client-flutter
await documentsDB.incrementDocumentAttribute(
  databaseId: '<DATABASE_ID>',
  collectionId: '<COLLECTION_ID>',
  documentId: '<DOCUMENT_ID>',
  attribute: 'likes',
  value: 1,
  transactionId: '<TRANSACTION_ID>'
);
```
```client-apple
try await documentsDB.incrementDocumentAttribute(
  databaseId: "<DATABASE_ID>",
  collectionId: "<COLLECTION_ID>",
  documentId: "<DOCUMENT_ID>",
  attribute: "likes",
  value: 1,
  transactionId: "<TRANSACTION_ID>"
)
```
```client-android-kotlin
documentsDB.incrementDocumentAttribute(
  databaseId = "<DATABASE_ID>",
  collectionId = "<COLLECTION_ID>",
  documentId = "<DOCUMENT_ID>",
  attribute = "likes",
  value = 1,
  transactionId = "<TRANSACTION_ID>"
)
```
```client-android-java
documentsDB.incrementDocumentAttribute(
  "<DATABASE_ID>",
  "<COLLECTION_ID>",
  "<DOCUMENT_ID>",
  "likes",
  1,
  "<TRANSACTION_ID>",
  new CoroutineCallback<>((result, error) -> {
    if (error != null) {
      error.printStackTrace();
      return null;
    }
    System.out.println(result);
    return null;
  })
);
```
```client-react-native
await documentsDB.incrementDocumentAttribute({
  databaseId: '<DATABASE_ID>',
  collectionId: '<COLLECTION_ID>',
  documentId: '<DOCUMENT_ID>',
  attribute: 'likes',
  value: 1,
  transactionId: '<TRANSACTION_ID>'
});
```
```server-nodejs
await documentsDB.incrementDocumentAttribute({
  databaseId: '<DATABASE_ID>',
  collectionId: '<COLLECTION_ID>',
  documentId: '<DOCUMENT_ID>',
  attribute: 'likes',
  value: 1,
  transactionId: '<TRANSACTION_ID>'
});
```
```server-deno
await documentsDB.incrementDocumentAttribute({
  databaseId: '<DATABASE_ID>',
  collectionId: '<COLLECTION_ID>',
  documentId: '<DOCUMENT_ID>',
  attribute: 'likes',
  value: 1,
  transactionId: '<TRANSACTION_ID>'
});
```
```server-python
documents_db.increment_document_attribute(
  database_id = '<DATABASE_ID>',
  collection_id = '<COLLECTION_ID>',
  document_id = '<DOCUMENT_ID>',
  attribute = 'likes',
  value = 1,
  transaction_id = '<TRANSACTION_ID>'
)
```
```rust
documents_db.increment_document_attribute(
    "<DATABASE_ID>",
    "<COLLECTION_ID>",
    "<DOCUMENT_ID>",
    "likes",
    Some(1.0),
    None,                          // max
    Some("<TRANSACTION_ID>"),      // transaction_id
).await?;
```
```server-php
$documentsDB->incrementDocumentAttribute(
  databaseId: '<DATABASE_ID>',
  collectionId: '<COLLECTION_ID>',
  documentId: '<DOCUMENT_ID>',
  attribute: 'likes',
  value: 1,
  transactionId: '<TRANSACTION_ID>'
);
```
```server-ruby
documents_db.increment_document_attribute(
  database_id: '<DATABASE_ID>',
  collection_id: '<COLLECTION_ID>',
  document_id: '<DOCUMENT_ID>',
  attribute: 'likes',
  value: 1,
  transaction_id: '<TRANSACTION_ID>'
)
```
```server-dotnet
await documentsDB.IncrementDocumentAttribute(
  databaseId: "<DATABASE_ID>",
  collectionId: "<COLLECTION_ID>",
  documentId: "<DOCUMENT_ID>",
  attribute: "likes",
  value: 1,
  transactionId: "<TRANSACTION_ID>"
);
```
```server-dart
await documentsDB.incrementDocumentAttribute(
  databaseId: '<DATABASE_ID>',
  collectionId: '<COLLECTION_ID>',
  documentId: '<DOCUMENT_ID>',
  attribute: 'likes',
  value: 1,
  transactionId: '<TRANSACTION_ID>'
);
```
```server-swift
try await documentsDB.incrementDocumentAttribute(
  databaseId: "<DATABASE_ID>",
  collectionId: "<COLLECTION_ID>",
  documentId: "<DOCUMENT_ID>",
  attribute: "likes",
  value: 1,
  transactionId: "<TRANSACTION_ID>"
)
```
```server-kotlin
documentsDB.incrementDocumentAttribute(
  databaseId = "<DATABASE_ID>",
  collectionId = "<COLLECTION_ID>",
  documentId = "<DOCUMENT_ID>",
  attribute = "likes",
  value = 1,
  transactionId = "<TRANSACTION_ID>"
)
```
```server-java
documentsDB.incrementDocumentAttribute(
  "<DATABASE_ID>",
  "<COLLECTION_ID>",
  "<DOCUMENT_ID>",
  "likes",
  1,
  "<TRANSACTION_ID>",
  new CoroutineCallback<>((result, error) -> {
    if (error != null) {
      error.printStackTrace();
      return null;
    }
    System.out.println(result);
    return null;
  })
);
```

#### Explore related features

- [Bulk operations](/docs/products/databases/documentsdb/bulk-operations) - Update multiple documents at once
- [Permissions](/docs/products/databases/documentsdb/permissions) - Control access to documents
- [Queries](/docs/products/databases/documentsdb/queries) - Find documents to update

---

## Backups

URL: https://appwrite.io/docs/products/databases/documentsdb/backups

Backups protect your DocumentsDB data by capturing a full copy of a database that you can restore later. Every backup is **encrypted** and taken as a **hot** backup, so your database keeps serving traffic with zero downtime and recovery stays fast.

**Backups are available for all Pro and Enterprise customers.**

You manage backups from a database's **Backups** tab, where you can automate backups with policies or create manual backups on demand.

![Backups tab](/images/docs/databases/documentsdb/backups-tab.avif)

### Backup policies

Backup policies automate your backups on a schedule. To create one, open your database's **Backups** tab and click **Create policy**, then choose a preset policy or add a custom one.

![Create backup policy](/images/docs/databases/documentsdb/backup-policy.avif)

The available options depend on your plan:

- On the **Pro** plan, you get a **Daily** backup policy retained for 7 days.
- On the **Enterprise** plan, you get access to additional preset policies and custom policies, where you control how often backups run and how long they are retained.

Click **Create** to save the policy. Your database is now set up for automated backups.

### Manual backups

You can create an on-demand backup whenever necessary. In your database's **Backups** tab, click **Manual backup**, then click **Create**.

![Manual backup](/images/docs/databases/documentsdb/manual-backup.avif)

Manual backups are retained until you delete them. Depending on the size of your database, the backup may take some time to complete. You can monitor its progress in the backups list.

### Restoring backups

To restore a database, you need an existing backup.

1. Open your database's **Backups** tab.
2. In the backups list, open the **Actions** menu for the backup you want to restore.
3. Click **Restore**.
4. Enter a name for the new database and an optional database ID.
5. Click **Restore**.

Depending on the size of your database, the restoration may take some time. The restore creates a new database from the backup, leaving the original untouched.

### Backup security & 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

To ensure your backups are robust and effective, consider the following best practices:

1. **Schedule regular backups**:
   Add backup policies based on the frequency of database changes. Daily 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.

---

## Bulk operations

URL: https://appwrite.io/docs/products/databases/documentsdb/bulk-operations

Appwrite DocumentsDB 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.

**Important notes**

Bulk operations trigger Functions, Webhooks, or Realtime events for each document manipulated. Rather than a single event for the entire bulk operation, each document generates a separate event on the existing realtime channels for its operation type.

### 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.

### Plan limits

Bulk operations have different limits based on your Appwrite plan:

| Plan | Documents per request |
|------|----------------------|
| Free | 100 |
| Pro | 1,000 |

These limits apply to all bulk operations including create, update, upsert, and delete operations. If you need higher limits than what the Pro plan offers, you can [inquire](/contact-us/enterprise) about a custom plan.

### Create documents

You can create multiple documents in a single request using the `createDocuments` method.

**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.

```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 documentsDB = new sdk.DocumentsDB(client);

const result = await documentsDB.createDocuments({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documents: [
        {
            $id: sdk.ID.unique(),
            name: 'Document 1'
        },
        {
            $id: sdk.ID.unique(),
            name: 'Document 2'
        }
    ]
});
```
```server-python
from appwrite.client import Client
from appwrite.services.documents_db import DocumentsDB
from appwrite.id import ID

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<API_KEY>')

documents_db = DocumentsDB(client)

result = documents_db.create_documents(
    database_id = '<DATABASE_ID>',
    collection_id = '<COLLECTION_ID>',
    documents = [
        {
            '$id': ID.unique(),
            'name': 'Document 1'
        },
        {
            '$id': ID.unique(),
            'name': 'Document 2'
        }
    ]
)
```
```server-deno
await documentsDB.createDocuments({
  databaseId: '<DATABASE_ID>',
  collectionId: '<COLLECTION_ID>',
  documents: [
    { $id: sdk.ID.unique(), name: 'Document 1' },
    { $id: sdk.ID.unique(), name: 'Document 2' }
  ]
});
```
```server-php
$documentsDB->createDocuments(
  databaseId: '<DATABASE_ID>',
  collectionId: '<COLLECTION_ID>',
  documents: [
    [ '$id' => ID::unique(), 'name' => 'Document 1' ],
    [ '$id' => ID::unique(), 'name' => 'Document 2' ]
  ]
);
```
```server-ruby
documents_db.create_documents(
  database_id: '<DATABASE_ID>',
  collection_id: '<COLLECTION_ID>',
  documents: [
    { '$id' => ID.unique(), 'name' => 'Document 1' },
    { '$id' => ID.unique(), 'name' => 'Document 2' }
  ]
)
```
```server-dotnet
await documentsDB.CreateDocuments(
  databaseId: "<DATABASE_ID>",
  collectionId: "<COLLECTION_ID>",
  documents: new List<Dictionary<string, object>>
  {
    new Dictionary<string, object>
    {
      ["$id"] = ID.Unique(),
      ["name"] = "Document 1"
    },
    new Dictionary<string, object>
    {
      ["$id"] = ID.Unique(),
      ["name"] = "Document 2"
    }
  }
);
```
```server-dart
await documentsDB.createDocuments(
  databaseId: '<DATABASE_ID>',
  collectionId: '<COLLECTION_ID>',
  documents: [
    { '\$id': ID.unique(), 'name': 'Document 1' },
    { '\$id': ID.unique(), 'name': 'Document 2' }
  ]
);
```
```server-swift
try await documentsDB.createDocuments(
  databaseId: "<DATABASE_ID>",
  collectionId: "<COLLECTION_ID>",
  documents: [
    ["$id": ID.unique(), "name": "Document 1"],
    ["$id": ID.unique(), "name": "Document 2"]
  ]
)
```
```server-kotlin
documentsDB.createDocuments(
  databaseId = "<DATABASE_ID>",
  collectionId = "<COLLECTION_ID>",
  documents = listOf(
    mapOf("\$id" to ID.unique(), "name" to "Document 1"),
    mapOf("\$id" to ID.unique(), "name" to "Document 2")
  )
)
```
```server-java
documentsDB.createDocuments(
  "<DATABASE_ID>",
  "<COLLECTION_ID>",
  Arrays.asList(
    Map.of(
      "$id", ID.unique(),
      "name", "Document 1"
    ),
    Map.of(
      "$id", ID.unique(),
      "name", "Document 2"
    )
  ),
  new CoroutineCallback<>((result, error) -> {
    if (error != null) {
      error.printStackTrace();
      return null;
    }
    System.out.println(result);
    return null;
  })
);
```
```rust
use appwrite::Client;
use appwrite::services::documents_db::DocumentsDB;
use appwrite::id::ID;
use serde_json::json;

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

    let documents_db = DocumentsDB::new(&client);

    let result = documents_db.create_documents(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
        vec![
            json!({
                "$id": ID::unique(),
                "name": "Document 1"
            }),
            json!({
                "$id": ID::unique(),
                "name": "Document 2"
            }),
        ],
        None,
    ).await?;

    Ok(())
}
```

### Update documents

**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/documentsdb/permissions)

You can update multiple documents in a single request using the `updateDocuments` method.

```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 documentsDB = new sdk.DocumentsDB(client);

const result = await documentsDB.updateDocuments({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    data: {
        status: 'published'
    },
    queries: [
        sdk.Query.equal('status', 'draft')
    ]
});
```

```server-python
from appwrite.client import Client
from appwrite.services.documents_db import DocumentsDB
from appwrite.query import Query

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<API_KEY>')

documents_db = DocumentsDB(client)

result = documents_db.update_documents(
    database_id = '<DATABASE_ID>',
    collection_id = '<COLLECTION_ID>',
    data = {
        'status': 'published'
    },
    queries = [
        Query.equal('status', 'draft')
    ]
)
```

```rust
use appwrite::Client;
use appwrite::services::documents_db::DocumentsDB;
use appwrite::query::Query;
use serde_json::json;

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

    let documents_db = DocumentsDB::new(&client);

    let result = documents_db.update_documents(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
        Some(json!({
            "status": "published"
        })),
        Some(vec![
            Query::equal("status", "draft").to_string(),
        ]),
        None,
    ).await?;

    Ok(())
}
```

### Upsert documents

**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/documentsdb/permissions)

You can upsert multiple documents in a single request using the `upsertDocuments` method.

```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 documentsDB = new sdk.DocumentsDB(client);

const result = await documentsDB.upsertDocuments({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documents: [
        {
            $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.documents_db import DocumentsDB
from appwrite.id import ID

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<API_KEY>')

documents_db = DocumentsDB(client)

result = documents_db.upsert_documents(
    database_id = '<DATABASE_ID>',
    collection_id = '<COLLECTION_ID>',
    documents = [
        {
            '$id': ID.unique(),
            'name': 'New Document 1'
        },
        {
            '$id': 'document-id-2',  # Existing document ID
            'name': 'New Document 2'
        }
    ]
)
```

```rust
use appwrite::Client;
use appwrite::services::documents_db::DocumentsDB;
use appwrite::id::ID;
use serde_json::json;

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

    let documents_db = DocumentsDB::new(&client);

    let result = documents_db.upsert_documents(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
        vec![
            json!({
                "$id": ID::unique(),
                "name": "New Document 1"
            }),
            json!({
                "$id": "document-id-2", // Existing document ID
                "name": "New Document 2"
            }),
        ],
        None,
    ).await?;

    Ok(())
}
```

### Delete documents

**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/documentsdb/permissions)

You can delete multiple documents in a single request using the `deleteDocuments` method.

```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 documentsDB = new sdk.DocumentsDB(client);

const result = await documentsDB.deleteDocuments({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    queries: [
        sdk.Query.equal('status', 'archived')
    ]
});
```

```server-python
from appwrite.client import Client
from appwrite.services.documents_db import DocumentsDB
from appwrite.query import Query

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<API_KEY>')

documents_db = DocumentsDB(client)

result = documents_db.delete_documents(
    database_id = '<DATABASE_ID>',
    collection_id = '<COLLECTION_ID>',
    queries = [
        Query.equal('status', 'archived')
    ]
)
```

```rust
use appwrite::Client;
use appwrite::services::documents_db::DocumentsDB;
use appwrite::query::Query;

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

    let documents_db = DocumentsDB::new(&client);

    let result = documents_db.delete_documents(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
        Some(vec![
            Query::equal("status", "archived").to_string(),
        ]),
        None,
    ).await?;

    Ok(())
}
```

**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/documentsdb/queries).

### Use transactions

All bulk operations accept `transactionId`. When provided, Appwrite stages the bulk request and applies it on commit. See [Transactions](/docs/products/databases/documentsdb/transactions).

```server-nodejs
await documentsDB.createDocuments({
  databaseId: '<DATABASE_ID>',
  collectionId: '<COLLECTION_ID>',
  documents: [
    { $id: sdk.ID.unique(), name: 'One' },
    { $id: sdk.ID.unique(), name: 'Two' }
  ],
  transactionId: '<TRANSACTION_ID>'
});
```
```server-python
documents_db.create_documents(
  database_id = '<DATABASE_ID>',
  collection_id = '<COLLECTION_ID>',
  documents = [
    { '$id': ID.unique(), 'name': 'One' },
    { '$id': ID.unique(), 'name': 'Two' }
  ],
  transaction_id = '<TRANSACTION_ID>'
)
```
```server-deno
await documentsDB.createDocuments({
  databaseId: '<DATABASE_ID>',
  collectionId: '<COLLECTION_ID>',
  documents: [
    { $id: sdk.ID.unique(), name: 'One' },
    { $id: sdk.ID.unique(), name: 'Two' }
  ],
  transactionId: '<TRANSACTION_ID>'
});
```
```server-php
$documentsDB->createDocuments(
  databaseId: '<DATABASE_ID>',
  collectionId: '<COLLECTION_ID>',
  documents: [
    [ '$id' => ID::unique(), 'name' => 'One' ],
    [ '$id' => ID::unique(), 'name' => 'Two' ]
  ],
  transactionId: '<TRANSACTION_ID>'
);
```
```server-ruby
documents_db.create_documents(
  database_id: '<DATABASE_ID>',
  collection_id: '<COLLECTION_ID>',
  documents: [
    { '$id' => ID.unique(), 'name' => 'One' },
    { '$id' => ID.unique(), 'name' => 'Two' }
  ],
  transaction_id: '<TRANSACTION_ID>'
)
```
```server-dotnet
await documentsDB.CreateDocuments(
  databaseId: "<DATABASE_ID>",
  collectionId: "<COLLECTION_ID>",
  documents: new List<Dictionary<string, object>>
  {
    new Dictionary<string, object>
    {
      ["$id"] = ID.Unique(),
      ["name"] = "One"
    },
    new Dictionary<string, object>
    {
      ["$id"] = ID.Unique(),
      ["name"] = "Two"
    }
  },
  transactionId: "<TRANSACTION_ID>"
);
```
```server-dart
await documentsDB.createDocuments(
  databaseId: '<DATABASE_ID>',
  collectionId: '<COLLECTION_ID>',
  documents: [
    { '\$id': ID.unique(), 'name': 'One' },
    { '\$id': ID.unique(), 'name': 'Two' }
  ],
  transactionId: '<TRANSACTION_ID>'
);
```
```server-swift
try await documentsDB.createDocuments(
  databaseId: "<DATABASE_ID>",
  collectionId: "<COLLECTION_ID>",
  documents: [
    ["$id": ID.unique(), "name": "One"],
    ["$id": ID.unique(), "name": "Two"]
  ],
  transactionId: "<TRANSACTION_ID>"
)
```
```server-kotlin
documentsDB.createDocuments(
  databaseId = "<DATABASE_ID>",
  collectionId = "<COLLECTION_ID>",
  documents = listOf(
    mapOf("\$id" to ID.unique(), "name" to "One"),
    mapOf("\$id" to ID.unique(), "name" to "Two")
  ),
  transactionId = "<TRANSACTION_ID>"
)
```
```server-java
documentsDB.createDocuments(
  "<DATABASE_ID>",
  "<COLLECTION_ID>",
  Arrays.asList(
    Map.of(
      "$id", ID.unique(),
      "name", "One"
    ),
    Map.of(
      "$id", ID.unique(),
      "name", "Two"
    )
  ),
  "<TRANSACTION_ID>",
  new CoroutineCallback<>((result, error) -> {
    if (error != null) {
      error.printStackTrace();
      return null;
    }
    System.out.println(result);
    return null;
  })
);
```
```rust
let result = documents_db.create_documents(
    "<DATABASE_ID>",
    "<COLLECTION_ID>",
    vec![
        json!({
            "$id": ID::unique(),
            "name": "One"
        }),
        json!({
            "$id": ID::unique(),
            "name": "Two"
        }),
    ],
    Some("<TRANSACTION_ID>"),
).await?;
```

---

## Collections

URL: https://appwrite.io/docs/products/databases/documentsdb/collections

Appwrite uses collections as containers of documents.
Collections are schemaless, so documents in the same collection can hold different fields. You shape data in your application instead of defining columns up front.

### 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).

**Console**

Head to the **Databases** page, open a [database](/docs/products/databases/documentsdb/databases), and click **Create collection**.

![Create collection dialog](/images/docs/databases/documentsdb/create-collection.avif)

**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).

```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 documentsDB = new sdk.DocumentsDB(client);

const result = await documentsDB.createCollection({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    name: '<NAME>',
    permissions: [sdk.Permission.read(sdk.Role.any())], // optional
    documentSecurity: false // 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('<YOUR_PROJECT_ID>') // Your project ID
    .setKey('<YOUR_API_KEY>'); // Your secret API key

const documentsDB = new sdk.DocumentsDB(client);

const result = await documentsDB.createCollection({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    name: '<NAME>',
    permissions: [sdk.Permission.read(sdk.Role.any())], // optional
    documentSecurity: false // optional
});
```
```php
<?php

use Appwrite\Client;
use Appwrite\Services\DocumentsDB;
use Appwrite\Permission;
use Appwrite\Role;

$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

$documentsDB = new DocumentsDB($client);

$result = $documentsDB->createCollection(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    name: '<NAME>',
    permissions: [Permission::read(Role::any())], // optional
    documentSecurity: false // optional
);
```
```python
from appwrite.client import Client
from appwrite.services.documents_db import DocumentsDB
from appwrite.permission import Permission
from appwrite.role import Role

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

documents_db = DocumentsDB(client)

result = documents_db.create_collection(
    database_id = '<DATABASE_ID>',
    collection_id = '<COLLECTION_ID>',
    name = '<NAME>',
    permissions = [Permission.read(Role.any())], # optional
    document_security = False # optional
)
```
```ruby
require 'appwrite'

include Appwrite
include Appwrite::Permission
include Appwrite::Role

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

documents_db = DocumentsDB.new(client)

result = documents_db.create_collection(
    database_id: '<DATABASE_ID>',
    collection_id: '<COLLECTION_ID>',
    name: '<NAME>',
    permissions: [Permission.read(Role.any())], # optional
    document_security: false # optional
)
```
```csharp
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

DocumentsDB documentsDB = new DocumentsDB(client);

Collection result = await documentsDB.CreateCollection(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    name: "<NAME>",
    permissions: new List<string> { Permission.Read(Role.Any()) }, // optional
    documentSecurity: false // optional
);
```
```dart
import 'package:dart_appwrite/dart_appwrite.dart';
import 'package:dart_appwrite/permission.dart';
import 'package:dart_appwrite/role.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

DocumentsDB documentsDB = DocumentsDB(client);

Collection result = await documentsDB.createCollection(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    name: '<NAME>',
    permissions: [Permission.read(Role.any())], // (optional)
    documentSecurity: false, // (optional)
);
```
```kotlin
import io.appwrite.Client
import io.appwrite.Permission
import io.appwrite.Role
import io.appwrite.services.DocumentsDB

val client = Client(context)
    .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 documentsDB = DocumentsDB(client)

val response = documentsDB.createCollection(
    databaseId = "<DATABASE_ID>",
    collectionId = "<COLLECTION_ID>",
    name = "<NAME>",
    permissions = listOf(Permission.read(Role.any())), // optional
    documentSecurity = false, // optional
)
```
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.DocumentsDB;

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

DocumentsDB documentsDB = new DocumentsDB(client);

documentsDB.createCollection(
    "<DATABASE_ID>",
    "<COLLECTION_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("<YOUR_PROJECT_ID>") // Your project ID
    .setKey("<YOUR_API_KEY>") // Your secret API key

let documentsDB = DocumentsDB(client)

let collection = try await documentsDB.createCollection(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    name: "<NAME>",
    permissions: [Permission.read(Role.any())], // optional
    documentSecurity: false // optional
)
```
```server-rust
use appwrite::Client;
use appwrite::services::DocumentsDB;
use appwrite::permission::Permission;
use appwrite::role::Role;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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

    let documents_db = DocumentsDB::new(&client);

    let result = documents_db.create_collection(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
        "<NAME>",
        Some(vec![Permission::read(Role::any()).to_string()]), // permissions (optional)
        Some(false), // documentSecurity (optional)
        None, // enabled (optional)
        None, // attributes (optional)
        None, // indexes (optional)
    ).await?;

    println!("{:?}", result);
    Ok(())
}
```

### Permissions
Appwrite uses permissions to control data access.
For security, only users that are granted permissions can access a resource.

By default, Appwrite doesn't grant permissions to any users when a new collection is created.
This means users can't create documents or read, update, and delete existing documents until you grant access.

Set `documentSecurity` to `true` on a collection to configure permissions on individual documents. A user then needs either collection level or document level permissions to access a document.

[Learn about configuring permissions](/docs/products/databases/documentsdb/permissions).

### Indexes
Databases use indexes to quickly locate data without scanning every document.
To ensure the best performance, Appwrite recommends an index for every field you query.
If you plan to query multiple fields in a single query, creating an index with **all** queried fields 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 text fields. Required for the [search query method](/docs/products/databases/documentsdb/queries). |

You can create an index by navigating to your collection's **Indexes** tab or by using your favorite [Server SDK](/docs/sdks#server).

```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 documentsDB = new sdk.DocumentsDB(client);

const result = await documentsDB.createIndex({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    key: 'title_index',
    type: sdk.DocumentsDBIndexType.Key,
    attributes: ['title']
});
```
```deno
import * as sdk from "npm: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 documentsDB = new sdk.DocumentsDB(client);

const result = await documentsDB.createIndex({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    key: 'title_index',
    type: sdk.DocumentsDBIndexType.Key,
    attributes: ['title']
});
```
```php
<?php

use Appwrite\Client;
use Appwrite\Services\DocumentsDB;
use Appwrite\Enums\DocumentsDBIndexType;

$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

$documentsDB = new DocumentsDB($client);

$result = $documentsDB->createIndex(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    key: 'title_index',
    type: DocumentsDBIndexType::KEY(),
    attributes: ['title']
);
```
```python
from appwrite.client import Client
from appwrite.services.documents_db import DocumentsDB
from appwrite.enums import DocumentsDBIndexType

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

documents_db = DocumentsDB(client)

result = documents_db.create_index(
    database_id = '<DATABASE_ID>',
    collection_id = '<COLLECTION_ID>',
    key = 'title_index',
    type = DocumentsDBIndexType.KEY,
    attributes = ['title']
)
```
```ruby
require 'appwrite'

include Appwrite
include Appwrite::Enums

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

documents_db = DocumentsDB.new(client)

result = documents_db.create_index(
    database_id: '<DATABASE_ID>',
    collection_id: '<COLLECTION_ID>',
    key: 'title_index',
    type: DocumentsDBIndexType::KEY,
    attributes: ['title']
)
```
```csharp
using Appwrite;
using Appwrite.Enums;
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

DocumentsDB documentsDB = new DocumentsDB(client);

Index result = await documentsDB.CreateIndex(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    key: "title_index",
    type: DocumentsDBIndexType.Key,
    attributes: new List<string> { "title" }
);
```
```dart
import 'package:dart_appwrite/dart_appwrite.dart';
import 'package:dart_appwrite/enums.dart' as enums;

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

DocumentsDB documentsDB = DocumentsDB(client);

Index result = await documentsDB.createIndex(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    key: 'title_index',
    type: enums.DocumentsDBIndexType.key,
    attributes: ['title'],
);
```
```kotlin
import io.appwrite.Client
import io.appwrite.enums.DocumentsDBIndexType
import io.appwrite.services.DocumentsDB

val client = Client(context)
    .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 documentsDB = DocumentsDB(client)

val response = documentsDB.createIndex(
    databaseId = "<DATABASE_ID>",
    collectionId = "<COLLECTION_ID>",
    key = "title_index",
    type = DocumentsDBIndexType.KEY,
    attributes = listOf("title"),
)
```
```java
import io.appwrite.Client;
import io.appwrite.enums.DocumentsDBIndexType;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.DocumentsDB;
import java.util.List;

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

DocumentsDB documentsDB = new DocumentsDB(client);

documentsDB.createIndex(
    "<DATABASE_ID>",
    "<COLLECTION_ID>",
    "title_index",
    DocumentsDBIndexType.KEY,
    List.of("title"),
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        System.out.println(result);
    })
);
```
```swift
import Appwrite
import AppwriteEnums

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 documentsDB = DocumentsDB(client)

let index = try await documentsDB.createIndex(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    key: "title_index",
    type: .key,
    attributes: ["title"]
)
```
```server-rust
use appwrite::Client;
use appwrite::services::DocumentsDB;
use appwrite::enums::DocumentsDBIndexType;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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

    let documents_db = DocumentsDB::new(&client);

    let result = documents_db.create_index(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
        "title_index",
        DocumentsDBIndexType::Key,
        vec!["title"],
        None, // orders (optional)
        None, // lengths (optional)
    ).await?;

    println!("{:?}", result);
    Ok(())
}
```

---

## Databases

URL: https://appwrite.io/docs/products/databases/documentsdb/databases

Databases are the largest organizational unit in Appwrite.
Each database contains a group of [collections](/docs/products/databases/documentsdb/collections).

A database runs on shared or dedicated infrastructure; see [shared and dedicated databases](/docs/products/databases#shared-and-dedicated).

### Create in Console
The easiest way to create a database is using the Appwrite Console.
Navigate to the **Databases** page and click **Create database**, choose **DocumentsDB** as the database type, and select your preferred tier.

![Create database](/images/docs/databases/documentsdb/create-database.avif)

### 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).

```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 documentsDB = new sdk.DocumentsDB(client);

const result = await documentsDB.create({
    databaseId: '<DATABASE_ID>',
    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('<YOUR_PROJECT_ID>') // Your project ID
    .setKey('<YOUR_API_KEY>'); // Your secret API key

const documentsDB = new sdk.DocumentsDB(client);

const result = await documentsDB.create({
    databaseId: '<DATABASE_ID>',
    name: '<NAME>'
});
```
```php
<?php

use Appwrite\Client;
use Appwrite\Services\DocumentsDB;

$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

$documentsDB = new DocumentsDB($client);

$result = $documentsDB->create(
    databaseId: '<DATABASE_ID>',
    name: '<NAME>'
);
```
```python
from appwrite.client import Client
from appwrite.services.documents_db import DocumentsDB

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

documents_db = DocumentsDB(client)

result = documents_db.create(
    database_id = '<DATABASE_ID>',
    name = '<NAME>'
)
```
```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

documents_db = DocumentsDB.new(client)

result = documents_db.create(
    database_id: '<DATABASE_ID>',
    name: '<NAME>'
)
```
```csharp
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

DocumentsDB documentsDB = new DocumentsDB(client);

Database result = await documentsDB.Create(
    databaseId: "<DATABASE_ID>",
    name: "<NAME>"
);
```
```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

DocumentsDB documentsDB = DocumentsDB(client);

Database result = await documentsDB.create(
    databaseId: '<DATABASE_ID>',
    name: '<NAME>',
);
```
```kotlin
import io.appwrite.Client
import io.appwrite.services.DocumentsDB

val client = Client(context)
    .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 documentsDB = DocumentsDB(client)

val response = documentsDB.create(
    databaseId = "<DATABASE_ID>",
    name = "<NAME>",
)
```
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.DocumentsDB;

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

DocumentsDB documentsDB = new DocumentsDB(client);

documentsDB.create(
    "<DATABASE_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("<YOUR_PROJECT_ID>") // Your project ID
    .setKey("<YOUR_API_KEY>") // Your secret API key

let documentsDB = DocumentsDB(client)

let database = try await documentsDB.create(
    databaseId: "<DATABASE_ID>",
    name: "<NAME>"
)
```
```server-rust
use appwrite::Client;
use appwrite::services::DocumentsDB;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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

    let documents_db = DocumentsDB::new(&client);

    let result = documents_db.create(
        "<DATABASE_ID>",
        "<NAME>",
        None, // enabled (optional)
        None, // dedicatedDatabaseId (optional)
    ).await?;

    println!("{:?}", result);
    Ok(())
}
```

### List databases
Use the `list` method to retrieve all databases in your project.

```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 documentsDB = new sdk.DocumentsDB(client);

const result = await documentsDB.list({});
```
```deno
import * as sdk from "npm: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 documentsDB = new sdk.DocumentsDB(client);

const result = await documentsDB.list({});
```
```php
<?php

use Appwrite\Client;
use Appwrite\Services\DocumentsDB;

$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

$documentsDB = new DocumentsDB($client);

$result = $documentsDB->list();
```
```python
from appwrite.client import Client
from appwrite.services.documents_db import DocumentsDB

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

documents_db = DocumentsDB(client)

result = documents_db.list()
```
```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

documents_db = DocumentsDB.new(client)

result = documents_db.list()
```
```csharp
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

DocumentsDB documentsDB = new DocumentsDB(client);

DatabaseList result = await documentsDB.List();
```
```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

DocumentsDB documentsDB = DocumentsDB(client);

DatabaseList result = await documentsDB.list();
```
```kotlin
import io.appwrite.Client
import io.appwrite.services.DocumentsDB

val client = Client(context)
    .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 documentsDB = DocumentsDB(client)

val response = documentsDB.list()
```
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.DocumentsDB;

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

DocumentsDB documentsDB = new DocumentsDB(client);

documentsDB.list(
    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("<YOUR_PROJECT_ID>") // Your project ID
    .setKey("<YOUR_API_KEY>") // Your secret API key

let documentsDB = DocumentsDB(client)

let databaseList = try await documentsDB.list()
```
```server-rust
use appwrite::Client;
use appwrite::services::DocumentsDB;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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

    let documents_db = DocumentsDB::new(&client);

    let result = documents_db.list(
        None, // queries (optional)
        None, // total (optional)
    ).await?;

    println!("{:?}", result);
    Ok(())
}
```
```bash
appwrite documentsdb list
```

### Get a database
Use the `get` method to retrieve a single database by its ID.

```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 documentsDB = new sdk.DocumentsDB(client);

const result = await documentsDB.get({
    databaseId: '<DATABASE_ID>'
});
```
```deno
import * as sdk from "npm: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 documentsDB = new sdk.DocumentsDB(client);

const result = await documentsDB.get({
    databaseId: '<DATABASE_ID>'
});
```
```php
<?php

use Appwrite\Client;
use Appwrite\Services\DocumentsDB;

$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

$documentsDB = new DocumentsDB($client);

$result = $documentsDB->get(
    databaseId: '<DATABASE_ID>'
);
```
```python
from appwrite.client import Client
from appwrite.services.documents_db import DocumentsDB

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

documents_db = DocumentsDB(client)

result = documents_db.get(
    database_id = '<DATABASE_ID>'
)
```
```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

documents_db = DocumentsDB.new(client)

result = documents_db.get(
    database_id: '<DATABASE_ID>'
)
```
```csharp
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

DocumentsDB documentsDB = new DocumentsDB(client);

Database result = await documentsDB.Get(
    databaseId: "<DATABASE_ID>"
);
```
```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

DocumentsDB documentsDB = DocumentsDB(client);

Database result = await documentsDB.get(
    databaseId: '<DATABASE_ID>',
);
```
```kotlin
import io.appwrite.Client
import io.appwrite.services.DocumentsDB

val client = Client(context)
    .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 documentsDB = DocumentsDB(client)

val response = documentsDB.get(
    databaseId = "<DATABASE_ID>",
)
```
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.DocumentsDB;

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

DocumentsDB documentsDB = new DocumentsDB(client);

documentsDB.get(
    "<DATABASE_ID>",
    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("<YOUR_PROJECT_ID>") // Your project ID
    .setKey("<YOUR_API_KEY>") // Your secret API key

let documentsDB = DocumentsDB(client)

let database = try await documentsDB.get(
    databaseId: "<DATABASE_ID>"
)
```
```server-rust
use appwrite::Client;
use appwrite::services::DocumentsDB;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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

    let documents_db = DocumentsDB::new(&client);

    let result = documents_db.get(
        "<DATABASE_ID>",
    ).await?;

    println!("{:?}", result);
    Ok(())
}
```
```bash
appwrite documentsdb get \
    --database-id <DATABASE_ID>
```

### Update a database
Use the `update` method to change a database's name or enabled state.

```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 documentsDB = new sdk.DocumentsDB(client);

const result = await documentsDB.update({
    databaseId: '<DATABASE_ID>',
    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('<YOUR_PROJECT_ID>') // Your project ID
    .setKey('<YOUR_API_KEY>'); // Your secret API key

const documentsDB = new sdk.DocumentsDB(client);

const result = await documentsDB.update({
    databaseId: '<DATABASE_ID>',
    name: '<NAME>'
});
```
```php
<?php

use Appwrite\Client;
use Appwrite\Services\DocumentsDB;

$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

$documentsDB = new DocumentsDB($client);

$result = $documentsDB->update(
    databaseId: '<DATABASE_ID>',
    name: '<NAME>'
);
```
```python
from appwrite.client import Client
from appwrite.services.documents_db import DocumentsDB

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

documents_db = DocumentsDB(client)

result = documents_db.update(
    database_id = '<DATABASE_ID>',
    name = '<NAME>'
)
```
```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

documents_db = DocumentsDB.new(client)

result = documents_db.update(
    database_id: '<DATABASE_ID>',
    name: '<NAME>'
)
```
```csharp
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

DocumentsDB documentsDB = new DocumentsDB(client);

Database result = await documentsDB.Update(
    databaseId: "<DATABASE_ID>",
    name: "<NAME>"
);
```
```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

DocumentsDB documentsDB = DocumentsDB(client);

Database result = await documentsDB.update(
    databaseId: '<DATABASE_ID>',
    name: '<NAME>',
);
```
```kotlin
import io.appwrite.Client
import io.appwrite.services.DocumentsDB

val client = Client(context)
    .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 documentsDB = DocumentsDB(client)

val response = documentsDB.update(
    databaseId = "<DATABASE_ID>",
    name = "<NAME>",
)
```
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.DocumentsDB;

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

DocumentsDB documentsDB = new DocumentsDB(client);

documentsDB.update(
    "<DATABASE_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("<YOUR_PROJECT_ID>") // Your project ID
    .setKey("<YOUR_API_KEY>") // Your secret API key

let documentsDB = DocumentsDB(client)

let database = try await documentsDB.update(
    databaseId: "<DATABASE_ID>",
    name: "<NAME>"
)
```
```server-rust
use appwrite::Client;
use appwrite::services::DocumentsDB;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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

    let documents_db = DocumentsDB::new(&client);

    let result = documents_db.update(
        "<DATABASE_ID>",
        "<NAME>",
        None, // enabled (optional)
        None, // specification (optional)
        None, // replicas (optional)
        None, // sync_mode (optional)
    ).await?;

    println!("{:?}", result);
    Ok(())
}
```
```bash
appwrite documentsdb update \
    --database-id <DATABASE_ID> \
    --name <NAME>
```

### Delete a database
Use the `delete` method to permanently remove a database and all of its collections and documents.

```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 documentsDB = new sdk.DocumentsDB(client);

const result = await documentsDB.delete({
    databaseId: '<DATABASE_ID>'
});
```
```deno
import * as sdk from "npm: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 documentsDB = new sdk.DocumentsDB(client);

const result = await documentsDB.delete({
    databaseId: '<DATABASE_ID>'
});
```
```php
<?php

use Appwrite\Client;
use Appwrite\Services\DocumentsDB;

$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

$documentsDB = new DocumentsDB($client);

$result = $documentsDB->delete(
    databaseId: '<DATABASE_ID>'
);
```
```python
from appwrite.client import Client
from appwrite.services.documents_db import DocumentsDB

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

documents_db = DocumentsDB(client)

result = documents_db.delete(
    database_id = '<DATABASE_ID>'
)
```
```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

documents_db = DocumentsDB.new(client)

result = documents_db.delete(
    database_id: '<DATABASE_ID>'
)
```
```csharp
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

DocumentsDB documentsDB = new DocumentsDB(client);

await documentsDB.Delete(
    databaseId: "<DATABASE_ID>"
);
```
```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

DocumentsDB documentsDB = DocumentsDB(client);

await documentsDB.delete(
    databaseId: '<DATABASE_ID>',
);
```
```kotlin
import io.appwrite.Client
import io.appwrite.services.DocumentsDB

val client = Client(context)
    .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 documentsDB = DocumentsDB(client)

documentsDB.delete(
    databaseId = "<DATABASE_ID>",
)
```
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.DocumentsDB;

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

DocumentsDB documentsDB = new DocumentsDB(client);

documentsDB.delete(
    "<DATABASE_ID>",
    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("<YOUR_PROJECT_ID>") // Your project ID
    .setKey("<YOUR_API_KEY>") // Your secret API key

let documentsDB = DocumentsDB(client)

try await documentsDB.delete(
    databaseId: "<DATABASE_ID>"
)
```
```server-rust
use appwrite::Client;
use appwrite::services::DocumentsDB;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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

    let documents_db = DocumentsDB::new(&client);

    documents_db.delete(
        "<DATABASE_ID>",
    ).await?;

    Ok(())
}
```
```bash
appwrite documentsdb delete \
    --database-id <DATABASE_ID>
```

---

## Documents

URL: https://appwrite.io/docs/products/databases/documentsdb/documents

Each piece of data in Appwrite DocumentsDB is a document.
Documents are schemaless JSON, so each document in a collection can hold its own set of fields.

### Create documents

**Permissions required**

You must grant _create_ permissions to users at the _collection level_ before users can create documents.
[Learn more about permissions](#permissions)

Use the `createDocument` method to add a document to a collection. Appwrite [Server SDKs](/docs/sdks#server) require an [API key](/docs/advanced/platform/api-keys).

```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 documentsDB = new sdk.DocumentsDB(client);

const result = await documentsDB.createDocument({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: sdk.ID.unique(),
    data: { title: 'Hamlet', year: 1601 },
    permissions: [sdk.Permission.read(sdk.Role.any())] // 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('<YOUR_PROJECT_ID>') // Your project ID
    .setKey('<YOUR_API_KEY>'); // Your secret API key

const documentsDB = new sdk.DocumentsDB(client);

const result = await documentsDB.createDocument({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: sdk.ID.unique(),
    data: { title: 'Hamlet', year: 1601 },
    permissions: [sdk.Permission.read(sdk.Role.any())] // optional
});
```
```php
<?php

use Appwrite\Client;
use Appwrite\Services\DocumentsDB;
use Appwrite\ID;
use Appwrite\Permission;
use Appwrite\Role;

$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

$documentsDB = new DocumentsDB($client);

$result = $documentsDB->createDocument(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: ID::unique(),
    data: ['title' => 'Hamlet', 'year' => 1601],
    permissions: [Permission::read(Role::any())] // optional
);
```
```python
from appwrite.client import Client
from appwrite.services.documents_db import DocumentsDB
from appwrite.id import ID
from appwrite.permission import Permission
from appwrite.role import Role

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

documents_db = DocumentsDB(client)

result = documents_db.create_document(
    database_id = '<DATABASE_ID>',
    collection_id = '<COLLECTION_ID>',
    document_id = ID.unique(),
    data = { "title": "Hamlet", "year": 1601 },
    permissions = [Permission.read(Role.any())] # optional
)
```
```ruby
require 'appwrite'

include Appwrite
include Appwrite::Permission
include Appwrite::Role

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

documents_db = DocumentsDB.new(client)

result = documents_db.create_document(
    database_id: '<DATABASE_ID>',
    collection_id: '<COLLECTION_ID>',
    document_id: ID.unique(),
    data: { "title" => "Hamlet", "year" => 1601 },
    permissions: [Permission.read(Role.any())] # optional
)
```
```csharp
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

DocumentsDB documentsDB = new DocumentsDB(client);

Document result = await documentsDB.CreateDocument(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    documentId: ID.Unique(),
    data: new { title = "Hamlet", year = 1601 },
    permissions: new List<string> { Permission.Read(Role.Any()) } // optional
);
```
```dart
import 'package:dart_appwrite/dart_appwrite.dart';
import 'package:dart_appwrite/permission.dart';
import 'package:dart_appwrite/role.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

DocumentsDB documentsDB = DocumentsDB(client);

Document result = await documentsDB.createDocument(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: ID.unique(),
    data: { "title": "Hamlet", "year": 1601 },
    permissions: [Permission.read(Role.any())], // (optional)
);
```
```kotlin
import io.appwrite.Client
import io.appwrite.ID
import io.appwrite.Permission
import io.appwrite.Role
import io.appwrite.services.DocumentsDB

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 documentsDB = DocumentsDB(client)

val response = documentsDB.createDocument(
    databaseId = "<DATABASE_ID>",
    collectionId = "<COLLECTION_ID>",
    documentId = ID.unique(),
    data = mapOf("title" to "Hamlet", "year" to 1601),
    permissions = listOf(Permission.read(Role.any())), // optional
)
```
```java
import io.appwrite.Client;
import io.appwrite.ID;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.DocumentsDB;
import java.util.Map;

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

DocumentsDB documentsDB = new DocumentsDB(client);

documentsDB.createDocument(
    "<DATABASE_ID>",
    "<COLLECTION_ID>",
    ID.unique(),
    Map.of("title", "Hamlet", "year", 1601),
    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("<YOUR_PROJECT_ID>") // Your project ID
    .setKey("<YOUR_API_KEY>") // Your secret API key

let documentsDB = DocumentsDB(client)

let document = try await documentsDB.createDocument(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    documentId: ID.unique(),
    data: ["title": "Hamlet", "year": 1601],
    permissions: [Permission.read(Role.any())] // optional
)
```
```server-rust
use appwrite::Client;
use appwrite::services::DocumentsDB;
use appwrite::id::ID;
use appwrite::permission::Permission;
use appwrite::role::Role;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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

    let documents_db = DocumentsDB::new(&client);

    let result = documents_db.create_document(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
        ID::unique(),
        serde_json::json!({ "title": "Hamlet", "year": 1601 }),
        Some(vec![Permission::read(Role::any()).to_string()]), // optional
    ).await?;

    println!("{:?}", result);
    Ok(())
}
```
```bash
appwrite documentsdb create-document \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --document-id 'unique()' \
    --data '{ "title": "Hamlet", "year": 1601 }'
```

### Get document
Use the `getDocument` method to read a single document by its ID.

```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 documentsDB = new sdk.DocumentsDB(client);

const result = await documentsDB.getDocument({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>'
});
```
```deno
import * as sdk from "npm: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 documentsDB = new sdk.DocumentsDB(client);

const result = await documentsDB.getDocument({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>'
});
```
```php
<?php

use Appwrite\Client;
use Appwrite\Services\DocumentsDB;

$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

$documentsDB = new DocumentsDB($client);

$result = $documentsDB->getDocument(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>'
);
```
```python
from appwrite.client import Client
from appwrite.services.documents_db import DocumentsDB

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

documents_db = DocumentsDB(client)

result = documents_db.get_document(
    database_id = '<DATABASE_ID>',
    collection_id = '<COLLECTION_ID>',
    document_id = '<DOCUMENT_ID>'
)
```
```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

documents_db = DocumentsDB.new(client)

result = documents_db.get_document(
    database_id: '<DATABASE_ID>',
    collection_id: '<COLLECTION_ID>',
    document_id: '<DOCUMENT_ID>'
)
```
```csharp
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

DocumentsDB documentsDB = new DocumentsDB(client);

Document result = await documentsDB.GetDocument(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    documentId: "<DOCUMENT_ID>"
);
```
```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

DocumentsDB documentsDB = DocumentsDB(client);

Document result = await documentsDB.getDocument(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>',
);
```
```kotlin
import io.appwrite.Client
import io.appwrite.services.DocumentsDB

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 documentsDB = DocumentsDB(client)

val response = documentsDB.getDocument(
    databaseId = "<DATABASE_ID>",
    collectionId = "<COLLECTION_ID>",
    documentId = "<DOCUMENT_ID>",
)
```
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.DocumentsDB;

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

DocumentsDB documentsDB = new DocumentsDB(client);

documentsDB.getDocument(
    "<DATABASE_ID>",
    "<COLLECTION_ID>",
    "<DOCUMENT_ID>",
    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("<YOUR_PROJECT_ID>") // Your project ID
    .setKey("<YOUR_API_KEY>") // Your secret API key

let documentsDB = DocumentsDB(client)

let document = try await documentsDB.getDocument(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    documentId: "<DOCUMENT_ID>"
)
```
```server-rust
use appwrite::Client;
use appwrite::services::DocumentsDB;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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

    let documents_db = DocumentsDB::new(&client);

    let result = documents_db.get_document(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
        "<DOCUMENT_ID>",
        None, // queries (optional)
        None, // transactionId (optional)
    ).await?;

    println!("{:?}", result);
    Ok(())
}
```
```bash
appwrite documentsdb get-document \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --document-id <DOCUMENT_ID>
```

### List documents
Use the `listDocuments` method to read documents from a collection. Pass [queries](/docs/products/databases/documentsdb/queries) to filter, order, and paginate the results.

**Cache list responses**

Pass a `ttl` value in seconds (1 to 86400) to cache the response. Identical requests return the cached result until the TTL expires, and the `X-Appwrite-Cache` header reports `hit` or `miss`. See [cache list responses](/docs/products/databases/documentsdb/pagination#cache-list-responses).

```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 documentsDB = new sdk.DocumentsDB(client);

const result = await documentsDB.listDocuments({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    queries: [
        sdk.Query.equal('title', 'Hamlet')
    ]
});
```
```deno
import * as sdk from "npm: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 documentsDB = new sdk.DocumentsDB(client);

const result = await documentsDB.listDocuments({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    queries: [
        sdk.Query.equal('title', 'Hamlet')
    ]
});
```
```php
<?php

use Appwrite\Client;
use Appwrite\Services\DocumentsDB;
use Appwrite\Query;

$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

$documentsDB = new DocumentsDB($client);

$result = $documentsDB->listDocuments(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    queries: [
        Query::equal('title', ['Hamlet'])
    ]
);
```
```python
from appwrite.client import Client
from appwrite.services.documents_db import DocumentsDB
from appwrite.query import Query

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

documents_db = DocumentsDB(client)

result = documents_db.list_documents(
    database_id = '<DATABASE_ID>',
    collection_id = '<COLLECTION_ID>',
    queries = [
        Query.equal('title', 'Hamlet')
    ]
)
```
```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

documents_db = DocumentsDB.new(client)

result = documents_db.list_documents(
    database_id: '<DATABASE_ID>',
    collection_id: '<COLLECTION_ID>',
    queries: [
        Query.equal('title', ['Hamlet'])
    ]
)
```
```csharp
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

DocumentsDB documentsDB = new DocumentsDB(client);

DocumentList result = await documentsDB.ListDocuments(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    queries: new List<string> {
        Query.Equal("title", new List<object> { "Hamlet" })
    }
);
```
```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

DocumentsDB documentsDB = DocumentsDB(client);

DocumentList result = await documentsDB.listDocuments(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    queries: [
        Query.equal('title', 'Hamlet')
    ],
);
```
```kotlin
import io.appwrite.Client
import io.appwrite.Query
import io.appwrite.services.DocumentsDB

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 documentsDB = DocumentsDB(client)

val response = documentsDB.listDocuments(
    databaseId = "<DATABASE_ID>",
    collectionId = "<COLLECTION_ID>",
    queries = listOf(
        Query.equal("title", "Hamlet")
    ),
)
```
```java
import io.appwrite.Client;
import io.appwrite.Query;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.DocumentsDB;
import java.util.List;

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

DocumentsDB documentsDB = new DocumentsDB(client);

documentsDB.listDocuments(
    "<DATABASE_ID>",
    "<COLLECTION_ID>",
    List.of(
        Query.equal("title", List.of("Hamlet"))
    ),
    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("<YOUR_PROJECT_ID>") // Your project ID
    .setKey("<YOUR_API_KEY>") // Your secret API key

let documentsDB = DocumentsDB(client)

let documentList = try await documentsDB.listDocuments(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    queries: [
        Query.equal("title", value: "Hamlet")
    ]
)
```
```server-rust
use appwrite::Client;
use appwrite::services::DocumentsDB;
use appwrite::query::Query;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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

    let documents_db = DocumentsDB::new(&client);

    let result = documents_db.list_documents(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
        Some(vec![Query::equal("title", vec!["Hamlet"])]),
        None, // transactionId (optional)
        None, // total (optional)
        None, // ttl (optional)
    ).await?;

    println!("{:?}", result);
    Ok(())
}
```
```bash
appwrite documentsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"equal","attribute":"title","values":["Hamlet"]}'
```

### Update document
Use the `updateDocument` method to update a document. With the patch behavior, you only pass the fields you want to change.

```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 documentsDB = new sdk.DocumentsDB(client);

const result = await documentsDB.updateDocument({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>',
    data: { year: 1602 }
});
```
```deno
import * as sdk from "npm: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 documentsDB = new sdk.DocumentsDB(client);

const result = await documentsDB.updateDocument({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>',
    data: { year: 1602 }
});
```
```php
<?php

use Appwrite\Client;
use Appwrite\Services\DocumentsDB;

$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

$documentsDB = new DocumentsDB($client);

$result = $documentsDB->updateDocument(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>',
    data: ['year' => 1602]
);
```
```python
from appwrite.client import Client
from appwrite.services.documents_db import DocumentsDB

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

documents_db = DocumentsDB(client)

result = documents_db.update_document(
    database_id = '<DATABASE_ID>',
    collection_id = '<COLLECTION_ID>',
    document_id = '<DOCUMENT_ID>',
    data = { "year": 1602 }
)
```
```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

documents_db = DocumentsDB.new(client)

result = documents_db.update_document(
    database_id: '<DATABASE_ID>',
    collection_id: '<COLLECTION_ID>',
    document_id: '<DOCUMENT_ID>',
    data: { "year" => 1602 }
)
```
```csharp
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

DocumentsDB documentsDB = new DocumentsDB(client);

Document result = await documentsDB.UpdateDocument(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    documentId: "<DOCUMENT_ID>",
    data: new { year = 1602 }
);
```
```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

DocumentsDB documentsDB = DocumentsDB(client);

Document result = await documentsDB.updateDocument(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>',
    data: { "year": 1602 },
);
```
```kotlin
import io.appwrite.Client
import io.appwrite.services.DocumentsDB

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 documentsDB = DocumentsDB(client)

val response = documentsDB.updateDocument(
    databaseId = "<DATABASE_ID>",
    collectionId = "<COLLECTION_ID>",
    documentId = "<DOCUMENT_ID>",
    data = mapOf("year" to 1602),
)
```
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.DocumentsDB;
import java.util.Map;

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

DocumentsDB documentsDB = new DocumentsDB(client);

documentsDB.updateDocument(
    "<DATABASE_ID>",
    "<COLLECTION_ID>",
    "<DOCUMENT_ID>",
    Map.of("year", 1602),
    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("<YOUR_PROJECT_ID>") // Your project ID
    .setKey("<YOUR_API_KEY>") // Your secret API key

let documentsDB = DocumentsDB(client)

let document = try await documentsDB.updateDocument(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    documentId: "<DOCUMENT_ID>",
    data: ["year": 1602]
)
```
```server-rust
use appwrite::Client;
use appwrite::services::DocumentsDB;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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

    let documents_db = DocumentsDB::new(&client);

    let result = documents_db.update_document(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
        "<DOCUMENT_ID>",
        Some(serde_json::json!({ "year": 1602 })),
        None, // permissions (optional)
        None, // transactionId (optional)
    ).await?;

    println!("{:?}", result);
    Ok(())
}
```
```bash
appwrite documentsdb update-document \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --document-id <DOCUMENT_ID> \
    --data '{ "year": 1602 }'
```

### Upsert documents
Use the `upsertDocument` method to create a document if it doesn't exist, or update it if it does.

```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 documentsDB = new sdk.DocumentsDB(client);

const result = await documentsDB.upsertDocument({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>',
    data: { title: 'Hamlet', year: 1603 }
});
```
```deno
import * as sdk from "npm: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 documentsDB = new sdk.DocumentsDB(client);

const result = await documentsDB.upsertDocument({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>',
    data: { title: 'Hamlet', year: 1603 }
});
```
```php
<?php

use Appwrite\Client;
use Appwrite\Services\DocumentsDB;

$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

$documentsDB = new DocumentsDB($client);

$result = $documentsDB->upsertDocument(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>',
    data: ['title' => 'Hamlet', 'year' => 1603]
);
```
```python
from appwrite.client import Client
from appwrite.services.documents_db import DocumentsDB

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

documents_db = DocumentsDB(client)

result = documents_db.upsert_document(
    database_id = '<DATABASE_ID>',
    collection_id = '<COLLECTION_ID>',
    document_id = '<DOCUMENT_ID>',
    data = { "title": "Hamlet", "year": 1603 }
)
```
```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

documents_db = DocumentsDB.new(client)

result = documents_db.upsert_document(
    database_id: '<DATABASE_ID>',
    collection_id: '<COLLECTION_ID>',
    document_id: '<DOCUMENT_ID>',
    data: { "title" => "Hamlet", "year" => 1603 }
)
```
```csharp
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

DocumentsDB documentsDB = new DocumentsDB(client);

Document result = await documentsDB.UpsertDocument(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    documentId: "<DOCUMENT_ID>",
    data: new { title = "Hamlet", year = 1603 }
);
```
```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

DocumentsDB documentsDB = DocumentsDB(client);

Document result = await documentsDB.upsertDocument(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>',
    data: { "title": "Hamlet", "year": 1603 },
);
```
```kotlin
import io.appwrite.Client
import io.appwrite.services.DocumentsDB

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 documentsDB = DocumentsDB(client)

val response = documentsDB.upsertDocument(
    databaseId = "<DATABASE_ID>",
    collectionId = "<COLLECTION_ID>",
    documentId = "<DOCUMENT_ID>",
    data = mapOf("title" to "Hamlet", "year" to 1603),
)
```
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.DocumentsDB;
import java.util.Map;

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

DocumentsDB documentsDB = new DocumentsDB(client);

documentsDB.upsertDocument(
    "<DATABASE_ID>",
    "<COLLECTION_ID>",
    "<DOCUMENT_ID>",
    Map.of("title", "Hamlet", "year", 1603),
    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("<YOUR_PROJECT_ID>") // Your project ID
    .setKey("<YOUR_API_KEY>") // Your secret API key

let documentsDB = DocumentsDB(client)

let document = try await documentsDB.upsertDocument(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    documentId: "<DOCUMENT_ID>",
    data: ["title": "Hamlet", "year": 1603]
)
```
```server-rust
use appwrite::Client;
use appwrite::services::DocumentsDB;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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

    let documents_db = DocumentsDB::new(&client);

    let result = documents_db.upsert_document(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
        "<DOCUMENT_ID>",
        Some(serde_json::json!({ "title": "Hamlet", "year": 1603 })),
        None, // permissions (optional)
        None, // transactionId (optional)
    ).await?;

    println!("{:?}", result);
    Ok(())
}
```
```bash
appwrite documentsdb upsert-document \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --document-id <DOCUMENT_ID> \
    --data '{ "title": "Hamlet", "year": 1603 }'
```

### Delete document
Use the `deleteDocument` method to remove a document from a collection.

```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 documentsDB = new sdk.DocumentsDB(client);

const result = await documentsDB.deleteDocument({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>'
});
```
```deno
import * as sdk from "npm: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 documentsDB = new sdk.DocumentsDB(client);

const result = await documentsDB.deleteDocument({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>'
});
```
```php
<?php

use Appwrite\Client;
use Appwrite\Services\DocumentsDB;

$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

$documentsDB = new DocumentsDB($client);

$result = $documentsDB->deleteDocument(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>'
);
```
```python
from appwrite.client import Client
from appwrite.services.documents_db import DocumentsDB

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

documents_db = DocumentsDB(client)

result = documents_db.delete_document(
    database_id = '<DATABASE_ID>',
    collection_id = '<COLLECTION_ID>',
    document_id = '<DOCUMENT_ID>'
)
```
```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

documents_db = DocumentsDB.new(client)

result = documents_db.delete_document(
    database_id: '<DATABASE_ID>',
    collection_id: '<COLLECTION_ID>',
    document_id: '<DOCUMENT_ID>'
)
```
```csharp
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

DocumentsDB documentsDB = new DocumentsDB(client);

await documentsDB.DeleteDocument(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    documentId: "<DOCUMENT_ID>"
);
```
```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

DocumentsDB documentsDB = DocumentsDB(client);

await documentsDB.deleteDocument(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>',
);
```
```kotlin
import io.appwrite.Client
import io.appwrite.services.DocumentsDB

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 documentsDB = DocumentsDB(client)

documentsDB.deleteDocument(
    databaseId = "<DATABASE_ID>",
    collectionId = "<COLLECTION_ID>",
    documentId = "<DOCUMENT_ID>",
)
```
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.DocumentsDB;

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

DocumentsDB documentsDB = new DocumentsDB(client);

documentsDB.deleteDocument(
    "<DATABASE_ID>",
    "<COLLECTION_ID>",
    "<DOCUMENT_ID>",
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        System.out.println("Deleted");
    })
);
```
```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 documentsDB = DocumentsDB(client)

try await documentsDB.deleteDocument(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    documentId: "<DOCUMENT_ID>"
)
```
```server-rust
use appwrite::Client;
use appwrite::services::DocumentsDB;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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

    let documents_db = DocumentsDB::new(&client);

    documents_db.delete_document(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
        "<DOCUMENT_ID>",
        None, // transactionId (optional)
    ).await?;

    Ok(())
}
```
```bash
appwrite documentsdb delete-document \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --document-id <DOCUMENT_ID>
```

### Permissions
To access documents through a [Client SDK](/docs/sdks#client), you must grant the relevant permissions.
By default, Appwrite doesn't grant any user permissions when a new collection is created.

You can configure permissions at the collection level, or enable `documentSecurity` on the collection to also set permissions on individual documents.

[Learn about configuring permissions](/docs/products/databases/documentsdb/permissions).

---

## JSON exports

URL: https://appwrite.io/docs/products/databases/documentsdb/json-exports

Appwrite's JSON Export feature allows you to export documents from a collection to a JSON file. This is especially useful for creating custom backups, sharing data with other teams, or moving datasets to another system.

### Export configuration

Before exporting, you can configure a few options to control the contents of the output. These settings let you export exactly the data you need.

#### Select fields

You can choose which fields to include in your export. By default, every field is exported, but selecting specific fields creates cleaner, more focused datasets that are easier to work with.

**Good to know**

System fields like `$id`, `$permissions`, `$createdAt`, and `$updatedAt` are always included in the export.

#### Filter documents

You can apply queries to export only the documents you need. This is especially useful when you want to export a subset of a collection for a specific use case.

### Export format

Each document is exported as a JSON object that includes its system fields and data fields. The output file is an array of these objects.

An example of exported data:

```json
[
  {
    "$id": "book-1",
    "$permissions": ["read(\"any\")"],
    "$createdAt": "2025-08-10T12:34:56.000Z",
    "$updatedAt": "2025-08-10T12:34:56.000Z",
    "title": "Harry Potter and the Sorcerer's Stone",
    "author": "J.K. Rowling",
    "year": 1997,
    "available": true
  }
]
```

### Timestamps

The `$createdAt` and `$updatedAt` fields are exported in ISO 8601 format, making them compatible with most tools that consume JSON.

### Permissions

If document level security is enabled for your collection, the `$permissions` field contains the permission strings for each document. Permission strings are formatted as an array of role definitions.

The roles used are API strings that can be found in the [permissions documentation](/docs/apis/rest#roles).

### Background processing

Large exports run as background tasks to avoid blocking your workflow. When an export completes, you'll receive an email with a short-lived download link to retrieve your JSON file.

This means you can start an export, close the Console, and return later to download your file. The Console displays a floating progress bar while the export is active.

### Export documents from the Console

![JSON export from the collection toolbar](/images/docs/databases/documentsdb-import-export.avif)

To export documents using the Appwrite Console:

1. Go to your project and navigate to **Databases**
2. Select your database and open the target collection
3. Click the export (download) icon in the collection toolbar
4. Configure your export options:
   - Choose which fields to include (optional)
   - Apply queries to filter documents (optional)
5. Start the export

The export begins processing in the background. You'll see a progress indicator and receive an email when the export is ready to download.

### Use cases

JSON exports are useful for many common workflows:

- **Custom backups**: Archive specific data subsets for record-keeping
- **Data sharing**: Hand off datasets to other teams
- **Migration preparation**: Extract data for migration to other systems
- **Reporting**: Feed data into tools that consume JSON

### Additional resources

- [JSON imports](/docs/products/databases/documentsdb/json-imports)
- [Documents](/docs/products/databases/documentsdb/documents)
- [Database permissions](/docs/products/databases/documentsdb/permissions)

---

## JSON imports

URL: https://appwrite.io/docs/products/databases/documentsdb/json-imports

Appwrite's JSON Import feature allows you to create multiple documents in a collection by uploading a single JSON file. This is especially useful for importing existing data, seeding test environments, or migrating from other systems.

### Prepare your JSON file

Your JSON file is an array of objects, where each object becomes one document in the collection. DocumentsDB is schemaless, so each object can hold its own set of fields.

An example of a valid JSON file:

```json
[
  {
    "title": "Harry Potter and the Sorcerer's Stone",
    "author": "J.K. Rowling",
    "year": 1997,
    "available": true
  },
  {
    "title": "The Fellowship of the Ring",
    "author": "J.R.R. Tolkien",
    "year": 1954,
    "available": true
  }
]
```

Each object is validated before being imported.

### Custom document IDs

You can include a `$id` field on any object to set a custom document ID. If you omit it, Appwrite generates a unique ID for the document automatically.

```json
[
  {
    "$id": "book-1",
    "title": "Harry Potter and the Sorcerer's Stone",
    "author": "J.K. Rowling"
  }
]
```

### Handle duplicate IDs

When an imported document uses a `$id` that already exists in the collection, Appwrite decides what to do based on the duplicate handling you choose:

- **Fail** (default): the import stops at the first conflicting ID.
- **Skip**: conflicting documents are ignored, and the rest are imported.
- **Overwrite**: existing documents are replaced with the imported version.

### Permissions

You can set document level permissions by including a `$permissions` field with an array of permission strings. Make sure document level security is enabled for your collection.

```json
[
  {
    "$id": "book-1",
    "$permissions": ["read(\"any\")", "update(\"users\")", "delete(\"user:user_id\")"],
    "title": "Harry Potter and the Sorcerer's Stone"
  }
]
```

The roles used are API strings that can be found in the [permissions documentation](/docs/apis/rest#roles).

### Import documents from the Console

![JSON import from the collection toolbar](/images/docs/databases/documentsdb-import-export.avif)

To import documents using the Appwrite Console:

1. Go to your project and navigate to **Databases**
2. Select your database and open the target collection
3. Click the import (upload) icon in the collection toolbar
4. Upload a new JSON file or choose an existing file from your Storage bucket

JSON imports run as background tasks. The Console displays a floating progress bar while the import is active.

### Additional resources

- [JSON exports](/docs/products/databases/documentsdb/json-exports)
- [Documents](/docs/products/databases/documentsdb/documents)
- [Database permissions](/docs/products/databases/documentsdb/permissions)

---

## Order

URL: https://appwrite.io/docs/products/databases/documentsdb/order

You can order results returned by Appwrite DocumentsDB by using an order query.
For best performance, create an [index](/docs/products/databases/documentsdb/collections#indexes) on the field you plan to order by.

### Ordering one field

When querying using the [listDocuments](/docs/products/databases/documentsdb/documents#list-documents) endpoint,
you can specify the order of the documents returned using the `Query.orderAsc()` and `Query.orderDesc()` query methods.

```client-web
import { Client, Query, DocumentsDB } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

const documentsDB = new DocumentsDB(client);

documentsDB.listDocuments({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    queries: [
        Query.orderAsc('title'),
    ]
});
```

```client-flutter
import 'package:appwrite/appwrite.dart';

void main() async {
    final client = Client()
        .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
        .setProject('<PROJECT_ID>');

    final documentsDB = DocumentsDB(client);

    try {
        final documents = await documentsDB.listDocuments(
            databaseId: '<DATABASE_ID>',
            collectionId: '<COLLECTION_ID>',
            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://<REGION>.cloud.appwrite.io/v1')
        .setProject('<PROJECT_ID>');

    let documentsDB = DocumentsDB(client)

    do {
        let documents = try await documentsDB.listDocuments(
            databaseId: "<DATABASE_ID>",
            collectionId: "<COLLECTION_ID>",
            queries: [
                Query.orderAsc("title")
            ]
        )
    } catch {
        print(error.localizedDescription)
    }
}
```
```client-android-kotlin
import io.appwrite.Client
import io.appwrite.Query
import io.appwrite.services.DocumentsDB

suspend fun main() {
    val client = Client(applicationContext)
        .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
        .setProject('<PROJECT_ID>');

    val documentsDB = DocumentsDB(client)

    try {
        val documents = documentsDB.listDocuments(
            databaseId = "<DATABASE_ID>",
            collectionId = "<COLLECTION_ID>",
            queries = [
                Query.orderAsc("title")
            ]
        )
    } catch (e: AppwriteException) {
        Log.e("Appwrite", e.message)
    }
}
```

```graphql
query {
    documentsDBListDocuments(
        databaseId: "<DATABASE_ID>",
        collectionId: "<COLLECTION_ID>"
        queries: ["orderAsc(\"title\")"]
    ) {
        total
        documents {
            _id
            data
        }
    }
}
```

### Multiple fields
To sort based on multiple fields, simply provide multiple query methods.
For better performance, create an index on the first field 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.

```js
// Web SDK code example for sorting based on multiple fields
// ...

// List documents and sort based on multiple fields
documentsDB.listDocuments({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    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 fields
// ...

// List documents and sort based on multiple fields
try {
    final documents = await documentsDB.listDocuments(
        databaseId: '<DATABASE_ID>',
        collectionId: '<COLLECTION_ID>',
        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 fields
// ...

// List documents and sort based on multiple fields
try {
    val documents = documentsDB.listDocuments(
        databaseId = "<DATABASE_ID>",
        collectionId = "<COLLECTION_ID>",
        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 fields
// ...

// List documents and sort based on multiple fields
do {
    let documents = try await documentsDB.listDocuments(
        databaseId: "<DATABASE_ID>",
        collectionId: "<COLLECTION_ID>",
        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 {
    documentsDBListDocuments(
        databaseId: "<DATABASE_ID>",
        collectionId: "<COLLECTION_ID>",
        queries: ["orderAsc(\"title\")", "orderDesc(\"year\")"]
    ) {
        total
        documents {
            _id
            data
        }
    }
}
```

### Ordering by sequence

For ordering based on insertion order, you can use the `$sequence` field, which Appwrite automatically adds to all documents. Sorting by `$sequence` returns documents in the order they were created.

```client-web
import { Client, Query, DocumentsDB } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

const documentsDB = new DocumentsDB(client);

documentsDB.listDocuments({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    queries: [
        Query.orderAsc('$sequence'),
    ]
});
```

```client-flutter
import 'package:appwrite/appwrite.dart';

void main() async {
    final client = Client()
        .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
        .setProject('<PROJECT_ID>');

    final documentsDB = DocumentsDB(client);

    try {
        final documents = await documentsDB.listDocuments(
            databaseId: '<DATABASE_ID>',
            collectionId: '<COLLECTION_ID>',
            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://<REGION>.cloud.appwrite.io/v1")
        .setProject("<PROJECT_ID>")

    let documentsDB = DocumentsDB(client)

    do {
        let documents = try await documentsDB.listDocuments(
            databaseId: "<DATABASE_ID>",
            collectionId: "<COLLECTION_ID>",
            queries: [
                Query.orderAsc("$sequence")
            ]
        )
    } catch {
        print(error.localizedDescription)
    }
}
```

```client-android-kotlin
import io.appwrite.Client
import io.appwrite.Query
import io.appwrite.services.DocumentsDB

suspend fun main() {
    val client = Client(applicationContext)
        .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
        .setProject("<PROJECT_ID>")

    val documentsDB = DocumentsDB(client)

    try {
        val documents = documentsDB.listDocuments(
            databaseId = "<DATABASE_ID>",
            collectionId = "<COLLECTION_ID>",
            queries = listOf(
                Query.orderAsc("\$sequence")
            )
        )
    } catch (e: AppwriteException) {
        Log.e("Appwrite", e.message)
    }
}
```

```graphql
query {
    documentsDBListDocuments(
        databaseId: "<DATABASE_ID>",
        collectionId: "<COLLECTION_ID>"
        queries: ["orderAsc(\"$sequence\")"]
    ) {
        total
        documents {
            _id
            data
        }
    }
}
```

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 insertion-order sorting without managing custom counter fields

---

## Pagination

URL: https://appwrite.io/docs/products/databases/documentsdb/pagination

As your collection grows in size, you'll need to paginate the documents returned.
Pagination improves performance by returning a subset of documents that match a query at a time, called a page.

By default, list operations return 25 documents per page, which can be changed using the `Query.limit()` query method.
There is no hard limit on the number of documents you can request. However, beware that **large pages can degrade performance**.

### Offset pagination

Offset pagination divides documents into pages of `N` documents each.
To read page number `P`, skip `offset = N * (P - 1)` documents, then read the next `N`.

Using `Query.limit()` and `Query.offset()` you can achieve offset pagination.
With `Query.limit()` you define how many documents can be returned from one request.
The `Query.offset()` is the number of documents you wish to skip before selecting documents.

```client-web
import { Client, Query, DocumentsDB } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

const documentsDB = new DocumentsDB(client);

// Page 1
const page1 = await documentsDB.listDocuments({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    queries: [
        Query.limit(25),
        Query.offset(0)
    ]
});

// Page 2
const page2 = await documentsDB.listDocuments({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    queries: [
        Query.limit(25),
        Query.offset(25)
    ]
});
```
```client-flutter
import 'package:appwrite/appwrite.dart';

void main() async {
    final client = Client()
        .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
        .setProject('<PROJECT_ID>');

    final documentsDB = DocumentsDB(client);

    final page1 = await documentsDB.listDocuments(
        databaseId: '<DATABASE_ID>',
        collectionId: '<COLLECTION_ID>',
        queries: [
            Query.limit(25),
            Query.offset(0)
        ]
    );

    final page2 = await documentsDB.listDocuments(
        databaseId: '<DATABASE_ID>',
        collectionId: '<COLLECTION_ID>',
        queries: [
            Query.limit(25),
            Query.offset(25)
        ]
    );
}
```
```client-apple
import Appwrite
import AppwriteModels

func main() async throws {
    let client = Client()
        .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
        .setProject("<PROJECT_ID>")

    let documentsDB = DocumentsDB(client)

    let page1 = try await documentsDB.listDocuments(
        databaseId: "<DATABASE_ID>",
        collectionId: "<COLLECTION_ID>",
        queries: [
            Query.limit(25),
            Query.offset(0)
        ]
    )

    let page2 = try await documentsDB.listDocuments(
        databaseId: "<DATABASE_ID>",
        collectionId: "<COLLECTION_ID>",
        queries: [
            Query.limit(25),
            Query.offset(25)
        ]
    )
}
```
```client-android-kotlin
import io.appwrite.Client
import io.appwrite.Query
import io.appwrite.services.DocumentsDB

suspend fun main() {
    val client = Client(applicationContext)
        .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
        .setProject("<PROJECT_ID>")

    val documentsDB = DocumentsDB(client)

    val page1 = documentsDB.listDocuments(
        databaseId = "<DATABASE_ID>",
        collectionId = "<COLLECTION_ID>",
        queries = listOf(
            Query.limit(25),
            Query.offset(0)
        )
    )

    val page2 = documentsDB.listDocuments(
        databaseId = "<DATABASE_ID>",
        collectionId = "<COLLECTION_ID>",
        queries = listOf(
            Query.limit(25),
            Query.offset(25)
        )
    )
}
```

**Drawbacks**

While traditional offset pagination is familiar, it comes with some drawbacks.
The request gets slower as the offset increases because the database has to skip over all the preceding documents before it can start selecting data.
If the data changes frequently, offset pagination will also produce **missing and duplicate** results.

### 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.

```client-web
import { Client, Query, DocumentsDB } from "appwrite";

const client = new Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>");

const documentsDB = new DocumentsDB(client);

// Page 1
const page1 = await documentsDB.listDocuments({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    queries: [
        Query.limit(25)
    ]
});

const lastId = page1.documents[page1.documents.length - 1].$id;

// Page 2
const page2 = await documentsDB.listDocuments({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    queries: [
        Query.limit(25),
        Query.cursorAfter(lastId)
    ]
});
```

```client-flutter
import 'package:appwrite/appwrite.dart';

void main() async {
    final client = Client()
        .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
        .setProject('<PROJECT_ID>');

    final documentsDB = DocumentsDB(client);

    final page1 = await documentsDB.listDocuments(
        databaseId: '<DATABASE_ID>',
        collectionId: '<COLLECTION_ID>',
        queries: [
            Query.limit(25)
        ]
    );

    final lastId = page1.documents[page1.documents.length - 1].$id;

    final page2 = await documentsDB.listDocuments(
        databaseId: '<DATABASE_ID>',
        collectionId: '<COLLECTION_ID>',
        queries: [
            Query.limit(25),
            Query.cursorAfter(lastId)
        ]
    );
}
```
```client-apple
import Appwrite
import AppwriteModels

func main() async throws {
    let client = Client()
      .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
      .setProject("<PROJECT_ID>")

    let documentsDB = DocumentsDB(client)

    let page1 = try await documentsDB.listDocuments(
        databaseId: "<DATABASE_ID>",
        collectionId: "<COLLECTION_ID>",
        queries: [
            Query.limit(25)
        ]
    )

    let lastId = page1.documents[page1.documents.count - 1].$id

    let page2 = try await documentsDB.listDocuments(
        databaseId: "<DATABASE_ID>",
        collectionId: "<COLLECTION_ID>",
        queries: [
            Query.limit(25),
            Query.cursorAfter(lastId)
        ]
    )
}
```
```client-android-kotlin
import io.appwrite.Client
import io.appwrite.Query
import io.appwrite.services.DocumentsDB

suspend fun main() {
    val client = Client(applicationContext)
        .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
        .setProject("<PROJECT_ID>")

    val documentsDB = DocumentsDB(client)

    val page1 = documentsDB.listDocuments(
        databaseId = "<DATABASE_ID>",
        collectionId = "<COLLECTION_ID>",
        queries = listOf(
            Query.limit(25)
        )
    )

    val lastId = page1.documents[page1.documents.size - 1].$id

    val page2 = documentsDB.listDocuments(
        databaseId = "<DATABASE_ID>",
        collectionId = "<COLLECTION_ID>",
        queries = listOf(
            Query.limit(25),
            Query.cursorAfter(lastId)
        )
    )
}
```

### When to use what?
Offset pagination should be used for collections that rarely change.
Offset pagination lets you build an indicator of the current page number and the total page count.
For example, a list with up to 20 pages or static data like a list of countries or currencies.
Using offset pagination on large 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.

### Cache list responses

You can cache list responses by passing a `ttl` (time-to-live) value in seconds. Subsequent identical requests return the cached result until the TTL expires. The cache is permission-aware, so users with different roles never see each other's cached data.

Set `ttl` between `1` and `86400` (24 hours). The default is `0` (caching disabled). The response includes an `X-Appwrite-Cache` header with value `hit` or `miss`.

```client-web
import { Client, Query, DocumentsDB } from "appwrite";

const client = new Client()
  .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
  .setProject('<PROJECT_ID>');

const documentsDB = new DocumentsDB(client);

const page = await documentsDB.listDocuments({
  databaseId: '<DATABASE_ID>',
  collectionId: '<COLLECTION_ID>',
  queries: [
    Query.limit(25)
  ],
  ttl: 60 // Cache for 60 seconds
});
```
```server-nodejs
const sdk = require('node-appwrite');

const client = new sdk.Client()
  .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
  .setProject('<PROJECT_ID>')
  .setKey('<YOUR_API_KEY>');

const documentsDB = new sdk.DocumentsDB(client);

const page = await documentsDB.listDocuments({
  databaseId: '<DATABASE_ID>',
  collectionId: '<COLLECTION_ID>',
  queries: [
    sdk.Query.limit(25)
  ],
  ttl: 60 // Cache for 60 seconds
});
```
```server-python
from appwrite.client import Client
from appwrite.services.documents_db import DocumentsDB
from appwrite.query import Query

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

documents_db = DocumentsDB(client)

page = documents_db.list_documents(
    database_id='<DATABASE_ID>',
    collection_id='<COLLECTION_ID>',
    queries=[
        Query.limit(25)
    ],
    ttl=60  # Cache for 60 seconds
)
```
```server-ruby
require 'appwrite'

client = Appwrite::Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

documents_db = Appwrite::DocumentsDB.new(client)

page = documents_db.list_documents(
    database_id: '<DATABASE_ID>',
    collection_id: '<COLLECTION_ID>',
    queries: [
        Appwrite::Query.limit(25)
    ],
    ttl: 60  # Cache for 60 seconds
)
```
```server-deno
import { Client, Query, DocumentsDB } from "https://deno.land/x/appwrite/mod.ts";

const client = new Client()
  .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
  .setProject('<PROJECT_ID>')
  .setKey('<YOUR_API_KEY>');

const documentsDB = new DocumentsDB(client);

const page = await documentsDB.listDocuments({
  databaseId: '<DATABASE_ID>',
  collectionId: '<COLLECTION_ID>',
  queries: [
    Query.limit(25)
  ],
  ttl: 60 // Cache for 60 seconds
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Query;
use Appwrite\Services\DocumentsDB;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$documentsDB = new DocumentsDB($client);

$page = $documentsDB->listDocuments(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    queries: [
        Query::limit(25)
    ],
    ttl: 60 // Cache for 60 seconds
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

void main() async {
    final client = Client()
        .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
        .setProject('<PROJECT_ID>')
        .setKey('<YOUR_API_KEY>');

    final documentsDB = DocumentsDB(client);

    final page = await documentsDB.listDocuments(
        databaseId: '<DATABASE_ID>',
        collectionId: '<COLLECTION_ID>',
        queries: [
            Query.limit(25)
        ],
        ttl: 60 // Cache for 60 seconds
    );
}
```
```server-swift
import Appwrite
import AppwriteModels

func main() async throws {
    let client = Client()
        .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
        .setProject("<PROJECT_ID>")
        .setKey("<YOUR_API_KEY>")

    let documentsDB = DocumentsDB(client)

    let page = try await documentsDB.listDocuments(
        databaseId: "<DATABASE_ID>",
        collectionId: "<COLLECTION_ID>",
        queries: [
            Query.limit(25)
        ],
        ttl: 60 // Cache for 60 seconds
    )
}
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.Query
import io.appwrite.services.DocumentsDB

suspend fun main() {
    val client = Client()
        .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
        .setProject("<PROJECT_ID>")
        .setKey("<YOUR_API_KEY>")

    val documentsDB = DocumentsDB(client)

    val page = documentsDB.listDocuments(
        databaseId = "<DATABASE_ID>",
        collectionId = "<COLLECTION_ID>",
        queries = listOf(
            Query.limit(25)
        ),
        ttl = 60 // Cache for 60 seconds
    )
}
```
```server-rust
use appwrite::Client;
use appwrite::services::documents_db::DocumentsDB;
use appwrite::query::Query;

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

    let documents_db = DocumentsDB::new(&client);

    let page = documents_db.list_documents(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
        Some(vec![
            Query::limit(25).to_string(),
        ]),
        None,        // transaction_id
        None,        // total
        Some(60),    // ttl - Cache for 60 seconds
    ).await?;

    println!("{:?}", page);
    Ok(())
}
```
```graphql
query {
    documentsDBListDocuments(
        databaseId: "<DATABASE_ID>",
        collectionId: "<COLLECTION_ID>",
        queries: ["limit(25)"],
        ttl: 60
    ) {
        total
        documents {
            _id
            data
        }
    }
}
```
```http
GET /v1/documentsdb/<DATABASE_ID>/collections/<COLLECTION_ID>/documents?ttl=60 HTTP/1.1
Content-Type: application/json
X-Appwrite-Project: <PROJECT_ID>
```

Document writes do **not** invalidate the cache, so cached responses may contain stale data until the TTL expires. Use a short TTL for collections that change often, or skip caching entirely when you always need the latest documents.

---

## Database permissions

URL: https://appwrite.io/docs/products/databases/documentsdb/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 receives 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 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** > **Security** > **Permissions**.

![Collection permissions in the Security tab](/images/docs/databases/documentsdb/security-permissions.avif)

[Learn more about permissions and roles](/docs/advanced/platform/permissions)

### 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 level security is enabled in the security settings of your collection.
Enable document level permissions by navigating to **Your collection** > **Security** > **Document level security**.

![Document level security toggle in the Security tab](/images/docs/databases/documentsdb/security-rls.avif)

Document level permissions are configured on individual documents.

[Learn more about permissions and roles](/docs/advanced/platform/permissions)

### 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

URL: https://appwrite.io/docs/products/databases/documentsdb/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

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

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`.

**Default pagination behavior**

By default, results are limited to the **first 25 items**.
You can change this through [pagination](/docs/products/databases/documentsdb/pagination).

```client-web
import { Client, Query, DocumentsDB } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

const documentsDB = new DocumentsDB(client);

documentsDB.listDocuments({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    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 documentsDB = new sdk.DocumentsDB(client);

client
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>')
;

const promise = documentsDB.listDocuments({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    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://<REGION>.cloud.appwrite.io/v1')
        .setProject('<PROJECT_ID>');

    final documentsDB = DocumentsDB(client);

    try {
        final documents = await documentsDB.listDocuments(
            '<DATABASE_ID>',
            '<COLLECTION_ID>',
            [
                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://<REGION>.cloud.appwrite.io/v1")
        .setProject("<PROJECT_ID>")

    let documentsDB = DocumentsDB(client)

    do {
        let documents = try await documentsDB.listDocuments(
            databaseId: "<DATABASE_ID>",
            collectionId: "<COLLECTION_ID>",
            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.DocumentsDB

suspend fun main() {
    val client = Client(applicationContext)
        .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
        .setProject('<PROJECT_ID>');

    val documentsDB = DocumentsDB(client)

    try {
        val documents = documentsDB.listDocuments(
            databaseId = "<DATABASE_ID>",
            collectionId = "<COLLECTION_ID>",
            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://<REGION>.cloud.appwrite.io/v1"),
        appwrite.WithProject("<PROJECT_ID>"),
        appwrite.WithKey("<API_KEY>"),
    )

    documentsDB := appwrite.NewDocumentsDB(client)

    documents, err := documentsDB.ListDocuments(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
        documentsDB.WithListDocumentsQueries([]string{
            query.Equal("title", []string{"Avatar", "Lord of the Rings"}),
            query.GreaterThan("year", 1999),
        }),
    )
    if err != nil {
        log.Fatal(err)
    }

    fmt.Printf("Documents: %+v\n", documents)
}
```
```server-rust
use appwrite::Client;
use appwrite::services::documents_db::DocumentsDB;
use appwrite::query::Query;
use serde_json::Value;

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

    let documents_db = DocumentsDB::new(&client);

    let documents = documents_db.list_documents(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
        Some(vec![
            Query::equal("title", Value::Array(vec![
                Value::String("Avatar".to_string()),
                Value::String("Lord of the Rings".to_string()),
            ])).to_string(),
            Query::greater_than("year", 1999).to_string(),
        ]),
        None,
        None,
        None,
    ).await?;

    println!("{:?}", documents);
    Ok(())
}
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Query;
use Appwrite\Services\DocumentsDB;

$client = new Client();

$client
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>')
;

$documentsDB = new DocumentsDB($client);

$result = $documentsDB->listDocuments(
    '<DATABASE_ID>',
    '<COLLECTION_ID>',
    [
        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.documents_db import DocumentsDB

client = Client()

(client
  .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
  .set_project('<PROJECT_ID>')
  .set_key('<YOUR_API_KEY>')
)

documentsDB = DocumentsDB(client)

result = documentsDB.list_documents(
    '<DATABASE_ID>',
    '<COLLECTION_ID>',
    [
        Query.equal('title', ['Avatar', 'Lord of the Rings']),
        Query.greater_than('year', 1999)
    ]
)
```
```graphql
query {
    documentsDBListDocuments(
        databaseId: "<DATABASE_ID>",
        collectionId: "<COLLECTION_ID>"
        queries: [
            "{\"method\":\"equal\",\"attribute\":\"title\",\"values\":[\"Avatar\",\"Lord of the Rings\"]}",
            "{\"method\":\"greaterThan\",\"attribute\":\"year\",\"values\":[1999]}"
        ]
    ) {
        total
        documents {
            _id
            data
        }
    }
}
```
```http
GET /v1/documentsdb/<DATABASE_ID>/collections/<COLLECTION_ID>/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: <PROJECT_ID>
```
```bash
appwrite documentsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"equal","attribute":"title","values":["Avatar","Lord of the Rings"]}' \
    --queries '{"method":"greaterThan","attribute":"year","values":[1999]}'
```

### Query operators

#### Select

The `select` operator allows you to specify which fields should be returned from a document. This optimizes response size and retrieves only the data you need.

```client-web
Query.select(["name", "title"])
```
```client-flutter
Query.select(["name", "title"])
```
```client-react-native
Query.select(["name", "title"])
```
```client-apple
Query.select(["name", "title"])
```
```client-android-kotlin
Query.select(listOf("name", "title"))
```
```client-android-java
Query.select(Arrays.asList("name", "title"))
```
```server-python
Query.select(["name", "title"])
```
```server-ruby
Query.select(["name", "title"])
```
```server-deno
Query.select(["name", "title"])
```
```server-nodejs
Query.select(["name", "title"])
```
```server-php
Query::select(["name", "title"])
```
```server-dotnet
Query.Select(new List<string> { "name", "title" })
```
```server-go
query.Select([]string{"name", "title"})
```
```server-dart
Query.select(["name", "title"])
```
```server-swift
Query.select(["name", "title"])
```
```server-kotlin
Query.select(listOf("name", "title"))
```
```server-rust
Query::select(vec!["name", "title"]).to_string()
```
```bash
appwrite documentsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"select","values":["name","title"]}'
```
```http
{"method":"select","values":["name","title"]}
```

##### Use selection patterns

| Pattern | Description | Use case |
|---------|-------------|----------|
| `["field1", "field2"]` | Specific fields only | Minimize response size |
| `["*"]` | All document fields | Get complete document data |

##### Optimize performance

**Optimize response size** - Only select the fields you actually need. Smaller responses are faster to transfer and parse.

**Reduce database load** - Selecting fewer fields reduces database processing time, especially for large documents.

#### Comparison operators

##### Equal

Returns document if field is equal to any value in the provided array.

```client-web
Query.equal("title", ["Iron Man"])
```
```client-flutter
Query.equal("title", ["Iron Man"])
```
```client-react-native
Query.equal("title", ["Iron Man"])
```
```client-apple
Query.equal("title", value: ["Iron Man"])
```
```client-android-kotlin
Query.equal("title", listOf("Iron Man"))
```
```client-android-java
Query.equal("title", Arrays.asList("Iron Man"))
```
```server-python
Query.equal("title", ["Iron Man"])
```
```server-ruby
Query.equal("title", ["Iron Man"])
```
```server-deno
Query.equal("title", ["Iron Man"])
```
```server-nodejs
Query.equal("title", ["Iron Man"])
```
```server-php
Query::equal("title", ["Iron Man"])
```
```server-dotnet
Query.Equal("title", new List<string> { "Iron Man" })
```
```server-go
query.Equal("title", []string{"Iron Man"})
```
```server-dart
Query.equal("title", ["Iron Man"])
```
```server-swift
Query.equal("title", value: ["Iron Man"])
```
```server-kotlin
Query.equal("title", listOf("Iron Man"))
```
```server-rust
Query::equal("title", Value::Array(vec![Value::String("Iron Man".to_string())])).to_string()
```
```bash
appwrite documentsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"equal","attribute":"title","values":["Iron Man"]}'
```
```http
{"method":"equal","attribute":"title","values":["Iron Man"]}
```

##### Not equal

Returns document if field is not equal to any value in the provided array.

```client-web
Query.notEqual("title", "Iron Man")
```
```client-flutter
Query.notEqual("title", "Iron Man")
```
```client-react-native
Query.notEqual("title", "Iron Man")
```
```client-apple
Query.notEqual("title", value: "Iron Man")
```
```client-android-kotlin
Query.notEqual("title", "Iron Man")
```
```client-android-java
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-nodejs
Query.notEqual("title", "Iron Man")
```
```server-php
Query::notEqual("title", "Iron Man")
```
```server-dotnet
Query.NotEqual("title", "Iron Man")
```
```server-go
query.NotEqual("title", "Iron Man")
```
```server-dart
Query.notEqual("title", "Iron Man")
```
```server-swift
Query.notEqual("title", value: "Iron Man")
```
```server-kotlin
Query.notEqual("title", "Iron Man")
```
```server-rust
Query::not_equal("title", "Iron Man").to_string()
```
```bash
appwrite documentsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"notEqual","attribute":"title","values":"Iron Man"}'
```
```http
{"method":"notEqual","attribute":"title","values":"Iron Man"}
```

##### Less than

Returns document if field is less than the provided value.

```client-web
Query.lessThan("score", 10)
```
```client-flutter
Query.lessThan("score", 10)
```
```client-react-native
Query.lessThan("score", 10)
```
```client-apple
Query.lessThan("score", value: 10)
```
```client-android-kotlin
Query.lessThan("score", 10)
```
```client-android-java
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-nodejs
Query.lessThan("score", 10)
```
```server-php
Query::lessThan("score", 10)
```
```server-dotnet
Query.LessThan("score", 10)
```
```server-go
query.LessThan("score", 10)
```
```server-dart
Query.lessThan("score", 10)
```
```server-swift
Query.lessThan("score", value: 10)
```
```server-kotlin
Query.lessThan("score", 10)
```
```server-rust
Query::less_than("score", 10).to_string()
```
```bash
appwrite documentsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"lessThan","attribute":"score","values":[10]}'
```
```http
{"method":"lessThan","attribute":"score","values":[10]}
```

##### Less than or equal

Returns document if field is less than or equal to the provided value.

```client-web
Query.lessThanEqual("score", 10)
```
```client-flutter
Query.lessThanEqual("score", 10)
```
```client-react-native
Query.lessThanEqual("score", 10)
```
```client-apple
Query.lessThanEqual("score", value: 10)
```
```client-android-kotlin
Query.lessThanEqual("score", 10)
```
```client-android-java
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-nodejs
Query.lessThanEqual("score", 10)
```
```server-php
Query::lessThanEqual("score", 10)
```
```server-dotnet
Query.LessThanEqual("score", 10)
```
```server-go
query.LessThanEqual("score", 10)
```
```server-dart
Query.lessThanEqual("score", 10)
```
```server-swift
Query.lessThanEqual("score", value: 10)
```
```server-kotlin
Query.lessThanEqual("score", 10)
```
```server-rust
Query::less_than_equal("score", 10).to_string()
```
```bash
appwrite documentsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"lessThanEqual","attribute":"score","values":[10]}'
```
```http
{"method":"lessThanEqual","attribute":"score","values":[10]}
```

##### Greater than

Returns document if field is greater than the provided value.

```client-web
Query.greaterThan("score", 10)
```
```client-flutter
Query.greaterThan("score", 10)
```
```client-react-native
Query.greaterThan("score", 10)
```
```client-apple
Query.greaterThan("score", value: 10)
```
```client-android-kotlin
Query.greaterThan("score", 10)
```
```client-android-java
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-nodejs
Query.greaterThan("score", 10)
```
```server-php
Query::greaterThan("score", 10)
```
```server-dotnet
Query.GreaterThan("score", 10)
```
```server-go
query.GreaterThan("score", 10)
```
```server-dart
Query.greaterThan("score", 10)
```
```server-swift
Query.greaterThan("score", value: 10)
```
```server-kotlin
Query.greaterThan("score", 10)
```
```server-rust
Query::greater_than("score", 10).to_string()
```
```bash
appwrite documentsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"greaterThan","attribute":"score","values":[10]}'
```
```http
{"method":"greaterThan","attribute":"score","values":[10]}
```

##### Greater than or equal

Returns document if field is greater than or equal to the provided value.

```client-web
Query.greaterThanEqual("score", 10)
```
```client-flutter
Query.greaterThanEqual("score", 10)
```
```client-react-native
Query.greaterThanEqual("score", 10)
```
```client-apple
Query.greaterThanEqual("score", value: 10)
```
```client-android-kotlin
Query.greaterThanEqual("score", 10)
```
```client-android-java
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-nodejs
Query.greaterThanEqual("score", 10)
```
```server-php
Query::greaterThanEqual("score", 10)
```
```server-dotnet
Query.GreaterThanEqual("score", 10)
```
```server-go
query.GreaterThanEqual("score", 10)
```
```server-dart
Query.greaterThanEqual("score", 10)
```
```server-swift
Query.greaterThanEqual("score", value: 10)
```
```server-kotlin
Query.greaterThanEqual("score", 10)
```
```server-rust
Query::greater_than_equal("score", 10).to_string()
```
```bash
appwrite documentsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"greaterThanEqual","attribute":"score","values":[10]}'
```
```http
{"method":"greaterThanEqual","attribute":"score","values":[10]}
```

##### Between

Returns document if field value falls between the two values. The boundary values are inclusive and can be strings or numbers.

```client-web
Query.between("price", 5, 10)
```
```client-flutter
Query.between("price", 5, 10)
```
```client-react-native
Query.between("price", 5, 10)
```
```client-apple
Query.between("price", start: 5, end: 10)
```
```client-android-kotlin
Query.between("price", 5, 10)
```
```client-android-java
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-nodejs
Query.between("price", 5, 10)
```
```server-php
Query::between("price", 5, 10)
```
```server-dotnet
Query.Between("price", 5, 10)
```
```server-go
query.Between("price", 5, 10)
```
```server-dart
Query.between("price", 5, 10)
```
```server-swift
Query.between("price", start: 5, end: 10)
```
```server-kotlin
Query.between("price", 5, 10)
```
```server-rust
Query::between("price", 5, 10).to_string()
```
```bash
appwrite documentsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"between","attribute":"price","values":[5,10]}'
```
```http
{"method":"between","attribute":"price","values":[5,10]}
```

##### Not between

Returns documents if the field 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.

```client-web
Query.notBetween("price", 5, 10)
```
```client-flutter
Query.notBetween("price", 5, 10)
```
```client-react-native
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-dotnet
Query.NotBetween("price", 5, 10)
```
```server-go
query.NotBetween("price", 5, 10)
```
```server-dart
Query.notBetween("price", 5, 10)
```
```server-swift
Query.notBetween("price", start: 5, end: 10)
```
```server-kotlin
Query.notBetween("price", 5, 10)
```
```server-rust
Query::not_between("price", 5, 10).to_string()
```
```bash
appwrite documentsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"notBetween","attribute":"price","values":[5,10]}'
```
```http
{"method":"notBetween","attribute":"price","values":[5,10]}
```

#### Null checks

##### Is null

Returns documents where field value is null.

```client-web
Query.isNull("name")
```
```client-flutter
Query.isNull("name")
```
```client-react-native
Query.isNull("name")
```
```client-apple
Query.isNull("name")
```
```client-android-kotlin
Query.isNull("name")
```
```client-android-java
Query.isNull("name")
```
```server-python
Query.is_null("name")
```
```server-ruby
Query.is_null("name")
```
```server-deno
Query.isNull("name")
```
```server-nodejs
Query.isNull("name")
```
```server-php
Query::isNull("name")
```
```server-dotnet
Query.IsNull("name")
```
```server-go
query.IsNull("name")
```
```server-dart
Query.isNull("name")
```
```server-swift
Query.isNull("name")
```
```server-kotlin
Query.isNull("name")
```
```server-rust
Query::is_null("name").to_string()
```
```bash
appwrite documentsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"isNull","attribute":"name"}'
```
```http
{"method":"isNull","attribute":"name"}
```

##### Is not null

Returns documents where field value is **not** null.

```client-web
Query.isNotNull("name")
```
```client-flutter
Query.isNotNull("name")
```
```client-react-native
Query.isNotNull("name")
```
```client-apple
Query.isNotNull("name")
```
```client-android-kotlin
Query.isNotNull("name")
```
```client-android-java
Query.isNotNull("name")
```
```server-python
Query.is_not_null("name")
```
```server-ruby
Query.is_not_null("name")
```
```server-deno
Query.isNotNull("name")
```
```server-nodejs
Query.isNotNull("name")
```
```server-php
Query::isNotNull("name")
```
```server-dotnet
Query.IsNotNull("name")
```
```server-go
query.IsNotNull("name")
```
```server-dart
Query.isNotNull("name")
```
```server-swift
Query.isNotNull("name")
```
```server-kotlin
Query.isNotNull("name")
```
```server-rust
Query::is_not_null("name").to_string()
```
```bash
appwrite documentsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"isNotNull","attribute":"name"}'
```
```http
{"method":"isNotNull","attribute":"name"}
```

#### Field existence

Documents in a collection do not share a schema, so a field can be present in some documents and absent in others. These operators filter on presence rather than value.

##### Exists

Returns documents that have the given fields, regardless of their value. Pass one or more field names.

```client-web
Query.exists(["email"])
```
```client-flutter
Query.exists(["email"])
```
```client-react-native
Query.exists(["email"])
```
```client-apple
Query.exists(["email"])
```
```client-android-kotlin
Query.exists(listOf("email"))
```
```client-android-java
Query.exists(Arrays.asList("email"))
```
```server-python
Query.exists(["email"])
```
```server-ruby
Query.exists(["email"])
```
```server-deno
Query.exists(["email"])
```
```server-nodejs
Query.exists(["email"])
```
```server-php
Query::exists(["email"])
```
```server-dotnet
Query.Exists(new List<string> { "email" })
```
```server-go
query.Exists([]interface{}{"email"})
```
```server-dart
Query.exists(["email"])
```
```server-swift
Query.exists(["email"])
```
```server-kotlin
Query.exists(listOf("email"))
```
```server-rust
Query::exists(vec!["email"]).to_string()
```
```bash
appwrite documentsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"exists","values":["email"]}'
```
```http
{"method":"exists","values":["email"]}
```

##### Not exists

Returns documents that do not have the given fields.

```client-web
Query.notExists(["email"])
```
```client-flutter
Query.notExists(["email"])
```
```client-react-native
Query.notExists(["email"])
```
```client-apple
Query.notExists(["email"])
```
```client-android-kotlin
Query.notExists(listOf("email"))
```
```client-android-java
Query.notExists(Arrays.asList("email"))
```
```server-python
Query.not_exists(["email"])
```
```server-ruby
Query.not_exists(["email"])
```
```server-deno
Query.notExists(["email"])
```
```server-nodejs
Query.notExists(["email"])
```
```server-php
Query::notExists(["email"])
```
```server-dotnet
Query.NotExists(new List<string> { "email" })
```
```server-go
query.NotExists([]interface{}{"email"})
```
```server-dart
Query.notExists(["email"])
```
```server-swift
Query.notExists(["email"])
```
```server-kotlin
Query.notExists(listOf("email"))
```
```server-rust
Query::not_exists(vec!["email"]).to_string()
```
```bash
appwrite documentsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"notExists","values":["email"]}'
```
```http
{"method":"notExists","values":["email"]}
```

#### String operations

##### Starts with

Returns documents if a string field starts with a substring.

```client-web
Query.startsWith("name", "Once upon a time")
```
```client-flutter
Query.startsWith("name", "Once upon a time")
```
```client-react-native
Query.startsWith("name", "Once upon a time")
```
```client-apple
Query.startsWith("name", value: "Once upon a time")
```
```client-android-kotlin
Query.startsWith("name", "Once upon a time")
```
```client-android-java
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-nodejs
Query.startsWith("name", "Once upon a time")
```
```server-php
Query::startsWith("name", "Once upon a time")
```
```server-dotnet
Query.StartsWith("name", "Once upon a time")
```
```server-go
query.StartsWith("name", "Once upon a time")
```
```server-dart
Query.startsWith("name", "Once upon a time")
```
```server-swift
Query.startsWith("name", value: "Once upon a time")
```
```server-kotlin
Query.startsWith("name", "Once upon a time")
```
```server-rust
Query::starts_with("name", "Once upon a time").to_string()
```
```bash
appwrite documentsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"startsWith","attribute":"name","values":["Once upon a time"]}'
```
```http
{"method":"startsWith","attribute":"name","values":["Once upon a time"]}
```

##### Not starts with

Returns documents if a string field does not start with a substring.

```client-web
Query.notStartsWith("name", "Once upon a time")
```
```client-flutter
Query.notStartsWith("name", "Once upon a time")
```
```client-react-native
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-dotnet
Query.NotStartsWith("name", "Once upon a time")
```
```server-go
query.NotStartsWith("name", "Once upon a time")
```
```server-dart
Query.notStartsWith("name", "Once upon a time")
```
```server-swift
Query.notStartsWith("name", value: "Once upon a time")
```
```server-kotlin
Query.notStartsWith("name", "Once upon a time")
```
```server-rust
Query::not_starts_with("name", "Once upon a time").to_string()
```
```bash
appwrite documentsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"notStartsWith","attribute":"name","values":["Once upon a time"]}'
```
```http
{"method":"notStartsWith","attribute":"name","values":["Once upon a time"]}
```

##### Ends with

Returns documents if a string field ends with a substring.

```client-web
Query.endsWith("name", "happily ever after.")
```
```client-flutter
Query.endsWith("name", "happily ever after.")
```
```client-react-native
Query.endsWith("name", "happily ever after.")
```
```client-apple
Query.endsWith("name", value: "happily ever after.")
```
```client-android-kotlin
Query.endsWith("name", "happily ever after.")
```
```client-android-java
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-nodejs
Query.endsWith("name", "happily ever after.")
```
```server-php
Query::endsWith("name", "happily ever after.")
```
```server-dotnet
Query.EndsWith("name", "happily ever after.")
```
```server-go
query.EndsWith("name", "happily ever after.")
```
```server-dart
Query.endsWith("name", "happily ever after.")
```
```server-swift
Query.endsWith("name", value: "happily ever after.")
```
```server-kotlin
Query.endsWith("name", "happily ever after.")
```
```server-rust
Query::ends_with("name", "happily ever after.").to_string()
```
```bash
appwrite documentsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"endsWith","attribute":"name","values":["happily ever after."]}'
```
```http
{"method":"endsWith","attribute":"name","values":["happily ever after."]}
```

##### Not ends with

Returns documents if a string field does not end with a substring.

```client-web
Query.notEndsWith("name", "happily ever after.")
```
```client-flutter
Query.notEndsWith("name", "happily ever after.")
```
```client-react-native
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-dotnet
Query.NotEndsWith("name", "happily ever after.")
```
```server-go
query.NotEndsWith("name", "happily ever after.")
```
```server-dart
Query.notEndsWith("name", "happily ever after.")
```
```server-swift
Query.notEndsWith("name", value: "happily ever after.")
```
```server-kotlin
Query.notEndsWith("name", "happily ever after.")
```
```server-rust
Query::not_ends_with("name", "happily ever after.").to_string()
```
```bash
appwrite documentsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"notEndsWith","attribute":"name","values":["happily ever after."]}'
```
```http
{"method":"notEndsWith","attribute":"name","values":["happily ever after."]}
```

##### Contains

Returns documents if the array field contains the specified elements or if a string field contains the specified substring.

```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")
```
```client-react-native
// 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")
```
```client-android-kotlin
// For arrays
Query.contains("ingredients", listOf("apple", "banana"))

// For strings
Query.contains("name", "Tom")
```
```client-android-java
// For arrays
Query.contains("ingredients", Arrays.asList("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-nodejs
// 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")
```
```server-dotnet
// For arrays
Query.Contains("ingredients", new List<string> { "apple", "banana" })

// For strings
Query.Contains("name", "Tom")
```
```server-go
// For arrays
query.Contains("ingredients", []string{"apple", "banana"})

// For strings
query.Contains("name", "Tom")
```
```server-dart
// For arrays
Query.contains("ingredients", ['apple', 'banana'])

// For strings
Query.contains("name", "Tom")
```
```server-swift
// For arrays
Query.contains("ingredients", value: ["apple", "banana"])

// For strings
Query.contains("name", value: "Tom")
```
```server-kotlin
// For arrays
Query.contains("ingredients", listOf("apple", "banana"))

// For strings
Query.contains("name", "Tom")
```
```server-rust
// For arrays
Query::contains("ingredients", Value::Array(vec![
    Value::String("apple".to_string()),
    Value::String("banana".to_string()),
])).to_string()

// For strings
Query::contains("name", "Tom").to_string()
```
```bash
### For arrays
appwrite documentsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"contains","attribute":"ingredients","values":["apple","banana"]}'

### For strings
appwrite documentsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"contains","attribute":"name","values":["Tom"]}'
```
```http
### For arrays
{"method":"contains","attribute":"ingredients","values":["apple","banana"]}

### For strings
{"method":"contains","attribute":"name","values":["Tom"]}
```

##### Not contains

Returns documents if the array field does not contain the specified
elements, or if a string field does not contain the specified
substring.

```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<string> { "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")
```
```server-rust
// For arrays
Query::not_contains("ingredients", Value::Array(vec![
    Value::String("apple".to_string()),
    Value::String("banana".to_string()),
])).to_string()

// For strings
Query::not_contains("name", "Tom").to_string()
```
```bash
### For arrays
appwrite documentsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"notContains","attribute":"ingredients","values":["apple","banana"]}'

### For strings
appwrite documentsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"notContains","attribute":"name","values":["Tom"]}'
```
```http
### For arrays
{"method":"notContains","attribute":"ingredients","values":["apple","banana"]}

### For strings
{"method":"notContains","attribute":"name","values":["Tom"]}
```

##### Search

Searches string fields for provided keywords. Requires a [full-text index](/docs/products/databases/documentsdb/collections#indexes) on queried fields.

```client-web
Query.search("text", "key words")
```
```client-flutter
Query.search("text", "key words")
```
```client-react-native
Query.search("text", "key words")
```
```client-apple
Query.search("text", value: "key words")
```
```client-android-kotlin
Query.search("text", "key words")
```
```client-android-java
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-nodejs
Query.search("text", "key words")
```
```server-php
Query::search("text", "key words")
```
```server-dotnet
Query.Search("text", "key words")
```
```server-go
query.Search("text", "key words")
```
```server-dart
Query.search("text", "key words")
```
```server-swift
Query.search("text", value: "key words")
```
```server-kotlin
Query.search("text", "key words")
```
```server-rust
Query::search("text", "key words").to_string()
```
```bash
appwrite documentsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"search","attribute":"text","values":["key words"]}'
```
```http
{"method":"search","attribute":"text","values":["key words"]}
```

##### Not search

Returns documents if a string field does not match the full-text search
query. Requires a [full-text index](/docs/products/databases/documentsdb/collections#indexes)
on queried fields.

```client-web
Query.notSearch("text", "key words")
```
```client-flutter
Query.notSearch("text", "key words")
```
```client-react-native
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-dotnet
Query.NotSearch("text", "key words")
```
```server-go
query.NotSearch("text", "key words")
```
```server-dart
Query.notSearch("text", "key words")
```
```server-swift
Query.notSearch("text", value: "key words")
```
```server-kotlin
Query.notSearch("text", "key words")
```
```server-rust
Query::not_search("text", "key words").to_string()
```
```bash
appwrite documentsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"notSearch","attribute":"text","values":["key words"]}'
```
```http
{"method":"notSearch","attribute":"text","values":["key words"]}
```

##### Regex

Returns documents where the field matches a regular expression.

```client-web
Query.regex("title", "^Iron")
```
```client-flutter
Query.regex("title", "^Iron")
```
```client-react-native
Query.regex("title", "^Iron")
```
```client-apple
Query.regex("title", "^Iron")
```
```client-android-kotlin
Query.regex("title", "^Iron")
```
```client-android-java
Query.regex("title", "^Iron")
```
```server-python
Query.regex("title", "^Iron")
```
```server-ruby
Query.regex("title", "^Iron")
```
```server-deno
Query.regex("title", "^Iron")
```
```server-nodejs
Query.regex("title", "^Iron")
```
```server-php
Query::regex("title", "^Iron")
```
```server-dotnet
Query.Regex("title", "^Iron")
```
```server-go
query.Regex("title", "^Iron")
```
```server-dart
Query.regex("title", "^Iron")
```
```server-swift
Query.regex("title", "^Iron")
```
```server-kotlin
Query.regex("title", "^Iron")
```
```server-rust
Query::regex("title", "^Iron").to_string()
```
```bash
appwrite documentsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"regex","attribute":"title","values":["^Iron"]}'
```
```http
{"method":"regex","attribute":"title","values":["^Iron"]}
```

#### Array operators

##### Contains any

Returns documents where the array field contains at least one of the given values.

```client-web
Query.containsAny("genres", ["action", "drama"])
```
```client-flutter
Query.containsAny("genres", ["action", "drama"])
```
```client-react-native
Query.containsAny("genres", ["action", "drama"])
```
```client-apple
Query.containsAny("genres", value: ["action", "drama"])
```
```client-android-kotlin
Query.containsAny("genres", listOf("action", "drama"))
```
```client-android-java
Query.containsAny("genres", Arrays.asList("action", "drama"))
```
```server-python
Query.contains_any("genres", ["action", "drama"])
```
```server-ruby
Query.contains_any("genres", ["action", "drama"])
```
```server-deno
Query.containsAny("genres", ["action", "drama"])
```
```server-nodejs
Query.containsAny("genres", ["action", "drama"])
```
```server-php
Query::containsAny("genres", ["action", "drama"])
```
```server-dotnet
Query.ContainsAny("genres", new List<string> { "action", "drama" })
```
```server-go
query.ContainsAny("genres", []interface{}{"action", "drama"})
```
```server-dart
Query.containsAny("genres", ["action", "drama"])
```
```server-swift
Query.containsAny("genres", value: ["action", "drama"])
```
```server-kotlin
Query.containsAny("genres", listOf("action", "drama"))
```
```server-rust
Query::contains_any("genres", vec!["action", "drama"]).to_string()
```
```bash
appwrite documentsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"containsAny","attribute":"genres","values":["action","drama"]}'
```
```http
{"method":"containsAny","attribute":"genres","values":["action","drama"]}
```

##### Contains all

Returns documents where the array field contains every one of the given values.

```client-web
Query.containsAll("genres", ["action", "drama"])
```
```client-flutter
Query.containsAll("genres", ["action", "drama"])
```
```client-react-native
Query.containsAll("genres", ["action", "drama"])
```
```client-apple
Query.containsAll("genres", value: ["action", "drama"])
```
```client-android-kotlin
Query.containsAll("genres", listOf("action", "drama"))
```
```client-android-java
Query.containsAll("genres", Arrays.asList("action", "drama"))
```
```server-python
Query.contains_all("genres", ["action", "drama"])
```
```server-ruby
Query.contains_all("genres", ["action", "drama"])
```
```server-deno
Query.containsAll("genres", ["action", "drama"])
```
```server-nodejs
Query.containsAll("genres", ["action", "drama"])
```
```server-php
Query::containsAll("genres", ["action", "drama"])
```
```server-dotnet
Query.ContainsAll("genres", new List<string> { "action", "drama" })
```
```server-go
query.ContainsAll("genres", []interface{}{"action", "drama"})
```
```server-dart
Query.containsAll("genres", ["action", "drama"])
```
```server-swift
Query.containsAll("genres", value: ["action", "drama"])
```
```server-kotlin
Query.containsAll("genres", listOf("action", "drama"))
```
```server-rust
Query::contains_all("genres", vec!["action", "drama"]).to_string()
```
```bash
appwrite documentsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"containsAll","attribute":"genres","values":["action","drama"]}'
```
```http
{"method":"containsAll","attribute":"genres","values":["action","drama"]}
```

##### Element match

Returns documents where at least one element of an array of embedded objects matches all of the nested queries.

```client-web
Query.elemMatch("reviews", [Query.greaterThan("rating", 4)])
```
```client-flutter
Query.elemMatch("reviews", [Query.greaterThan("rating", 4)])
```
```client-react-native
Query.elemMatch("reviews", [Query.greaterThan("rating", 4)])
```
```client-apple
Query.elemMatch("reviews", queries: [Query.greaterThan("rating", value: 4)])
```
```client-android-kotlin
Query.elemMatch("reviews", listOf(Query.greaterThan("rating", 4)))
```
```client-android-java
Query.elemMatch("reviews", Arrays.asList(Query.greaterThan("rating", 4)))
```
```server-python
Query.elem_match("reviews", [Query.greater_than("rating", 4)])
```
```server-ruby
Query.elem_match("reviews", [Query.greater_than("rating", 4)])
```
```server-deno
Query.elemMatch("reviews", [Query.greaterThan("rating", 4)])
```
```server-nodejs
Query.elemMatch("reviews", [Query.greaterThan("rating", 4)])
```
```server-php
Query::elemMatch("reviews", [Query::greaterThan("rating", 4)])
```
```server-dotnet
Query.ElemMatch("reviews", new List<string> { Query.GreaterThan("rating", 4) })
```
```server-go
query.ElemMatch("reviews", []string{query.GreaterThan("rating", 4)})
```
```server-dart
Query.elemMatch("reviews", [Query.greaterThan("rating", 4)])
```
```server-swift
Query.elemMatch("reviews", queries: [Query.greaterThan("rating", value: 4)])
```
```server-kotlin
Query.elemMatch("reviews", listOf(Query.greaterThan("rating", 4)))
```
```server-rust
Query::elem_match("reviews", vec![Query::greater_than("rating", 4)]).to_string()
```
```bash
appwrite documentsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"elemMatch","attribute":"reviews","values":[{"method":"greaterThan","attribute":"rating","values":[4]}]}'
```
```http
{"method":"elemMatch","attribute":"reviews","values":[{"method":"greaterThan","attribute":"rating","values":[4]}]}
```

#### Logical operators

##### AND

Returns document if it matches all of the nested sub-queries in the array passed in.

```client-web
Query.and([
    Query.lessThan("size", 10),
    Query.greaterThan("size", 5)
])
```
```client-flutter
Query.and([
    Query.lessThan("size", 10),
    Query.greaterThan("size", 5)
])
```
```client-react-native
Query.and([
    Query.lessThan("size", 10),
    Query.greaterThan("size", 5)
])
```
```client-apple
Query.and([
    Query.lessThan("size", value: 10),
    Query.greaterThan("size", value: 5)
])
```
```client-android-kotlin
Query.and(listOf(
    Query.lessThan("size", 10),
    Query.greaterThan("size", 5)
))
```
```client-android-java
Query.and(Arrays.asList(
    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-nodejs
Query.and([
    Query.lessThan("size", 10),
    Query.greaterThan("size", 5)
])
```
```server-php
Query::and([
    Query::lessThan("size", 10),
    Query::greaterThan("size", 5)
])
```
```server-dotnet
Query.And(new List<string> {
    Query.LessThan("size", 10),
    Query.GreaterThan("size", 5)
})
```
```server-go
query.And([]string{
    query.LessThan("size", 10),
    query.GreaterThan("size", 5),
})
```
```server-dart
Query.and([
    Query.lessThan("size", 10),
    Query.greaterThan("size", 5)
])
```
```server-swift
Query.and([
    Query.lessThan("size", value: 10),
    Query.greaterThan("size", value: 5)
])
```
```server-kotlin
Query.and(listOf(
    Query.lessThan("size", 10),
    Query.greaterThan("size", 5)
))
```
```server-rust
Query::and(vec![
    Query::less_than("size", 10).to_string(),
    Query::greater_than("size", 5).to_string(),
]).to_string()
```
```bash
appwrite documentsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"and","values":[{"method":"lessThan","attribute":"size","values":[10]},{"method":"greaterThan","attribute":"size","values":[5]}]}'
```
```http
{"method":"and","values":[{"method":"lessThan","attribute":"size","values":[10]},{"method":"greaterThan","attribute":"size","values":[5]}]}
```

##### OR

Returns document if it matches any of the nested sub-queries in the array passed in.

```client-web
Query.or([
    Query.lessThan("size", 5),
    Query.greaterThan("size", 10)
])
```
```client-flutter
Query.or([
    Query.lessThan("size", 5),
    Query.greaterThan("size", 10)
])
```
```client-react-native
Query.or([
    Query.lessThan("size", 5),
    Query.greaterThan("size", 10)
])
```
```client-apple
Query.or([
    Query.lessThan("size", value: 5),
    Query.greaterThan("size", value: 10)
])
```
```client-android-kotlin
Query.or(listOf(
    Query.lessThan("size", 5),
    Query.greaterThan("size", 10)
))
```
```client-android-java
Query.or(Arrays.asList(
    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-nodejs
Query.or([
    Query.lessThan("size", 5),
    Query.greaterThan("size", 10)
])
```
```server-php
Query::or([
    Query::lessThan("size", 5),
    Query::greaterThan("size", 10)
])
```
```server-dotnet
Query.Or(new List<string> {
    Query.LessThan("size", 5),
    Query.GreaterThan("size", 10)
})
```
```server-go
query.Or([]string{
    query.LessThan("size", 5),
    query.GreaterThan("size", 10),
})
```
```server-dart
Query.or([
    Query.lessThan("size", 5),
    Query.greaterThan("size", 10)
])
```
```server-swift
Query.or([
    Query.lessThan("size", value: 5),
    Query.greaterThan("size", value: 10)
])
```
```server-kotlin
Query.or(listOf(
    Query.lessThan("size", 5),
    Query.greaterThan("size", 10)
))
```
```server-rust
Query::or(vec![
    Query::less_than("size", 5).to_string(),
    Query::greater_than("size", 10).to_string(),
]).to_string()
```
```bash
appwrite documentsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"or","values":[{"method":"lessThan","attribute":"size","values":[5]},{"method":"greaterThan","attribute":"size","values":[10]}]}'
```
```http
{"method":"or","values":[{"method":"lessThan","attribute":"size","values":[5]},{"method":"greaterThan","attribute":"size","values":[10]}]}
```

#### Ordering

##### Order descending

Orders results in descending order by field.

```client-web
Query.orderDesc("field")
```
```client-flutter
Query.orderDesc("field")
```
```client-react-native
Query.orderDesc("field")
```
```client-apple
Query.orderDesc("field")
```
```client-android-kotlin
Query.orderDesc("field")
```
```client-android-java
Query.orderDesc("field")
```
```server-python
Query.order_desc("field")
```
```server-ruby
Query.order_desc("field")
```
```server-deno
Query.orderDesc("field")
```
```server-nodejs
Query.orderDesc("field")
```
```server-php
Query::orderDesc("field")
```
```server-dotnet
Query.OrderDesc("field")
```
```server-go
query.OrderDesc("attribute")
```
```server-dart
Query.orderDesc("field")
```
```server-swift
Query.orderDesc("field")
```
```server-kotlin
Query.orderDesc("field")
```
```server-rust
Query::order_desc("field").to_string()
```
```bash
appwrite documentsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"orderDesc","attribute":"field"}'
```
```http
{"method":"orderDesc","attribute":"field"}
```

##### Order ascending

Orders results in ascending order by field.

```client-web
Query.orderAsc("field")
```
```client-flutter
Query.orderAsc("field")
```
```client-react-native
Query.orderAsc("field")
```
```client-apple
Query.orderAsc("field")
```
```client-android-kotlin
Query.orderAsc("field")
```
```client-android-java
Query.orderAsc("field")
```
```server-python
Query.order_asc("field")
```
```server-ruby
Query.order_asc("field")
```
```server-deno
Query.orderAsc("field")
```
```server-nodejs
Query.orderAsc("field")
```
```server-php
Query::orderAsc("field")
```
```server-dotnet
Query.OrderAsc("field")
```
```server-go
query.OrderAsc("attribute")
```
```server-dart
Query.orderAsc("field")
```
```server-swift
Query.orderAsc("field")
```
```server-kotlin
Query.orderAsc("field")
```
```server-rust
Query::order_asc("field").to_string()
```
```bash
appwrite documentsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"orderAsc","attribute":"field"}'
```
```http
{"method":"orderAsc","attribute":"field"}
```

##### Order random

Returns documents in a random order.

```client-web
Query.orderRandom()
```
```client-flutter
Query.orderRandom()
```
```client-react-native
Query.orderRandom()
```
```client-apple
Query.orderRandom()
```
```client-android-kotlin
Query.orderRandom()
```
```client-android-java
Query.orderRandom()
```
```server-python
Query.order_random()
```
```server-ruby
Query.order_random()
```
```server-deno
Query.orderRandom()
```
```server-nodejs
Query.orderRandom()
```
```server-php
Query::orderRandom()
```
```server-dotnet
Query.OrderRandom()
```
```server-go
query.OrderRandom()
```
```server-dart
Query.orderRandom()
```
```server-swift
Query.orderRandom()
```
```server-kotlin
Query.orderRandom()
```
```server-rust
Query::order_random().to_string()
```
```bash
appwrite documentsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"orderRandom"}'
```
```http
{"method":"orderRandom"}
```

#### Pagination

##### Limit

Limits the number of results returned by the query. Used for [pagination](/docs/products/databases/documentsdb/pagination).

```client-web
Query.limit(25)
```
```client-flutter
Query.limit(25)
```
```client-react-native
Query.limit(25)
```
```client-apple
Query.limit(25)
```
```client-android-kotlin
Query.limit(25)
```
```client-android-java
Query.limit(25)
```
```server-python
Query.limit(25)
```
```server-ruby
Query.limit(25)
```
```server-deno
Query.limit(25)
```
```server-nodejs
Query.limit(25)
```
```server-php
Query::limit(25)
```
```server-dotnet
Query.Limit(25)
```
```server-go
query.Limit(25)
```
```server-dart
Query.limit(25)
```
```server-swift
Query.limit(25)
```
```server-kotlin
Query.limit(25)
```
```server-rust
Query::limit(25).to_string()
```
```bash
appwrite documentsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"limit","values":[25]}'
```
```http
{"method":"limit","values":[25]}
```

##### Offset

Offset the results returned by skipping some of the results. Used for [pagination](/docs/products/databases/documentsdb/pagination).

```client-web
Query.offset(0)
```
```client-flutter
Query.offset(0)
```
```client-react-native
Query.offset(0)
```
```client-apple
Query.offset(0)
```
```client-android-kotlin
Query.offset(0)
```
```client-android-java
Query.offset(0)
```
```server-python
Query.offset(0)
```
```server-ruby
Query.offset(0)
```
```server-deno
Query.offset(0)
```
```server-nodejs
Query.offset(0)
```
```server-php
Query::offset(0)
```
```server-dotnet
Query.Offset(0)
```
```server-go
query.Offset(0)
```
```server-dart
Query.offset(0)
```
```server-swift
Query.offset(0)
```
```server-kotlin
Query.offset(0)
```
```server-rust
Query::offset(0).to_string()
```
```bash
appwrite documentsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"offset","values":[0]}'
```
```http
{"method":"offset","values":[0]}
```

##### Cursor after

Places the cursor after the specified resource ID. Used for [pagination](/docs/products/databases/documentsdb/pagination).

```client-web
Query.cursorAfter("62a7...f620")
```
```client-flutter
Query.cursorAfter("62a7...f620")
```
```client-react-native
Query.cursorAfter("62a7...f620")
```
```client-apple
Query.cursorAfter("62a7...f620")
```
```client-android-kotlin
Query.cursorAfter("62a7...f620")
```
```client-android-java
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-nodejs
Query.cursorAfter("62a7...f620")
```
```server-php
Query::cursorAfter("62a7...f620")
```
```server-dotnet
Query.CursorAfter("62a7...f620")
```
```server-go
query.CursorAfter("62a7...f620")
```
```server-dart
Query.cursorAfter("62a7...f620")
```
```server-swift
Query.cursorAfter("62a7...f620")
```
```server-kotlin
Query.cursorAfter("62a7...f620")
```
```server-rust
Query::cursor_after("62a7...f620").to_string()
```
```bash
appwrite documentsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"cursorAfter","values":["62a7...f620"]}'
```
```http
{"method":"cursorAfter","values":["62a7...f620"]}
```

##### Cursor before

Places the cursor before the specified resource ID. Used for [pagination](/docs/products/databases/documentsdb/pagination).

```client-web
Query.cursorBefore("62a7...a600")
```
```client-flutter
Query.cursorBefore("62a7...a600")
```
```client-react-native
Query.cursorBefore("62a7...a600")
```
```client-apple
Query.cursorBefore("62a7...a600")
```
```client-android-kotlin
Query.cursorBefore("62a7...a600")
```
```client-android-java
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-nodejs
Query.cursorBefore("62a7...a600")
```
```server-php
Query::cursorBefore("62a7...a600")
```
```server-dotnet
Query.CursorBefore("62a7...a600")
```
```server-go
query.CursorBefore("62a7...a600")
```
```server-dart
Query.cursorBefore("62a7...a600")
```
```server-swift
Query.cursorBefore("62a7...a600")
```
```server-kotlin
Query.cursorBefore("62a7...a600")
```
```server-rust
Query::cursor_before("62a7...a600").to_string()
```
```bash
appwrite documentsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"cursorBefore","values":["62a7...a600"]}'
```
```http
{"method":"cursorBefore","values":["62a7...a600"]}
```

### 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

Returns documents created before the given date.

```client-web
Query.createdBefore("2025-01-01T00:00:00Z")
```
```client-flutter
Query.createdBefore("2025-01-01T00:00:00Z")
```
```client-react-native
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-dotnet
Query.CreatedBefore("2025-01-01T00:00:00Z")
```
```server-go
query.CreatedBefore("2025-01-01T00:00:00Z")
```
```server-dart
Query.createdBefore("2025-01-01T00:00:00Z")
```
```server-swift
Query.createdBefore("2025-01-01T00:00:00Z")
```
```server-kotlin
Query.createdBefore("2025-01-01T00:00:00Z")
```
```server-rust
Query::created_before("2025-01-01T00:00:00Z").to_string()
```
```bash
appwrite documentsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"createdBefore","values":["2025-01-01T00:00:00Z"]}'
```
```http
{"method":"createdBefore","values":["2025-01-01T00:00:00Z"]}
```

##### Created after

Returns documents created after the given date.

```client-web
Query.createdAfter("2025-01-01T00:00:00Z")
```
```client-flutter
Query.createdAfter("2025-01-01T00:00:00Z")
```
```client-react-native
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-dotnet
Query.CreatedAfter("2025-01-01T00:00:00Z")
```
```server-go
query.CreatedAfter("2025-01-01T00:00:00Z")
```
```server-dart
Query.createdAfter("2025-01-01T00:00:00Z")
```
```server-swift
Query.createdAfter("2025-01-01T00:00:00Z")
```
```server-kotlin
Query.createdAfter("2025-01-01T00:00:00Z")
```
```server-rust
Query::created_after("2025-01-01T00:00:00Z").to_string()
```
```bash
appwrite documentsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"createdAfter","values":["2025-01-01T00:00:00Z"]}'
```
```http
{"method":"createdAfter","values":["2025-01-01T00:00:00Z"]}
```

##### Created between

Returns documents created between the two given dates.

```client-web
Query.createdBetween("2025-01-01T00:00:00Z", "2025-12-31T23:59:59Z")
```
```client-flutter
Query.createdBetween("2025-01-01T00:00:00Z", "2025-12-31T23:59:59Z")
```
```client-react-native
Query.createdBetween("2025-01-01T00:00:00Z", "2025-12-31T23:59:59Z")
```
```client-apple
Query.createdBetween("2025-01-01T00:00:00Z", "2025-12-31T23:59:59Z")
```
```client-android-kotlin
Query.createdBetween("2025-01-01T00:00:00Z", "2025-12-31T23:59:59Z")
```
```client-android-java
Query.createdBetween("2025-01-01T00:00:00Z", "2025-12-31T23:59:59Z")
```
```server-python
Query.created_between("2025-01-01T00:00:00Z", "2025-12-31T23:59:59Z")
```
```server-ruby
Query.created_between("2025-01-01T00:00:00Z", "2025-12-31T23:59:59Z")
```
```server-deno
Query.createdBetween("2025-01-01T00:00:00Z", "2025-12-31T23:59:59Z")
```
```server-nodejs
Query.createdBetween("2025-01-01T00:00:00Z", "2025-12-31T23:59:59Z")
```
```server-php
Query::createdBetween("2025-01-01T00:00:00Z", "2025-12-31T23:59:59Z")
```
```server-dotnet
Query.CreatedBetween("2025-01-01T00:00:00Z", "2025-12-31T23:59:59Z")
```
```server-go
query.CreatedBetween("2025-01-01T00:00:00Z", "2025-12-31T23:59:59Z")
```
```server-dart
Query.createdBetween("2025-01-01T00:00:00Z", "2025-12-31T23:59:59Z")
```
```server-swift
Query.createdBetween("2025-01-01T00:00:00Z", "2025-12-31T23:59:59Z")
```
```server-kotlin
Query.createdBetween("2025-01-01T00:00:00Z", "2025-12-31T23:59:59Z")
```
```server-rust
Query::created_between("2025-01-01T00:00:00Z", "2025-12-31T23:59:59Z").to_string()
```
```bash
appwrite documentsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"between","attribute":"$createdAt","values":["2025-01-01T00:00:00Z","2025-12-31T23:59:59Z"]}'
```
```http
{"method":"between","attribute":"$createdAt","values":["2025-01-01T00:00:00Z","2025-12-31T23:59:59Z"]}
```

##### Updated before

Returns documents updated before the given date.

```client-web
Query.updatedBefore("2025-01-01T00:00:00Z")
```
```client-flutter
Query.updatedBefore("2025-01-01T00:00:00Z")
```
```client-react-native
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-dotnet
Query.UpdatedBefore("2025-01-01T00:00:00Z")
```
```server-go
query.UpdatedBefore("2025-01-01T00:00:00Z")
```
```server-dart
Query.updatedBefore("2025-01-01T00:00:00Z")
```
```server-swift
Query.updatedBefore("2025-01-01T00:00:00Z")
```
```server-kotlin
Query.updatedBefore("2025-01-01T00:00:00Z")
```
```server-rust
Query::updated_before("2025-01-01T00:00:00Z").to_string()
```
```bash
appwrite documentsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"updatedBefore","values":["2025-01-01T00:00:00Z"]}'
```
```http
{"method":"updatedBefore","values":["2025-01-01T00:00:00Z"]}
```

##### Updated after

Returns documents updated after the given date.

```client-web
Query.updatedAfter("2025-01-01T00:00:00Z")
```
```client-flutter
Query.updatedAfter("2025-01-01T00:00:00Z")
```
```client-react-native
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-dotnet
Query.UpdatedAfter("2025-01-01T00:00:00Z")
```
```server-go
query.UpdatedAfter("2025-01-01T00:00:00Z")
```
```server-dart
Query.updatedAfter("2025-01-01T00:00:00Z")
```
```server-swift
Query.updatedAfter("2025-01-01T00:00:00Z")
```
```server-kotlin
Query.updatedAfter("2025-01-01T00:00:00Z")
```
```server-rust
Query::updated_after("2025-01-01T00:00:00Z").to_string()
```
```bash
appwrite documentsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"updatedAfter","values":["2025-01-01T00:00:00Z"]}'
```
```http
{"method":"updatedAfter","values":["2025-01-01T00:00:00Z"]}
```

##### Updated between

Returns documents updated between the two given dates.

```client-web
Query.updatedBetween("2025-01-01T00:00:00Z", "2025-12-31T23:59:59Z")
```
```client-flutter
Query.updatedBetween("2025-01-01T00:00:00Z", "2025-12-31T23:59:59Z")
```
```client-react-native
Query.updatedBetween("2025-01-01T00:00:00Z", "2025-12-31T23:59:59Z")
```
```client-apple
Query.updatedBetween("2025-01-01T00:00:00Z", "2025-12-31T23:59:59Z")
```
```client-android-kotlin
Query.updatedBetween("2025-01-01T00:00:00Z", "2025-12-31T23:59:59Z")
```
```client-android-java
Query.updatedBetween("2025-01-01T00:00:00Z", "2025-12-31T23:59:59Z")
```
```server-python
Query.updated_between("2025-01-01T00:00:00Z", "2025-12-31T23:59:59Z")
```
```server-ruby
Query.updated_between("2025-01-01T00:00:00Z", "2025-12-31T23:59:59Z")
```
```server-deno
Query.updatedBetween("2025-01-01T00:00:00Z", "2025-12-31T23:59:59Z")
```
```server-nodejs
Query.updatedBetween("2025-01-01T00:00:00Z", "2025-12-31T23:59:59Z")
```
```server-php
Query::updatedBetween("2025-01-01T00:00:00Z", "2025-12-31T23:59:59Z")
```
```server-dotnet
Query.UpdatedBetween("2025-01-01T00:00:00Z", "2025-12-31T23:59:59Z")
```
```server-go
query.UpdatedBetween("2025-01-01T00:00:00Z", "2025-12-31T23:59:59Z")
```
```server-dart
Query.updatedBetween("2025-01-01T00:00:00Z", "2025-12-31T23:59:59Z")
```
```server-swift
Query.updatedBetween("2025-01-01T00:00:00Z", "2025-12-31T23:59:59Z")
```
```server-kotlin
Query.updatedBetween("2025-01-01T00:00:00Z", "2025-12-31T23:59:59Z")
```
```server-rust
Query::updated_between("2025-01-01T00:00:00Z", "2025-12-31T23:59:59Z").to_string()
```
```bash
appwrite documentsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"between","attribute":"$updatedAt","values":["2025-01-01T00:00:00Z","2025-12-31T23:59:59Z"]}'
```
```http
{"method":"between","attribute":"$updatedAt","values":["2025-01-01T00:00:00Z","2025-12-31T23:59:59Z"]}
```

### 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:

```client-web
const results = await documentsDB.listDocuments({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    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 documentsDB.listDocuments(
    '<DATABASE_ID>',
    '<COLLECTION_ID>',
    [
        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 = documentsDB.list_documents(
    database_id='<DATABASE_ID>',
    collection_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)
            ])
        ])
    ]
)
```
```server-go
documents, err := documentsDB.ListDocuments(
    "<DATABASE_ID>",
    "<COLLECTION_ID>",
    documentsDB.WithListDocumentsQueries([]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)
}
```
```server-rust
let documents = documents_db.list_documents(
    "<DATABASE_ID>",
    "<COLLECTION_ID>",
    Some(vec![
        Query::or(vec![
            Query::and(vec![
                Query::equal("category", Value::Array(vec![Value::String("books".to_string())])).to_string(),
                Query::less_than("price", 20).to_string(),
            ]).to_string(),
            Query::and(vec![
                Query::equal("category", Value::Array(vec![Value::String("magazines".to_string())])).to_string(),
                Query::less_than("price", 10).to_string(),
            ]).to_string(),
        ]).to_string(),
    ]),
    None,
    None,
    None,
).await?;
```
```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]}]}]}
```

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 DocumentsDB

URL: https://appwrite.io/docs/products/databases/documentsdb/quick-start

#### 1. Create database

![Create database](/images/docs/databases/documentsdb/create-database.avif)
Head to your [Appwrite Console](https://appwrite.io/console), open your project, and navigate to the **Databases** page. Click **Create database**, name it `Oscar`, and choose **DocumentsDB** as the database type.
Optionally, add a custom database ID. Select your preferred tier, then click **Create database**.

#### 2. Create collection

![Create collection dialog](/images/docs/databases/documentsdb/create-collection.avif)
In the `Oscar` database, click **Create collection** and name it `My books`. Optionally, add a custom collection ID.

Collections are schemaless, so there are no columns to define. Each document holds its own fields as flexible JSON.

Open the collection's **Security** tab. Under **Permissions**, add a new role **Any** and check **Create** and **Read**, so anyone can create and read documents. Click **Update** to save.

#### 3. Create documents

To create a document use the `createDocument` method.

In the **Settings** menu, find your project ID and replace `<PROJECT_ID>` in the example.

Navigate to the `Oscar` database, copy the database ID, and replace `<DATABASE_ID>`.
Then, in the `My books` collection, copy the collection ID, and replace `<COLLECTION_ID>`.

```client-web
import { Client, ID, DocumentsDB } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

const documentsDB = new DocumentsDB(client);

const promise = documentsDB.createDocument({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: 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://<REGION>.cloud.appwrite.io/v1')
        .setProject('<PROJECT_ID>');

    final documentsDB = DocumentsDB(client);

    try {
        final document = documentsDB.createDocument(
            databaseId: '<DATABASE_ID>',
            collectionId: '<COLLECTION_ID>',
            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://<REGION>.cloud.appwrite.io/v1")
        .setProject("<PROJECT_ID>")

    let documentsDB = DocumentsDB(client)

    do {
        let document = try await documentsDB.createDocument(
            databaseId: "<DATABASE_ID>",
            collectionId: "<COLLECTION_ID>",
            documentId: ID.unique(),
            data: ["title": "Hamlet"]
        )
    } catch {
        print(error.localizedDescription)
    }
}
```
```client-android-kotlin
import io.appwrite.Client
import io.appwrite.ID
import io.appwrite.services.DocumentsDB

suspend fun main() {
    val client = Client(applicationContext)
        .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
        .setProject("<PROJECT_ID>")

    val documentsDB = DocumentsDB(client)

    try {
        val document = documentsDB.createDocument(
            databaseId = "<DATABASE_ID>",
            collectionId = "<COLLECTION_ID>",
            documentId = ID.unique(),
            data = mapOf("title" to "Hamlet"),
        )
    } catch (e: Exception) {
        Log.e("Appwrite", "Error: " + e.message)
    }
}
```

The response should look similar to this.

```json
{
    "title": "Hamlet",
    "$id": "6a423aa70000c39da0be",
    "$sequence": "019f12b5-1c89-7094-8d12-5b7e7a09f025",
    "$permissions": [],
    "$createdAt": "2026-06-29T09:28:07.047+00:00",
    "$updatedAt": "2026-06-29T09:28:07.047+00:00",
    "$databaseId": "650125c64b3c25ce4bc4",
    "$collectionId": "650125cff227cf9f95ad"
}
```

![The My books collection with a document selected](/images/docs/databases/documentsdb/collection-documents.avif)

#### 4. List documents

To read and query data from your collection, use the `listDocuments` endpoint.

Like the previous step, replace `<PROJECT_ID>`, `<DATABASE_ID>`, and `<COLLECTION_ID>` with their respective IDs.

```client-web
import { Client, Query, DocumentsDB } from "appwrite";

const client = new Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

const documentsDB = new DocumentsDB(client);

const promise = documentsDB.listDocuments({
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    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://<REGION>.cloud.appwrite.io/v1")
        .setProject("<PROJECT_ID>")

    final documentsDB = DocumentsDB(client);

    try {
        final documents = await documentsDB.listDocuments(
            databaseId: '<DATABASE_ID>',
            collectionId: '<COLLECTION_ID>',
            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://<REGION>.cloud.appwrite.io/v1")
        .setProject("<PROJECT_ID>")

    let documentsDB = DocumentsDB(client)

    do {
        let documents = try await documentsDB.listDocuments(
            databaseId: "<DATABASE_ID>",
            collectionId: "<COLLECTION_ID>",
            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.DocumentsDB

suspend fun main() {
    val client = Client(applicationContext)
        .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
        .setProject("<PROJECT_ID>")

    val documentsDB = DocumentsDB(client)

    try {
        val documents = documentsDB.listDocuments(
            databaseId = "<DATABASE_ID>",
            collectionId = "<COLLECTION_ID>",
            queries = listOf(
                Query.equal("title", "Hamlet")
            )
        )
    } catch (e: AppwriteException) {
        Log.e("Appwrite", "Error: " + e.message)
    }
}
```

---

## Timestamp overrides

URL: https://appwrite.io/docs/products/databases/documentsdb/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.

**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).

### 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

When working with individual documents, you can set custom timestamps during create, update, and upsert operations.

##### Create with custom timestamps

```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 documentsDB = new sdk.DocumentsDB(client);

await documentsDB.createDocument({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: sdk.ID.unique(),
    data: {
        '$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\DocumentsDB;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<YOUR_PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$documentsDB = new DocumentsDB($client);

$documentsDB->createDocument(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: ID::unique(),
    data: [
        '$createdAt' => (new DateTime('<CUSTOM_DATE>'))->format(DATE_ATOM),
        '$updatedAt' => (new DateTime('<CUSTOM_DATE>'))->format(DATE_ATOM),
        // ...your attributes
    ]
);
```
```server-swift
import Appwrite
import Foundation

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<YOUR_PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let documentsDB = DocumentsDB(client)

let isoFormatter = ISO8601DateFormatter()
isoFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
let customDate = isoFormatter.date(from: "<CUSTOM_DATE>") ?? Date()
let createdAt = isoFormatter.string(from: customDate)
let updatedAt = isoFormatter.string(from: customDate)

do {
    let created = try await documentsDB.createDocument(
        databaseId: "<DATABASE_ID>",
        collectionId: "<COLLECTION_ID>",
        documentId: "<DOCUMENT_ID>",
        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.documents_db import DocumentsDB
from appwrite.id import ID
from datetime import datetime, timezone

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<API_KEY>')

documents_db = DocumentsDB(client)

iso = datetime(2025, 8, 10, 12, 34, 56, tzinfo=timezone.utc).isoformat()

documents_db.create_document(
        database_id='<DATABASE_ID>',
        collection_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://<REGION>.cloud.appwrite.io/v1')
    .set_project('<YOUR_PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

documents_db = DocumentsDB.new(client)

custom_date = Time.parse('2025-08-10T12:34:56.000Z').iso8601

documents_db.create_document(
    database_id: '<DATABASE_ID>',
    collection_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://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<YOUR_PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

DocumentsDB documentsDB = new DocumentsDB(client);

string customDate = DateTimeOffset.Parse("2025-08-10T12:34:56.000Z").ToString("O");

await documentsDB.CreateDocument(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    documentId: ID.Unique(),
    data: new Dictionary<string, object>
    {
        ["$createdAt"] = customDate,
        ["$updatedAt"] = customDate,
        // ...your attributes
    }
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<YOUR_PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

DocumentsDB documentsDB = DocumentsDB(client);

String customDate = DateTime.parse('2025-08-10T12:34:56.000Z').toIso8601String();

await documentsDB.createDocument(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: ID.unique(),
    data: {
        '\$createdAt': customDate,
        '\$updatedAt': customDate,
        // ...your attributes
    },
);
```
```rust
use appwrite::Client;
use appwrite::services::documents_db::DocumentsDB;
use appwrite::id::ID;
use serde_json::json;

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

    let documents_db = DocumentsDB::new(&client);

    let result = documents_db.create_document(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
        &ID::unique(),
        json!({
            "$createdAt": "2025-08-10T12:34:56.000Z",
            "$updatedAt": "2025-08-10T12:34:56.000Z"
            // ...your attributes
        }),
        None,
        None,
    ).await?;

    println!("Created: {:?}", result);
    Ok(())
}
```

##### Update with custom timestamps

When updating documents, you can also set a custom `$updatedAt` timestamp:

```server-nodejs
await documentsDB.updateDocument({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>',
    data: {
        '$updatedAt': new Date('2025-08-10T12:34:56.000Z').toISOString(),
        // ...your attributes
    }
});
```
```server-php
$documentsDB->updateDocument(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>',
    data: [
        '$updatedAt' => (new DateTime('<CUSTOM_DATE>'))->format(DATE_ATOM),
        // ...your attributes
    ]
);
```
```server-python
from datetime import datetime, timezone

documents_db.update_document(
    database_id='<DATABASE_ID>',
    collection_id='<COLLECTION_ID>',
    document_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://<REGION>.cloud.appwrite.io/v1")
    .setProject("<YOUR_PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let documentsDB = DocumentsDB(client)

let isoFormatter = ISO8601DateFormatter()
isoFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
let updatedAt = isoFormatter.string(from: isoFormatter.date(from: "<CUSTOM_DATE>") ?? Date())

do {
    let updated = try await documentsDB.updateDocument(
        databaseId: "<DATABASE_ID>",
        collectionId: "<COLLECTION_ID>",
        documentId: "<DOCUMENT_ID>",
        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://<REGION>.cloud.appwrite.io/v1')
    .set_project('<YOUR_PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

documents_db = DocumentsDB.new(client)

custom_date = Time.parse('<CUSTOM_DATE>').iso8601

documents_db.update_document(
  database_id: '<DATABASE_ID>',
  collection_id: '<COLLECTION_ID>',
  document_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://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<YOUR_PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

DocumentsDB documentsDB = new DocumentsDB(client);

string customDate = DateTimeOffset.Parse("<CUSTOM_DATE>").ToString("O");

await documentsDB.UpdateDocument(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    documentId: "<DOCUMENT_ID>",
    data: new Dictionary<string, object>
    {
        ["$updatedAt"] = customDate,
        // ...your attributes
    }
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<YOUR_PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

DocumentsDB documentsDB = DocumentsDB(client);

String customDate = DateTime.parse('<CUSTOM_DATE>').toIso8601String();

await documentsDB.updateDocument(
  databaseId: '<DATABASE_ID>',
  collectionId: '<COLLECTION_ID>',
  documentId: '<DOCUMENT_ID>',
  data: {
    '\$updatedAt': customDate,
    // ...your attributes
  },
);
```
```rust
use appwrite::Client;
use appwrite::services::documents_db::DocumentsDB;
use serde_json::json;

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

    let documents_db = DocumentsDB::new(&client);

    let result = documents_db.update_document(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
        "<DOCUMENT_ID>",
        Some(json!({
            "$updatedAt": "2025-08-10T12:34:56.000Z"
            // ...your attributes
        })),
        None,
        None,
    ).await?;

    println!("Updated: {:?}", result);
    Ok(())
}
```

#### Bulk operations

Custom timestamps also work with bulk operations, allowing you to set different timestamps for each document in the batch:

##### Bulk create

```server-nodejs
await documentsDB.createDocuments({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documents: [
        {
            '$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
documents_db.create_documents(
        database_id='<DATABASE_ID>',
        collection_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\DocumentsDB;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<YOUR_PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$documentsDB = new DocumentsDB($client);

$documentsDB->createDocuments(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documents: [
        [
            '$id' => ID::unique(),
            '$createdAt' => (new DateTime('<CUSTOM_DATE>'))->format(DATE_ATOM),
            '$updatedAt' => (new DateTime('<CUSTOM_DATE>'))->format(DATE_ATOM),
            // ...your attributes
        ],
        [
            '$id' => ID::unique(),
            '$createdAt' => (new DateTime('<CUSTOM_DATE>'))->format(DATE_ATOM),
            '$updatedAt' => (new DateTime('<CUSTOM_DATE>'))->format(DATE_ATOM),
            // ...your attributes
        ],
    ]
);
```
```server-swift
import Appwrite
import Foundation

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<YOUR_PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let documentsDB = DocumentsDB(client)

let isoFormatter = ISO8601DateFormatter()
isoFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]

let first = isoFormatter.string(from: isoFormatter.date(from: "<CUSTOM_DATE>") ?? Date())
let second = isoFormatter.string(from: isoFormatter.date(from: "<CUSTOM_DATE>") ?? Date())

do {
    let bulkCreated = try await documentsDB.createDocuments(
        databaseId: "<DATABASE_ID>",
        collectionId: "<COLLECTION_ID>",
        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://<REGION>.cloud.appwrite.io/v1')
    .set_project('<YOUR_PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

documents_db = DocumentsDB.new(client)

first = Time.parse('<CUSTOM_DATE>').iso8601
second = Time.parse('<CUSTOM_DATE>').iso8601

documents_db.create_documents(
  database_id: '<DATABASE_ID>',
  collection_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://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<YOUR_PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

DocumentsDB documentsDB = new DocumentsDB(client);

string first = DateTimeOffset.Parse("<CUSTOM_DATE>").ToString("O");
string second = DateTimeOffset.Parse("<CUSTOM_DATE>").ToString("O");

await documentsDB.CreateDocuments(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    documents: new List<object>
    {
        new Dictionary<string, object>
        {
            ["$id"] = ID.Unique(),
            ["$createdAt"] = first,
            ["$updatedAt"] = first,
            // ...your attributes
        },
        new Dictionary<string, object>
        {
            ["$id"] = ID.Unique(),
            ["$createdAt"] = second,
            ["$updatedAt"] = second,
            // ...your attributes
        }
    }
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<YOUR_PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

DocumentsDB documentsDB = DocumentsDB(client);

String first = DateTime.parse('<CUSTOM_DATE>').toIso8601String();
String second = DateTime.parse('<CUSTOM_DATE>').toIso8601String();

await documentsDB.createDocuments(
  databaseId: '<DATABASE_ID>',
  collectionId: '<COLLECTION_ID>',
  documents: [
    {
      '\$id': ID.unique(),
      '\$createdAt': first,
      '\$updatedAt': first,
      // ...your attributes
    },
    {
      '\$id': ID.unique(),
      '\$createdAt': second,
      '\$updatedAt': second,
      // ...your attributes
    }
  ],
);
```
```rust
use appwrite::Client;
use appwrite::services::documents_db::DocumentsDB;
use appwrite::id::ID;
use serde_json::json;

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

    let documents_db = DocumentsDB::new(&client);

    let result = documents_db.create_documents(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
        vec![
            json!({
                "$id": ID::unique(),
                "$createdAt": "2024-01-01T00:00:00.000Z",
                "$updatedAt": "2024-01-01T00:00:00.000Z"
                // ...your attributes
            }),
            json!({
                "$id": ID::unique(),
                "$createdAt": "2024-02-01T00:00:00.000Z",
                "$updatedAt": "2024-02-01T00:00:00.000Z"
                // ...your attributes
            }),
        ],
        None,
    ).await?;

    println!("Bulk create: {:?}", result);
    Ok(())
}
```

##### Bulk upsert

```server-nodejs
await documentsDB.upsertDocuments({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documents: [
        {
            '$id': '<DOCUMENT_ID_OR_NEW_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
documents_db.upsert_documents(
    database_id='<DATABASE_ID>',
    collection_id='<COLLECTION_ID>',
    documents=[
        {
            '$id': '<DOCUMENT_ID_OR_NEW_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\DocumentsDB;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<YOUR_PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$documentsDB = new DocumentsDB($client);

$documentsDB->upsertDocuments(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documents: [
        [
            '$id' => '<DOCUMENT_ID_OR_NEW_ID>',
            '$createdAt' => (new DateTime('<CUSTOM_DATE>'))->format(DATE_ATOM),
            '$updatedAt' => (new DateTime('<CUSTOM_DATE>'))->format(DATE_ATOM),
            // ...your attributes
        ],
    ]
);
```
```server-swift
import Appwrite
import Foundation

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<YOUR_PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let documentsDB = DocumentsDB(client)

let isoFormatter = ISO8601DateFormatter()
isoFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
let createdAt = isoFormatter.string(from: isoFormatter.date(from: "<CUSTOM_DATE>") ?? Date())
let updatedAt = isoFormatter.string(from: isoFormatter.date(from: "<CUSTOM_DATE>") ?? Date())

do {
    let bulkUpserted = try await documentsDB.upsertDocuments(
        databaseId: "<DATABASE_ID>",
        collectionId: "<COLLECTION_ID>",
        documents: [
            [
                "$id": "<DOCUMENT_ID_OR_NEW_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://<REGION>.cloud.appwrite.io/v1')
    .set_project('<YOUR_PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

documents_db = DocumentsDB.new(client)

custom_date = Time.parse('<CUSTOM_DATE>').iso8601

documents_db.upsert_documents(
  database_id: '<DATABASE_ID>',
  collection_id: '<COLLECTION_ID>',
  documents: [
    {
      '$id' => '<DOCUMENT_ID_OR_NEW_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://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<YOUR_PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

DocumentsDB documentsDB = new DocumentsDB(client);

string createdAt = DateTimeOffset.Parse("<CUSTOM_DATE>").ToString("O");
string updatedAt = DateTimeOffset.Parse("<CUSTOM_DATE>").ToString("O");

await documentsDB.UpsertDocuments(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    documents: new List<object>
    {
        new Dictionary<string, object>
        {
            ["$id"] = "<DOCUMENT_ID_OR_NEW_ID>",
            ["$createdAt"] = createdAt,
            ["$updatedAt"] = updatedAt,
            // ...your attributes
        }
    }
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<YOUR_PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

DocumentsDB documentsDB = DocumentsDB(client);

String createdAt = DateTime.parse('<CUSTOM_DATE>').toIso8601String();
String updatedAt = DateTime.parse('<CUSTOM_DATE>').toIso8601String();

await documentsDB.upsertDocuments(
  databaseId: '<DATABASE_ID>',
  collectionId: '<COLLECTION_ID>',
  documents: [
    {
      '\$id': '<DOCUMENT_ID_OR_NEW_ID>',
      '\$createdAt': createdAt,
      '\$updatedAt': updatedAt,
      // ...your attributes
    }
  ],
);
```
```rust
use appwrite::Client;
use appwrite::services::documents_db::DocumentsDB;
use serde_json::json;

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

    let documents_db = DocumentsDB::new(&client);

    let result = documents_db.upsert_documents(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
        vec![
            json!({
                "$id": "<DOCUMENT_ID_OR_NEW_ID>",
                "$createdAt": "2024-01-01T00:00:00.000Z",
                "$updatedAt": "2025-01-01T00:00:00.000Z"
                // ...your attributes
            }),
        ],
        None,
    ).await?;

    println!("Bulk upsert: {:?}", result);
    Ok(())
}
```

### Common use cases

Custom timestamps are particularly useful in several scenarios:

#### Data migration
When migrating existing data from another system, you can preserve the original
creation and modification times:

```server-nodejs
await documentsDB.createDocument({
  databaseId: '<DATABASE_ID>',
  collectionId: 'blog_posts',
  documentId: sdk.ID.unique(),
  data: {
    '$createdAt': '<ORIGINAL_CREATED_AT_ISO>',
    '$updatedAt': '<LAST_MODIFIED_ISO>',
    title: '<TITLE>',
    content: '<CONTENT>'
  }
});
```
```server-php
$documentsDB->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-swift
let _ = try await documentsDB.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
documents_db.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
documents_db.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 documentsDB.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 documentsDB.createDocument(
  databaseId: '<DATABASE_ID>',
  collectionId: 'blog_posts',
  documentId: ID.unique(),
  data: {
    '\$createdAt': '<ORIGINAL_CREATED_AT_ISO>',
    '\$updatedAt': '<LAST_MODIFIED_ISO>',
    'title': '<TITLE>',
    'content': '<CONTENT>'
  },
);
```
```rust
use appwrite::Client;
use appwrite::services::documents_db::DocumentsDB;
use appwrite::id::ID;
use serde_json::json;

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

    let documents_db = DocumentsDB::new(&client);

    let result = documents_db.create_document(
        "<DATABASE_ID>",
        "blog_posts",
        &ID::unique(),
        json!({
            "$createdAt": "<ORIGINAL_CREATED_AT_ISO>",
            "$updatedAt": "<LAST_MODIFIED_ISO>",
            "title": "<TITLE>",
            "content": "<CONTENT>"
        }),
        None,
        None,
    ).await?;

    println!("Created: {:?}", result);
    Ok(())
}
```

#### Backdating records
For historical data entry or when creating records that represent past events:

```server-nodejs
await documentsDB.createDocument({
  databaseId: '<DATABASE_ID>',
  collectionId: 'transactions',
  documentId: sdk.ID.unique(),
  data: {
    '$createdAt': '2023-12-31T23:59:59.000Z',
    '$updatedAt': '2023-12-31T23:59:59.000Z',
    amount: 1000,
    type: 'year-end-bonus'
  }
});
```
```server-php
$documentsDB->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-swift
let _ = try await documentsDB.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
documents_db.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
documents_db.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 documentsDB.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 documentsDB.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'
  },
);
```
```rust
use appwrite::Client;
use appwrite::services::documents_db::DocumentsDB;
use appwrite::id::ID;
use serde_json::json;

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

    let documents_db = DocumentsDB::new(&client);

    let result = documents_db.create_document(
        "<DATABASE_ID>",
        "transactions",
        &ID::unique(),
        json!({
            "$createdAt": "2023-12-31T23:59:59.000Z",
            "$updatedAt": "2023-12-31T23:59:59.000Z",
            "amount": 1000,
            "type": "year-end-bonus"
        }),
        None,
        None,
    ).await?;

    println!("Created: {:?}", result);
    Ok(())
}
```

#### Synchronization
When synchronizing data between systems while maintaining timestamp consistency:

```server-nodejs
await documentsDB.upsertDocument({
  databaseId: '<DATABASE_ID>',
  collectionId: 'users',
  documentId: '<DOCUMENT_ID_OR_NEW_ID>',
  data: {
    '$updatedAt': '<EXTERNAL_LAST_MODIFIED_ISO>',
    profile: '<PROFILE_DATA>'
  }
});
```
```server-php
$documentsDB->upsertDocument(
  databaseId: '<DATABASE_ID>',
  collectionId: 'users',
  documentId: '<DOCUMENT_ID_OR_NEW_ID>',
  data: [
    '$updatedAt' => '<EXTERNAL_LAST_MODIFIED_ISO>',
    'profile' => '<PROFILE_DATA>'
  ]
);
```
```server-swift
let _ = try await documentsDB.upsertDocument(
  databaseId: "<DATABASE_ID>",
  collectionId: "users",
  documentId: "<DOCUMENT_ID_OR_NEW_ID>",
  data: [
    "$updatedAt": "<EXTERNAL_LAST_MODIFIED_ISO>",
    "profile": "<PROFILE_DATA>"
  ]
)
```
```server-python
documents_db.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
documents_db.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 documentsDB.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 documentsDB.upsertDocument(
  databaseId: '<DATABASE_ID>',
  collectionId: 'users',
  documentId: '<DOCUMENT_ID_OR_NEW_ID>',
  data: {
    '\$updatedAt': '<EXTERNAL_LAST_MODIFIED_ISO>',
    'profile': '<PROFILE_DATA>'
  },
);
```
```rust
use appwrite::Client;
use appwrite::services::documents_db::DocumentsDB;
use serde_json::json;

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

    let documents_db = DocumentsDB::new(&client);

    let result = documents_db.upsert_document(
        "<DATABASE_ID>",
        "users",
        "<DOCUMENT_ID_OR_NEW_ID>",
        Some(json!({
            "$updatedAt": "<EXTERNAL_LAST_MODIFIED_ISO>",
            "profile": "<PROFILE_DATA>"
        })),
        None,
        None,
    ).await?;

    println!("Upserted: {:?}", result);
    Ok(())
}
```

**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.

---

## Transactions

URL: https://appwrite.io/docs/products/databases/documentsdb/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 collections.

### 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 document, 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 document changed outside your transaction, the commit fails with a conflict.

**Scope and limitations**

You can stage operations across any database and collection within the same transaction. Schema operations (for example, creating or deleting indexes) are not included in transactions.

### 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 |

### 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.

```client-web
import { Client, DocumentsDB } from 'appwrite';

const client = new Client()
  .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
  .setProject('<PROJECT_ID>');

const documentsDB = new DocumentsDB(client);

const tx = await documentsDB.createTransaction();
// tx.$id is your transactionId
```
```client-react-native
import { Client, DocumentsDB } from 'react-native-appwrite';

const client = new Client()
  .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
  .setProject('<PROJECT_ID>');

const documentsDB = new DocumentsDB(client);

const tx = await documentsDB.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 documentsDB = DocumentsDB(client);

final tx = await documentsDB.createTransaction();
// tx.$id is your transactionId
```
```client-apple
import Appwrite

let client = Client()
  .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
  .setProject("<PROJECT_ID>")

let documentsDB = DocumentsDB(client)

let tx = try await documentsDB.createTransaction()
// tx.$id is your transactionId
```
```client-android-kotlin
import io.appwrite.Client
import io.appwrite.services.DocumentsDB

val client = Client(applicationContext)
  .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
  .setProject("<PROJECT_ID>")

val documentsDB = DocumentsDB(client)

val tx = documentsDB.createTransaction()
// tx.$id is your transactionId
```
```client-android-java
import io.appwrite.Client;
import io.appwrite.services.DocumentsDB;

Client client = new Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>");

DocumentsDB documentsDB = new DocumentsDB(client);

// Create a transaction (asynchronous)
documentsDB.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 documentsDB = new sdk.DocumentsDB(client);

const tx = await documentsDB.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 documentsDB = new sdk.DocumentsDB(client);

const tx = await documentsDB.createTransaction();
// tx.$id is your transactionId
```
```server-python
from appwrite.client import Client
from appwrite.services.documents_db import DocumentsDB

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<API_KEY>')

documents_db = DocumentsDB(client)

tx = documents_db.create_transaction()
### tx.$id is your transactionId
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\DocumentsDB;

$client = new Client();

$client
  ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
  ->setProject('<PROJECT_ID>')
  ->setKey('<API_KEY>')
;

$documentsDB = new DocumentsDB($client);

$tx = $documentsDB->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>')

documents_db = DocumentsDB.new(client)

tx = documents_db.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 documentsDB = new DocumentsDB(client);

var tx = await documentsDB.CreateTransaction();
// tx.$id is your transactionId
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

void main() async {
  Client client = Client();
  DocumentsDB documentsDB = DocumentsDB(client);

  client
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<API_KEY>');

  final tx = await documentsDB.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>"),
  )

  documentsDB := appwrite.NewDocumentsDB(client)

  tx, err := documentsDB.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 documentsDB = DocumentsDB(client)

let tx = try await documentsDB.createTransaction()
// tx.$id is your transactionId
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.DocumentsDB

val client = Client()
  .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
  .setProject("<PROJECT_ID>")
  .setKey("<API_KEY>")

val documentsDB = DocumentsDB(client)

val tx = documentsDB.createTransaction()
// tx.$id is your transactionId
```
```server-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.DocumentsDB;

Client client = new Client()
  .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
  .setProject("<PROJECT_ID>")
  .setKey("<API_KEY>");

DocumentsDB documentsDB = new DocumentsDB(client);

documentsDB.createTransaction(new CoroutineCallback<>((result, error) -> {
  if (error != null) {
    error.printStackTrace();
    return;
  }
  System.out.println(result);
}));
```
```server-rust
use appwrite::Client;
use appwrite::services::documents_db::DocumentsDB;

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

    let documents_db = DocumentsDB::new(&client);

    let tx = documents_db.create_transaction(None).await?;
    // tx.id is your transaction_id

    Ok(())
}
```

### 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 collection is not modified until you commit the transaction.

#### Stage single operations

Create, update, upsert, delete, and atomic numeric operations accept `transactionId`, as well as their bulk versions (createDocuments, updateDocuments, upsertDocuments, deleteDocuments).

```client-web
// Create inside a transaction
await documentsDB.createDocument({
  databaseId: '<DATABASE_ID>',
  collectionId: '<COLLECTION_ID>',
  documentId: '<DOCUMENT_ID>',
  data: { name: 'Walter' },
  transactionId: tx.$id
});

// Increment inside a transaction
await documentsDB.incrementDocumentAttribute({
  databaseId: '<DATABASE_ID>',
  collectionId: '<COLLECTION_ID>',
  documentId: '<DOCUMENT_ID>',
  attribute: 'credits',
  value: 1,
  transactionId: tx.$id
});
```
```client-flutter
// Create inside a transaction
await documentsDB.createDocument(
  databaseId: '<DATABASE_ID>',
  collectionId: '<COLLECTION_ID>',
  documentId: '<DOCUMENT_ID>',
  data: { 'name': 'Walter' },
  transactionId: tx.$id
);

// Increment inside a transaction
await documentsDB.incrementDocumentAttribute(
  databaseId: '<DATABASE_ID>',
  collectionId: '<COLLECTION_ID>',
  documentId: '<DOCUMENT_ID>',
  attribute: 'credits',
  value: 1,
  transactionId: tx.$id
);
```
```client-apple
// Create inside a transaction
try await documentsDB.createDocument(
  databaseId: "<DATABASE_ID>",
  collectionId: "<COLLECTION_ID>",
  documentId: "<DOCUMENT_ID>",
  data: ["name": "Walter"],
  transactionId: tx.$id
)

// Increment inside a transaction
try await documentsDB.incrementDocumentAttribute(
  databaseId: "<DATABASE_ID>",
  collectionId: "<COLLECTION_ID>",
  documentId: "<DOCUMENT_ID>",
  attribute: "credits",
  value: 1,
  transactionId: tx.$id
)
```
```server-kotlin
// Create inside a transaction
documentsDB.createDocument(
  databaseId = "<DATABASE_ID>",
  collectionId = "<COLLECTION_ID>",
  documentId = "<DOCUMENT_ID>",
  data = mapOf("name" to "Walter"),
  transactionId = tx.$id
)

// Increment inside a transaction
documentsDB.incrementDocumentAttribute(
  databaseId = "<DATABASE_ID>",
  collectionId = "<COLLECTION_ID>",
  documentId = "<DOCUMENT_ID>",
  attribute = "credits",
  value = 1,
  transactionId = tx.$id
)
```
```server-java
// Create inside a transaction (asynchronous)
documentsDB.createDocument(
  "<DATABASE_ID>",
  "<COLLECTION_ID>",
  "<DOCUMENT_ID>",
  Map.of("name", "Walter"),
  "<TRANSACTION_ID>",
  new CoroutineCallback<>((document, error) -> {
    if (error != null) {
      error.printStackTrace();
      return null;
    }
    System.out.println(document);
    return null;
  })
);

// Increment inside a transaction (asynchronous)
documentsDB.incrementDocumentAttribute(
  "<DATABASE_ID>",
  "<COLLECTION_ID>",
  "<DOCUMENT_ID>",
  "credits",
  1,
  "<TRANSACTION_ID>",
  new CoroutineCallback<>((document, error) -> {
    if (error != null) {
      error.printStackTrace();
      return null;
    }
    System.out.println(document);
    return null;
  })
);
```
```client-react-native
// Create inside a transaction
await documentsDB.createDocument({
  databaseId: '<DATABASE_ID>',
  collectionId: '<COLLECTION_ID>',
  documentId: '<DOCUMENT_ID>',
  data: { name: 'Walter' },
  transactionId: tx.$id
});

// Increment inside a transaction
await documentsDB.incrementDocumentAttribute({
  databaseId: '<DATABASE_ID>',
  collectionId: '<COLLECTION_ID>',
  documentId: '<DOCUMENT_ID>',
  attribute: 'credits',
  value: 1,
  transactionId: tx.$id
});
```
```server-nodejs
// Update inside a transaction
await documentsDB.updateDocument({
  databaseId: '<DATABASE_ID>',
  collectionId: '<COLLECTION_ID>',
  documentId: '<DOCUMENT_ID>',
  data: { plan: 'pro' },
  transactionId: tx.$id
});

// Delete inside a transaction
await documentsDB.deleteDocument({
  databaseId: '<DATABASE_ID>',
  collectionId: '<COLLECTION_ID>',
  documentId: '<DOCUMENT_ID>',
  transactionId: tx.$id
});
```
```server-python
### Upsert inside a transaction
documents_db.upsert_document(
  database_id = '<DATABASE_ID>',
  collection_id = '<COLLECTION_ID>',
  document_id = '<DOCUMENT_ID>',
  data = { 'name': 'Walter' },
  transaction_id = tx.id
)

### Decrement inside a transaction
documents_db.decrement_document_attribute(
  database_id = '<DATABASE_ID>',
  collection_id = '<COLLECTION_ID>',
  document_id = '<DOCUMENT_ID>',
  attribute = 'credits',
  value = 1,
  transaction_id = tx.id
)
```
```server-php
// Create inside a transaction
$documentsDB->createDocument(
  databaseId: '<DATABASE_ID>',
  collectionId: '<COLLECTION_ID>',
  documentId: '<DOCUMENT_ID>',
  data: ['name' => 'Walter'],
  transactionId: $tx['$id']
);

// Increment inside a transaction
$documentsDB->incrementDocumentAttribute(
  databaseId: '<DATABASE_ID>',
  collectionId: '<COLLECTION_ID>',
  documentId: '<DOCUMENT_ID>',
  attribute: 'credits',
  value: 1,
  transactionId: $tx['$id']
);
```
```server-ruby
### Create inside a transaction
documents_db.create_document(
  database_id: '<DATABASE_ID>',
  collection_id: '<COLLECTION_ID>',
  document_id: '<DOCUMENT_ID>',
  data: { 'name' => 'Walter' },
  transaction_id: tx['$id']
)

### Increment inside a transaction
documents_db.increment_document_attribute(
  database_id: '<DATABASE_ID>',
  collection_id: '<COLLECTION_ID>',
  document_id: '<DOCUMENT_ID>',
  attribute: 'credits',
  value: 1,
  transaction_id: tx['$id']
)
```
```server-dotnet
// Create inside a transaction
await documentsDB.CreateDocument(
  databaseId: "<DATABASE_ID>",
  collectionId: "<COLLECTION_ID>",
  documentId: "<DOCUMENT_ID>",
  data: new Dictionary<string, object> { ["name"] = "Walter" },
  transactionId: tx.Id
);

// Increment inside a transaction
await documentsDB.IncrementDocumentAttribute(
  databaseId: "<DATABASE_ID>",
  collectionId: "<COLLECTION_ID>",
  documentId: "<DOCUMENT_ID>",
  attribute: "credits",
  value: 1,
  transactionId: tx.Id
);
```
```server-dart
// Create inside a transaction
await documentsDB.createDocument(
  databaseId: '<DATABASE_ID>',
  collectionId: '<COLLECTION_ID>',
  documentId: '<DOCUMENT_ID>',
  data: { 'name': 'Walter' },
  transactionId: tx.Id
);

// Increment inside a transaction
await documentsDB.incrementDocumentAttribute(
  databaseId: '<DATABASE_ID>',
  collectionId: '<COLLECTION_ID>',
  documentId: '<DOCUMENT_ID>',
  attribute: 'credits',
  value: 1,
  transactionId: tx.Id
);
```
```server-deno
// Create inside a transaction
await documentsDB.createDocument({
  databaseId: '<DATABASE_ID>',
  collectionId: '<COLLECTION_ID>',
  documentId: '<DOCUMENT_ID>',
  data: { name: 'Walter' },
  transactionId: tx.$id
});

// Increment inside a transaction
await documentsDB.incrementDocumentAttribute({
  databaseId: '<DATABASE_ID>',
  collectionId: '<COLLECTION_ID>',
  documentId: '<DOCUMENT_ID>',
  attribute: 'credits',
  value: 1,
  transactionId: tx.$id
});
```
```server-swift
// Create inside a transaction
try await documentsDB.createDocument(
  databaseId: "<DATABASE_ID>",
  collectionId: "<COLLECTION_ID>",
  documentId: "<DOCUMENT_ID>",
  data: ["name": "Walter"],
  transactionId: tx.$id
)

// Increment inside a transaction
try await documentsDB.incrementDocumentAttribute(
  databaseId: "<DATABASE_ID>",
  collectionId: "<COLLECTION_ID>",
  documentId: "<DOCUMENT_ID>",
  attribute: "credits",
  value: 1,
  transactionId: tx.$id
)
```
```server-rust
use appwrite::Client;
use appwrite::services::documents_db::DocumentsDB;
use serde_json::json;

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

    let documents_db = DocumentsDB::new(&client);

    let tx = documents_db.create_transaction(None).await?;

    // Update inside a transaction
    documents_db.update_document(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
        "<DOCUMENT_ID>",
        Some(json!({ "plan": "pro" })),
        None,
        Some(&tx.id),
    ).await?;

    // Delete inside a transaction
    documents_db.delete_document(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
        "<DOCUMENT_ID>",
        Some(&tx.id),
    ).await?;

    Ok(())
}
```

#### Stage many with createOperations

Use the `createOperations` method to stage multiple operations across databases and collections in a single request:

```server-nodejs
// Stage multiple operations at once
await documentsDB.createOperations({
  transactionId: tx.$id,
  operations: [
    {
      action: 'create',
      databaseId: '<DB_A>',
      collectionId: '<COLLECTION_1>',
      documentId: 'u1',
      data: { name: 'Walter' }
    },
    {
      action: 'increment',
      databaseId: '<DB_B>',
      collectionId: '<COLLECTION_2>',
      documentId: 'u2',
      data: { value: 1, min: 0, attribute: 'credits' }
    }
  ]
});
```
```server-python
documents_db.create_operations(
  transaction_id = tx.id,
  operations = [
    {
      'action': 'create',
      'databaseId': '<DB_A>',
      'collectionId': '<COLLECTION_1>',
      'documentId': 'u1',
      'data': { 'name': 'Walter' }
    },
    {
      'action': 'increment',
      'databaseId': '<DB_B>',
      'collectionId': '<COLLECTION_2>',
      'documentId': 'u2',
      'data': { 'value': 1, 'min': 0, 'attribute': 'credits' }
    }
  ]
)
```
```client-web
await documentsDB.createOperations({
  transactionId: tx.$id,
  operations: [
    {
      action: 'create',
      databaseId: '<DB_A>',
      collectionId: '<COLLECTION_1>',
      documentId: 'u1',
      data: { name: 'Walter' }
    },
    {
      action: 'increment',
      databaseId: '<DB_B>',
      collectionId: '<COLLECTION_2>',
      documentId: 'u2',
      data: { value: 1, min: 0, attribute: 'credits' }
    }
  ]
});
```
```client-flutter
await documentsDB.createOperations(
  transactionId: tx.$id,
  operations: [
    {
      'action': 'create',
      'databaseId': '<DB_A>',
      'collectionId': '<COLLECTION_1>',
      'documentId': 'u1',
      'data': { 'name': 'Walter' }
    },
    {
      'action': 'increment',
      'databaseId': '<DB_B>',
      'collectionId': '<COLLECTION_2>',
      'documentId': 'u2',
      'data': { 'value': 1, 'min': 0, 'attribute': 'credits' }
    }
  ],
);
```
```client-apple
try await documentsDB.createOperations(
  transactionId: tx.$id,
  operations: [
    [
      "action": "create",
      "databaseId": "<DB_A>",
      "collectionId": "<COLLECTION_1>",
      "documentId": "u1",
      "data": ["name": "Walter"]
    ],
    [
      "action": "increment",
      "databaseId": "<DB_B>",
      "collectionId": "<COLLECTION_2>",
      "documentId": "u2",
      "data": ["value": 1, "min": 0, "attribute": "credits"]
    ]
  ]
)
```
```server-kotlin
documentsDB.createOperations(
  transactionId = tx.$id,
  operations = listOf(
    mapOf(
      "action" to "create",
      "databaseId" to "<DB_A>",
      "collectionId" to "<COLLECTION_1>",
      "documentId" to "u1",
      "data" to mapOf("name" to "Walter")
    ),
    mapOf(
      "action" to "increment",
      "databaseId" to "<DB_B>",
      "collectionId" to "<COLLECTION_2>",
      "documentId" to "u2",
      "data" to mapOf("value" to 1, "min" to 0, "attribute" to "credits")
    )
  )
)
```
```server-java
// Stage multiple operations at once (asynchronous)
List<Map<String, Object>> operations = Arrays.asList(
  Map.of(
    "action", "create",
    "databaseId", "<DB_A>",
    "collectionId", "<COLLECTION_1>",
    "documentId", "u1",
    "data", Map.of("name", "Walter")
  ),
  Map.of(
    "action", "increment",
    "databaseId", "<DB_B>",
    "collectionId", "<COLLECTION_2>",
    "documentId", "u2",
    "data", Map.of("value", 1, "min", 0, "attribute", "credits")
  )
);

documentsDB.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 documentsDB.createOperations({
  transactionId: tx.$id,
  operations: [
    {
      action: 'create',
      databaseId: '<DB_A>',
      collectionId: '<COLLECTION_1>',
      documentId: 'u1',
      data: { name: 'Walter' }
    },
    {
      action: 'increment',
      databaseId: '<DB_B>',
      collectionId: '<COLLECTION_2>',
      documentId: 'u2',
      data: { value: 1, min: 0, attribute: 'credits' }
    }
  ]
});
```
```server-deno
await documentsDB.createOperations({
  transactionId: tx.$id,
  operations: [
    {
      action: 'create',
      databaseId: '<DB_A>',
      collectionId: '<COLLECTION_1>',
      documentId: 'u1',
      data: { name: 'Walter' }
    },
    {
      action: 'increment',
      databaseId: '<DB_B>',
      collectionId: '<COLLECTION_2>',
      documentId: 'u2',
      data: { value: 1, min: 0, attribute: 'credits' }
    }
  ]
});
```
```server-php
$documentsDB->createOperations(
  transactionId: $tx['$id'],
  operations: [
    [
      'action' => 'create',
      'databaseId' => '<DB_A>',
      'collectionId' => '<COLLECTION_1>',
      'documentId' => 'u1',
      'data' => [ 'name' => 'Walter' ]
    ],
    [
      'action' => 'increment',
      'databaseId' => '<DB_B>',
      'collectionId' => '<COLLECTION_2>',
      'documentId' => 'u2',
      'data' => [ 'value' => 1, 'min' => 0, 'attribute' => 'credits' ]
    ]
  ]
);
```
```server-ruby
documents_db.create_operations(
  transaction_id: tx['$id'],
  operations: [
    {
      'action' => 'create',
      'databaseId' => '<DB_A>',
      'collectionId' => '<COLLECTION_1>',
      'documentId' => 'u1',
      'data' => { 'name' => 'Walter' }
    },
    {
      'action' => 'increment',
      'databaseId' => '<DB_B>',
      'collectionId' => '<COLLECTION_2>',
      'documentId' => 'u2',
      'data' => { 'value' => 1, 'min' => 0, 'attribute' => 'credits' }
    }
  ]
)
```
```server-dotnet
await documentsDB.CreateOperations(
  transactionId: tx.Id,
  operations: new List<Dictionary<string, object>>
  {
    new Dictionary<string, object>
    {
      ["action"] = "create",
      ["databaseId"] = "<DB_A>",
      ["collectionId"] = "<COLLECTION_1>",
      ["documentId"] = "u1",
      ["data"] = new Dictionary<string, object> { ["name"] = "Walter" }
    },
    new Dictionary<string, object>
    {
      ["action"] = "increment",
      ["databaseId"] = "<DB_B>",
      ["collectionId"] = "<COLLECTION_2>",
      ["documentId"] = "u2",
      ["data"] = new Dictionary<string, object> { ["value"] = 1, ["min"] = 0, ["attribute"] = "credits" }
    }
  }
);
```
```server-dart
await documentsDB.createOperations(
  transactionId: tx.Id,
  operations: [
    {
      'action': 'create',
      'databaseId': '<DB_A>',
      'collectionId': '<COLLECTION_1>',
      'documentId': 'u1',
      'data': { 'name': 'Walter' }
    },
    {
      'action': 'increment',
      'databaseId': '<DB_B>',
      'collectionId': '<COLLECTION_2>',
      'documentId': 'u2',
      'data': { 'value': 1, 'min': 0, 'attribute': 'credits' }
    }
  ]
);
```
```server-swift
try await documentsDB.createOperations(
  transactionId: tx.$id,
  operations: [
    [
      "action": "create",
      "databaseId": "<DB_A>",
      "collectionId": "<COLLECTION_1>",
      "documentId": "u1",
      "data": ["name": "Walter"]
    ],
    [
      "action": "increment",
      "databaseId": "<DB_B>",
      "collectionId": "<COLLECTION_2>",
      "documentId": "u2",
      "data": ["value": 1, "min": 0, "attribute": "credits"]
    ]
  ]
)
```
```server-rust
use appwrite::Client;
use appwrite::services::documents_db::DocumentsDB;
use serde_json::json;

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

    let documents_db = DocumentsDB::new(&client);

    let tx = documents_db.create_transaction(None).await?;

    // Stage multiple operations at once
    documents_db.create_operations(
        &tx.id,
        Some(vec![
            json!({
                "action": "create",
                "databaseId": "<DB_A>",
                "collectionId": "<COLLECTION_1>",
                "documentId": "u1",
                "data": { "name": "Walter" }
            }),
            json!({
                "action": "increment",
                "databaseId": "<DB_B>",
                "collectionId": "<COLLECTION_2>",
                "documentId": "u2",
                "data": { "value": 1, "min": 0, "attribute": "credits" }
            }),
        ]),
    ).await?;

    Ok(())
}
```

Each operation object has this shape:

```json
[
  {
    "action": "create|update|upsert|increment|decrement|delete|bulkCreate|bulkUpdate|bulkUpsert|bulkDelete",
    "databaseId": "<DATABASE_ID>",
    "collectionId": "<COLLECTION_ID>",
    "documentId": "<DOCUMENT_ID>",
    "data": {}
  }
]
```

##### Provide data for each action (createOperations)

##### Create, update, and upsert
Pass a raw data object.
```json
{ "name": "Walter" }
```

##### Increment and decrement
Pass a value and optionally `min`/`max` bounds.
```json
{ "value": 1, "min": 0, "max": 1000, "attribute": "<ATTRIBUTE_NAME>" }
```

##### Bulk create and bulk upsert
Pass an array of raw data objects.
```json
[{ "$id": "123", "name": "Walter" }]
```

##### Bulk update
Pass queries and the data to apply.
```json
{ "queries": [{"method": "equal", "attribute": "status", "values": ["draft"]}], "data": { "status": "published" } }
```

##### Bulk delete
Pass queries to select documents to delete.
```json
{ "queries": [{"method": "equal", "attribute": "archived", "values": [true]}] }
```

### Commit or roll back

When you are done staging operations, call the `updateTransaction` method to finalize the transaction.

```client-web
// Commit
await documentsDB.updateTransaction({
  transactionId: tx.$id,
  commit: true
});

// Or roll back
await documentsDB.updateTransaction({
  transactionId: tx.$id,
  rollback: true
});
```
```client-flutter
// Commit
await documentsDB.updateTransaction(
  transactionId: tx.$id,
  commit: true
);

// Roll back
await documentsDB.updateTransaction(
  transactionId: tx.$id,
  rollback: true
);
```
```client-apple
// Commit
try await documentsDB.updateTransaction(
  transactionId: tx.$id,
  commit: true
)

// Roll back
try await documentsDB.updateTransaction(
  transactionId: tx.$id,
  rollback: true
)
```
```server-kotlin
// Commit
documentsDB.updateTransaction(
  transactionId = tx.$id,
  commit = true
)

// Roll back
documentsDB.updateTransaction(
  transactionId = tx.$id,
  rollback = true
)
```
```server-java
// Commit (asynchronous)
documentsDB.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)
documentsDB.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 documentsDB.updateTransaction({
  transactionId: tx.$id,
  commit: true
});

// Roll back
await documentsDB.updateTransaction({
  transactionId: tx.$id,
  rollback: true
});
```
```server-nodejs
// Commit
await documentsDB.updateTransaction({
  transactionId: tx.$id,
  commit: true
});

// Roll back
await documentsDB.updateTransaction({
  transactionId: tx.$id,
  rollback: true
});
```
```server-python
### Commit
documents_db.update_transaction(
  transaction_id = tx.id,
  commit = True
)

### Roll back
documents_db.update_transaction(
  transaction_id = tx.id,
  rollback = True
)
```
```server-php
// Commit
$documentsDB->updateTransaction(
  transactionId: $tx['$id'],
  commit: true
);

// Roll back
$documentsDB->updateTransaction(
  transactionId: $tx['$id'],
  rollback: true
);
```
```server-ruby
### Commit
documents_db.update_transaction(
  transaction_id: tx['$id'],
  commit: true
)

### Roll back
documents_db.update_transaction(
  transaction_id: tx['$id'],
  rollback: true
)
```
```server-dotnet
// Commit
await documentsDB.UpdateTransaction(
  transactionId: tx.Id,
  commit: true
);

// Roll back
await documentsDB.UpdateTransaction(
  transactionId: tx.Id,
  rollback: true
);
```
```server-dart
// Commit
await documentsDB.updateTransaction(
  transactionId: tx.Id,
  commit: true
);

// Roll back
await documentsDB.updateTransaction(
  transactionId: tx.Id,
  rollback: true
);
```
```server-rust
use appwrite::Client;
use appwrite::services::documents_db::DocumentsDB;

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

    let documents_db = DocumentsDB::new(&client);

    // Commit
    documents_db.update_transaction(
        "<TRANSACTION_ID>",
        Some(true),
        None,
    ).await?;

    // Roll back
    documents_db.update_transaction(
        "<TRANSACTION_ID>",
        None,
        Some(true),
    ).await?;

    Ok(())
}
```

### Handle conflicts

On commit, Appwrite verifies that documents 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.

**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 collections.

[Explore the API references](/docs/references)

---

## MySQL

URL: https://appwrite.io/docs/products/databases/mysql

Appwrite native MySQL databases give you a managed MySQL instance provisioned for your project. You pick the compute specification, and Appwrite provisions the engine in your project's region with its own storage, networking, and credentials, exposed through a per-database public hostname secured with TLS.

A native MySQL database gives you the raw engine: you connect with the `mysql` client or any MySQL driver, bring your own ORM and migrations, and use the full feature set of MySQL, with no Appwrite layer in between. If you want Appwrite SDKs, platform permissions, and serverless scaling for app data, use [TablesDB](/docs/products/databases/tablesdb) for relational data, [DocumentsDB](/docs/products/databases/documentsdb) for schemaless documents, or [VectorsDB](/docs/products/databases/vectorsdb) for embeddings instead.

### Supported versions

Appwrite manages the database container, storage, backups, and networking. You bring the application.

New databases run MySQL 8.4 by default, and you can select MySQL 8.0 on create instead. The database listens on the standard MySQL port, 3306.

The version is selected on create and can be upgraded later. Upgrades run online by provisioning a second instance on the new version, streaming data over with logical replication, and moving traffic once replication is caught up.

A new database starts in a `provisioning` state and becomes `ready` within a few minutes. Large specifications or high-availability configurations take longer to schedule; poll the database status or check the databases list until it reports `ready`.

### Regions

A native database lives in the same region as the project that owns it. There is no per-database region selector. Each database gets a unique hostname in the form `db-<hash>.<region>.appwrite.center`, and data does not leave the region.

### Feature overview

- [Connections](/docs/products/databases/mysql/connections): Connect with the `mysql` client or any driver. Credentials are rotatable through the API.
- [Connection pooling](/docs/products/databases/mysql/connection-pooling): Per-database connection pooler with automatic read/write split when high availability is enabled.
- [Backups](/docs/products/databases/mysql/backups): Scheduled backups, manual backups, restores, and point-in-time recovery.
- [Branches](/docs/products/databases/mysql/branches): Spin up an ephemeral copy of your database in seconds from a storage snapshot.
- [High availability](/docs/products/databases/mysql/high-availability): Up to five synchronous, semi-synchronous, or asynchronous replicas with automatic failover.
- [Scaling](/docs/products/databases/mysql/scaling): Resize compute online and grow storage automatically as your data grows.
- [Network security](/docs/products/databases/mysql/network-security): TLS by default, optional IP allowlists, and a dedicated hostname per database.
- [Monitoring](/docs/products/databases/mysql/monitoring): Poll live health, connection, replica, and storage information from your pipelines and monitoring.
- [Maintenance](/docs/products/databases/mysql/maintenance): Maintenance windows, online version upgrades, and pause and resume.

### Specifications and pricing

Each database runs against one of the following compute specifications, billed monthly:

| Tier | Specification | CPU | Memory | Storage | Bandwidth | Connections | Price |
|-----------------|----------------|---------|--------|---------|-----------|-------------|---------|
| Starter | `s-1vcpu-1gb` | 1 core | 1 GB | 10 GB | 50 GB | 100 | $10/mo |
| Standard | `s-2vcpu-2gb` | 2 cores | 2 GB | 25 GB | 200 GB | 200 | $20/mo |
| Standard Plus | `s-2vcpu-4gb` | 2 cores | 4 GB | 50 GB | 400 GB | 500 | $49/mo |
| Professional | `s-4vcpu-8gb` | 4 cores | 8 GB | 200 GB | 1 TB | 1,000 | $85/mo |
| Business | `s-4vcpu-16gb` | 4 cores | 16 GB | 500 GB | 2 TB | 2,000 | $160/mo |
| Business Plus | `s-4vcpu-32gb` | 4 cores | 32 GB | 1 TB | 5 TB | 4,000 | $299/mo |
| Enterprise | `s-8vcpu-32gb` | 8 cores | 32 GB | 2 TB | 7.5 TB | 5,000 | $425/mo |
| Enterprise Plus | `s-8vcpu-64gb` | 8 cores | 64 GB | 3 TB | 10 TB | 10,000 | $699/mo |

The Storage and Bandwidth columns are the monthly allowances included with each tier. Usage beyond them is billed as overage, and optional features are billed as add-ons on top of the tier price:

| Add-on | Price |
|----------------------------|---------------------------------|
| Storage overage | $0.125 per GB per month |
| Bandwidth overage | $0.08 per GB per month |
| High availability replica | 100% of the tier price, per replica |
| Point-in-time recovery | 20% of the tier price |

You can [resize between tiers](/docs/products/databases/mysql/scaling) as your workload changes.

### Limits

The following limits apply per database. Some are also capped by your plan:

| Limit | Value |
|--------------------------------|-----------------------------------------------------------|
| High availability replicas | 0 - 5 |
| IP allowlist entries | 100 |
| Backup retention | 7 days on Pro |
| Point-in-time recovery window | 1 - 35 days |
| Max simultaneous connections | Set by the specification, from 100 up to 10,000 |

### Billing and plan requirements

Native databases are available on paid plans, and a payment method must be attached to your organization. Each database is billed by the hour against its [compute specification](#specifications), with separate line items for overages and add-ons. See [pricing](/pricing) for details.

---

## Backups

URL: https://appwrite.io/docs/products/databases/mysql/backups

Native databases are backed up automatically. Backups are stored off the database instance and restorable from the API. For finer recovery granularity than scheduled backups, enable point-in-time recovery.

### Automatic backups

Every database gets a default backup policy when it is provisioned, so you have scheduled backups from day one. You can adjust the default policy, or add more policies with different schedules and retention windows.

### Backup policies

A policy defines a schedule (cron expression) and a retention period in days. Create one with:

```server-nodejs
import { Client, Mysql } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const mysql = new Mysql(client);

await mysql.createBackupPolicy({
    databaseId: '<DATABASE_ID>',
    policyId: 'daily',
    name: 'Daily',
    schedule: '0 3 * * *',
    retention: 7,
});
```

```server-deno
import { Client, Mysql } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const mysql = new Mysql(client);

await mysql.createBackupPolicy({
    databaseId: '<DATABASE_ID>',
    policyId: 'daily',
    name: 'Daily',
    schedule: '0 3 * * *',
    retention: 7,
});
```

```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Mysql;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$mysql = new Mysql($client);

$mysql->createBackupPolicy(
    databaseId: '<DATABASE_ID>',
    policyId: 'daily',
    name: 'Daily',
    schedule: '0 3 * * *',
    retention: 7,
);
```

```server-python
from appwrite.client import Client
from appwrite.services.mysql import Mysql

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

mysql = Mysql(client)

mysql.create_backup_policy(
    database_id='<DATABASE_ID>',
    policy_id='daily',
    name='Daily',
    schedule='0 3 * * *',
    retention=7,
)
```

```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

mysql = Mysql.new(client)

mysql.create_backup_policy(
    database_id: '<DATABASE_ID>',
    policy_id: 'daily',
    name: 'Daily',
    schedule: '0 3 * * *',
    retention: 7,
)
```

```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Mysql mysql = new Mysql(client);

await mysql.CreateBackupPolicy(
    databaseId: "<DATABASE_ID>",
    policyId: "daily",
    name: "Daily",
    schedule: "0 3 * * *",
    retention: 7
);
```

```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Mysql mysql = Mysql(client);

await mysql.createBackupPolicy(
    databaseId: '<DATABASE_ID>',
    policyId: 'daily',
    name: 'Daily',
    schedule: '0 3 * * *',
    retention: 7,
);
```

```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Mysql

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val mysql = Mysql(client)

mysql.createBackupPolicy(
    databaseId = "<DATABASE_ID>",
    policyId = "daily",
    name = "Daily",
    schedule = "0 3 * * *",
    retention = 7,
)
```

```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let mysql = Mysql(client)

_ = try await mysql.createBackupPolicy(
    databaseId: "<DATABASE_ID>",
    policyId: "daily",
    name: "Daily",
    schedule: "0 3 * * *",
    retention: 7
)
```

```server-go
package main

import (
    "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("<YOUR_API_KEY>"),
    )

    service := appwrite.NewMysql(client)

    _, err := service.CreateBackupPolicy("<DATABASE_ID>", "daily", "Daily", "0 3 * * *", 7)
    if err != nil {
        panic(err)
    }
}
```

```server-rust
use appwrite::client::Client;
use appwrite::services::mysql::Mysql;

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

    let mysql = Mysql::new(&client);

    mysql.create_backup_policy("<DATABASE_ID>", "daily", "Daily", "0 3 * * *", 7, None, None).await?;

    Ok(())
}
```

```bash
curl -X POST \
  -H "X-Appwrite-Project: <PROJECT_ID>" \
  -H "X-Appwrite-Key: <API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{
      "policyId": "daily",
      "name": "Daily",
      "schedule": "0 3 * * *",
      "retention": 7
  }' \
  https://<REGION>.cloud.appwrite.io/v1/mysql/<DATABASE_ID>/backups/policies
```

| Parameter | Value | Description |
| ----------- | ----------------------- | ------------------------------------------ |
| `policyId` | custom ID or `unique()` | Policy identifier |
| `name` | text | Display name |
| `schedule` | cron expression | When backups run, for example `0 3 * * *` |
| `retention` | days | How long backups from this policy are kept; 7 on Pro |

List, update, and delete policies with `listBackupPolicies`, `updateBackupPolicy`, and `deleteBackupPolicy`. Plan limits apply: Pro allows one policy with a daily schedule and 7-day retention.

### Manual backups

Take an on-demand backup before a risky change:

```server-nodejs
import { Client, Mysql } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const mysql = new Mysql(client);

const backup = await mysql.createBackup({
    databaseId: '<DATABASE_ID>',
});
```

```server-deno
import { Client, Mysql } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const mysql = new Mysql(client);

const backup = await mysql.createBackup({
    databaseId: '<DATABASE_ID>',
});
```

```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Mysql;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$mysql = new Mysql($client);

$backup = $mysql->createBackup(
    databaseId: '<DATABASE_ID>',
);
```

```server-python
from appwrite.client import Client
from appwrite.services.mysql import Mysql

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

mysql = Mysql(client)

backup = mysql.create_backup(
    database_id='<DATABASE_ID>',
)
```

```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

mysql = Mysql.new(client)

backup = mysql.create_backup(
    database_id: '<DATABASE_ID>',
)
```

```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Mysql mysql = new Mysql(client);

var backup = await mysql.CreateBackup(
    databaseId: "<DATABASE_ID>"
);
```

```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Mysql mysql = Mysql(client);

final backup = await mysql.createBackup(
    databaseId: '<DATABASE_ID>',
);
```

```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Mysql

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val mysql = Mysql(client)

val backup = mysql.createBackup(
    databaseId = "<DATABASE_ID>",
)
```

```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let mysql = Mysql(client)

let backup = try await mysql.createBackup(
    databaseId: "<DATABASE_ID>"
)
```

```server-go
package main

import (
    "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("<YOUR_API_KEY>"),
    )

    service := appwrite.NewMysql(client)

    result, err := service.CreateBackup("<DATABASE_ID>")
    if err != nil {
        panic(err)
    }
    _ = result
}
```

```server-rust
use appwrite::client::Client;
use appwrite::services::mysql::Mysql;

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

    let mysql = Mysql::new(&client);

    let backup = mysql.create_backup("<DATABASE_ID>", None).await?;

    Ok(())
}
```

```bash
curl -X POST \
  -H "X-Appwrite-Project: <PROJECT_ID>" \
  -H "X-Appwrite-Key: <API_KEY>" \
  https://<REGION>.cloud.appwrite.io/v1/mysql/<DATABASE_ID>/backups
```

The backup runs asynchronously with status `pending` until it completes. List backups and check their status with `listBackups`, or fetch one with `getBackup`.

### Restore from a backup

Restoring replaces the database's current data with the backup's contents. The database status moves to `restoring` and returns to `ready` when the restore completes. Connections are still accepted while the restore runs, but data is replaced when it completes.

```server-nodejs
import { Client, Mysql } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const mysql = new Mysql(client);

await mysql.createRestoration({
    databaseId: '<DATABASE_ID>',
    type: 'backup',
    backupId: '<BACKUP_ID>',
});
```

```server-deno
import { Client, Mysql } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const mysql = new Mysql(client);

await mysql.createRestoration({
    databaseId: '<DATABASE_ID>',
    type: 'backup',
    backupId: '<BACKUP_ID>',
});
```

```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Mysql;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$mysql = new Mysql($client);

$mysql->createRestoration(
    databaseId: '<DATABASE_ID>',
    type: 'backup',
    backupId: '<BACKUP_ID>',
);
```

```server-python
from appwrite.client import Client
from appwrite.services.mysql import Mysql

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

mysql = Mysql(client)

mysql.create_restoration(
    database_id='<DATABASE_ID>',
    type='backup',
    backup_id='<BACKUP_ID>',
)
```

```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

mysql = Mysql.new(client)

mysql.create_restoration(
    database_id: '<DATABASE_ID>',
    type: 'backup',
    backup_id: '<BACKUP_ID>',
)
```

```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Mysql mysql = new Mysql(client);

await mysql.CreateRestoration(
    databaseId: "<DATABASE_ID>",
    type: "backup",
    backupId: "<BACKUP_ID>"
);
```

```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Mysql mysql = Mysql(client);

await mysql.createRestoration(
    databaseId: '<DATABASE_ID>',
    type: 'backup',
    backupId: '<BACKUP_ID>',
);
```

```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Mysql

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val mysql = Mysql(client)

mysql.createRestoration(
    databaseId = "<DATABASE_ID>",
    type = "backup",
    backupId = "<BACKUP_ID>",
)
```

```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let mysql = Mysql(client)

_ = try await mysql.createRestoration(
    databaseId: "<DATABASE_ID>",
    type: "backup",
    backupId: "<BACKUP_ID>"
)
```

```server-go
package main

import (
    "github.com/appwrite/sdk-for-go/appwrite"
    "github.com/appwrite/sdk-for-go/mysql"
)

func main() {
    client := appwrite.NewClient(
        appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"),
        appwrite.WithProject("<PROJECT_ID>"),
        appwrite.WithKey("<YOUR_API_KEY>"),
    )

    service := appwrite.NewMysql(client)

    _, err := service.CreateRestoration(
        "<DATABASE_ID>",
        mysql.WithCreateRestorationType("backup"),
        mysql.WithCreateRestorationBackupId("<BACKUP_ID>"),
    )
    if err != nil {
        panic(err)
    }
}
```

```server-rust
use appwrite::client::Client;
use appwrite::services::mysql::Mysql;

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

    let mysql = Mysql::new(&client);

    mysql.create_restoration("<DATABASE_ID>", Some("backup"), Some("<BACKUP_ID>"), None).await?;

    Ok(())
}
```

```bash
curl -X POST \
  -H "X-Appwrite-Project: <PROJECT_ID>" \
  -H "X-Appwrite-Key: <API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{
      "type": "backup",
      "backupId": "<BACKUP_ID>"
  }' \
  https://<REGION>.cloud.appwrite.io/v1/mysql/<DATABASE_ID>/restorations
```

Track progress with `getRestoration` or the database status.

**Restores overwrite current data**

Everything written after the backup was taken is lost when you restore it. If you need the current state too, take a manual backup first, or use a [branch](/docs/products/databases/mysql/branches) to inspect data without touching the live database.

### Point-in-time recovery

Scheduled backups recover to fixed snapshots. Point-in-time recovery (PITR) continuously archives the binary log, so you can restore to any moment inside the retention window, for example the second before a bad migration ran.

PITR is enabled by default when you create a database through the API, with 7 days of retention. Change it when creating the database, or later through the API:

```server-nodejs
import { Client, Mysql } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const mysql = new Mysql(client);

await mysql.update({
    databaseId: '<DATABASE_ID>',
    pitr: true,
    pitrRetentionDays: 7,
});
```

```server-deno
import { Client, Mysql } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const mysql = new Mysql(client);

await mysql.update({
    databaseId: '<DATABASE_ID>',
    pitr: true,
    pitrRetentionDays: 7,
});
```

```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Mysql;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$mysql = new Mysql($client);

$mysql->update(
    databaseId: '<DATABASE_ID>',
    pitr: true,
    pitrRetentionDays: 7,
);
```

```server-python
from appwrite.client import Client
from appwrite.services.mysql import Mysql

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

mysql = Mysql(client)

mysql.update(
    database_id='<DATABASE_ID>',
    pitr=True,
    pitr_retention_days=7,
)
```

```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

mysql = Mysql.new(client)

mysql.update(
    database_id: '<DATABASE_ID>',
    pitr: true,
    pitr_retention_days: 7,
)
```

```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Mysql mysql = new Mysql(client);

await mysql.Update(
    databaseId: "<DATABASE_ID>",
    pitr: true,
    pitrRetentionDays: 7
);
```

```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Mysql mysql = Mysql(client);

await mysql.update(
    databaseId: '<DATABASE_ID>',
    pitr: true,
    pitrRetentionDays: 7,
);
```

```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Mysql

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val mysql = Mysql(client)

mysql.update(
    databaseId = "<DATABASE_ID>",
    pitr = true,
    pitrRetentionDays = 7,
)
```

```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let mysql = Mysql(client)

_ = try await mysql.update(
    databaseId: "<DATABASE_ID>",
    pitr: true,
    pitrRetentionDays: 7
)
```

```server-go
package main

import (
    "github.com/appwrite/sdk-for-go/appwrite"
    "github.com/appwrite/sdk-for-go/mysql"
)

func main() {
    client := appwrite.NewClient(
        appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"),
        appwrite.WithProject("<PROJECT_ID>"),
        appwrite.WithKey("<YOUR_API_KEY>"),
    )

    service := appwrite.NewMysql(client)

    _, err := service.Update(
        "<DATABASE_ID>",
        mysql.WithUpdatePitr(true),
        mysql.WithUpdatePitrRetentionDays(7),
    )
    if err != nil {
        panic(err)
    }
}
```

```server-rust
use appwrite::client::Client;
use appwrite::services::mysql::Mysql;

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

    let mysql = Mysql::new(&client);

    mysql.update("<DATABASE_ID>", None, None, None, None, None, None, None, None, Some(true), Some(7), None, None, None, None, None, None, None, None, None, None).await?;

    Ok(())
}
```

```bash
curl -X PATCH \
  -H "X-Appwrite-Project: <PROJECT_ID>" \
  -H "X-Appwrite-Key: <API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{
      "pitr": true,
      "pitrRetentionDays": 7
  }' \
  https://<REGION>.cloud.appwrite.io/v1/mysql/<DATABASE_ID>
```

`pitrRetentionDays` accepts 1 to 35 days. PITR is billed as an add-on on top of your specification; see [pricing](/pricing).

#### Check the recovery window

You can retrieve the time ranges you can restore to. Right after enabling PITR, no recovery window is available yet; the window opens once continuous archiving has captured its first segment.

```server-nodejs
import { Client, Mysql } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const mysql = new Mysql(client);

const windows = await mysql.getPitr({
    databaseId: '<DATABASE_ID>',
});
```

```server-deno
import { Client, Mysql } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const mysql = new Mysql(client);

const windows = await mysql.getPitr({
    databaseId: '<DATABASE_ID>',
});
```

```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Mysql;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$mysql = new Mysql($client);

$windows = $mysql->getPitr(
    databaseId: '<DATABASE_ID>',
);
```

```server-python
from appwrite.client import Client
from appwrite.services.mysql import Mysql

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

mysql = Mysql(client)

windows = mysql.get_pitr(
    database_id='<DATABASE_ID>',
)
```

```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

mysql = Mysql.new(client)

windows = mysql.get_pitr(
    database_id: '<DATABASE_ID>',
)
```

```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Mysql mysql = new Mysql(client);

var windows = await mysql.GetPitr(
    databaseId: "<DATABASE_ID>"
);
```

```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Mysql mysql = Mysql(client);

final windows = await mysql.getPitr(
    databaseId: '<DATABASE_ID>',
);
```

```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Mysql

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val mysql = Mysql(client)

val windows = mysql.getPitr(
    databaseId = "<DATABASE_ID>",
)
```

```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let mysql = Mysql(client)

let windows = try await mysql.getPitr(
    databaseId: "<DATABASE_ID>"
)
```

```server-go
package main

import (
    "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("<YOUR_API_KEY>"),
    )

    service := appwrite.NewMysql(client)

    result, err := service.GetPitr("<DATABASE_ID>")
    if err != nil {
        panic(err)
    }
    _ = result
}
```

```server-rust
use appwrite::client::Client;
use appwrite::services::mysql::Mysql;

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

    let mysql = Mysql::new(&client);

    let windows = mysql.get_pitr("<DATABASE_ID>").await?;

    Ok(())
}
```

```bash
curl -X GET \
  -H "X-Appwrite-Project: <PROJECT_ID>" \
  -H "X-Appwrite-Key: <API_KEY>" \
  https://<REGION>.cloud.appwrite.io/v1/mysql/<DATABASE_ID>/pitr
```

#### Restore to a point in time

Pass an ISO 8601 timestamp inside the recovery window:

```server-nodejs
import { Client, Mysql } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const mysql = new Mysql(client);

await mysql.createRestoration({
    databaseId: '<DATABASE_ID>',
    type: 'pitr',
    targetTime: '2026-01-01T00:00:00.000+00:00',
});
```

```server-deno
import { Client, Mysql } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const mysql = new Mysql(client);

await mysql.createRestoration({
    databaseId: '<DATABASE_ID>',
    type: 'pitr',
    targetTime: '2026-01-01T00:00:00.000+00:00',
});
```

```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Mysql;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$mysql = new Mysql($client);

$mysql->createRestoration(
    databaseId: '<DATABASE_ID>',
    type: 'pitr',
    targetTime: '2026-01-01T00:00:00.000+00:00',
);
```

```server-python
from appwrite.client import Client
from appwrite.services.mysql import Mysql

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

mysql = Mysql(client)

mysql.create_restoration(
    database_id='<DATABASE_ID>',
    type='pitr',
    target_time='2026-01-01T00:00:00.000+00:00',
)
```

```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

mysql = Mysql.new(client)

mysql.create_restoration(
    database_id: '<DATABASE_ID>',
    type: 'pitr',
    target_time: '2026-01-01T00:00:00.000+00:00',
)
```

```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Mysql mysql = new Mysql(client);

await mysql.CreateRestoration(
    databaseId: "<DATABASE_ID>",
    type: "pitr",
    targetTime: "2026-01-01T00:00:00.000+00:00"
);
```

```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Mysql mysql = Mysql(client);

await mysql.createRestoration(
    databaseId: '<DATABASE_ID>',
    type: 'pitr',
    targetTime: "2026-01-01T00:00:00.000+00:00",
);
```

```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Mysql

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val mysql = Mysql(client)

mysql.createRestoration(
    databaseId = "<DATABASE_ID>",
    type = "pitr",
    targetTime = "2026-01-01T00:00:00.000+00:00",
)
```

```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let mysql = Mysql(client)

_ = try await mysql.createRestoration(
    databaseId: "<DATABASE_ID>",
    type: "pitr",
    targetTime: "2026-01-01T00:00:00.000+00:00"
)
```

```server-go
package main

import (
    "github.com/appwrite/sdk-for-go/appwrite"
    "github.com/appwrite/sdk-for-go/mysql"
)

func main() {
    client := appwrite.NewClient(
        appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"),
        appwrite.WithProject("<PROJECT_ID>"),
        appwrite.WithKey("<YOUR_API_KEY>"),
    )

    service := appwrite.NewMysql(client)

    _, err := service.CreateRestoration(
        "<DATABASE_ID>",
        mysql.WithCreateRestorationType("pitr"),
        mysql.WithCreateRestorationTargetTime("2026-01-01T00:00:00.000+00:00"),
    )
    if err != nil {
        panic(err)
    }
}
```

```server-rust
use appwrite::client::Client;
use appwrite::services::mysql::Mysql;

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

    let mysql = Mysql::new(&client);

    mysql.create_restoration("<DATABASE_ID>", Some("pitr"), None, Some("2026-01-01T00:00:00.000+00:00")).await?;

    Ok(())
}
```

```bash
curl -X POST \
  -H "X-Appwrite-Project: <PROJECT_ID>" \
  -H "X-Appwrite-Key: <API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{
      "type": "pitr",
      "targetTime": "2026-01-01T00:00:00.000+00:00"
  }' \
  https://<REGION>.cloud.appwrite.io/v1/mysql/<DATABASE_ID>/restorations
```

Like a backup restore, a PITR restore is in-place: the database is unavailable while restoring and everything after the target time is discarded.

### Limits

| Limit | Value |
| ---------------- | -------------- |
| Backup retention | 1 - 365 days |
| PITR retention | 1 - 35 days |
| Backup policies | Plan-dependent |

---

## Branches

URL: https://appwrite.io/docs/products/databases/mysql/branches

A branch is a short-lived, isolated copy of your database. It has its own hostname and reuses the parent's credentials, because it is a snapshot copy of the parent's storage volume taken at a point in time. Branches are not replicas: once created, they diverge from the parent and never sync back.

**Branches do not merge back**

There is no branch merge operation. Use a branch to validate a migration, data repair, or application change, then intentionally cut application traffic over to the validated database or copy the data you want back with engine-native tools. Appwrite does not reconcile two diverged database histories for you.

Use cases:

- **Preview environments**: one branch per pull request, destroyed when the PR closes
- **Test migrations**: apply a destructive `ALTER` against the branch first, observe the behavior, then run it against the source
- **Reproduce a bug**: branch the database, attach a debugger, throw the branch away when done
- **Heavy analytical queries**: `EXPLAIN ANALYZE` experiments against a branch cannot slow down the primary

### How it works

Creating a branch takes a fast `CHECKPOINT` on the parent, snapshots the parent's storage volume with a near-instant copy-on-write operation, and provisions a branch instance from the snapshot on the same engine version with its own isolated storage. Branch compute is fixed and lightweight, enough to validate a change rather than carry production load, and is not configurable. The parent and the branch share storage at the moment of branching; storage cost grows as the two diverge.

### Create a branch

```server-nodejs
import { Client, Mysql } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const mysql = new Mysql(client);

await mysql.createBranch({
    databaseId: '<DATABASE_ID>',
    branchId: 'preview',
    ttl: 86400,
});
```

```server-deno
import { Client, Mysql } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const mysql = new Mysql(client);

await mysql.createBranch({
    databaseId: '<DATABASE_ID>',
    branchId: 'preview',
    ttl: 86400,
});
```

```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Mysql;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$mysql = new Mysql($client);

$mysql->createBranch(
    databaseId: '<DATABASE_ID>',
    branchId: 'preview',
    ttl: 86400,
);
```

```server-python
from appwrite.client import Client
from appwrite.services.mysql import Mysql

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

mysql = Mysql(client)

mysql.create_branch(
    database_id='<DATABASE_ID>',
    branch_id='preview',
    ttl=86400,
)
```

```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

mysql = Mysql.new(client)

mysql.create_branch(
    database_id: '<DATABASE_ID>',
    branch_id: 'preview',
    ttl: 86400,
)
```

```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Mysql mysql = new Mysql(client);

await mysql.CreateBranch(
    databaseId: "<DATABASE_ID>",
    branchId: "preview",
    ttl: 86400
);
```

```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Mysql mysql = Mysql(client);

await mysql.createBranch(
    databaseId: '<DATABASE_ID>',
    branchId: 'preview',
    ttl: 86400,
);
```

```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Mysql

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val mysql = Mysql(client)

mysql.createBranch(
    databaseId = "<DATABASE_ID>",
    branchId = "preview",
    ttl = 86400,
)
```

```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let mysql = Mysql(client)

_ = try await mysql.createBranch(
    databaseId: "<DATABASE_ID>",
    branchId: "preview",
    ttl: 86400
)
```

```server-go
package main

import (
    "github.com/appwrite/sdk-for-go/appwrite"
    "github.com/appwrite/sdk-for-go/mysql"
)

func main() {
    client := appwrite.NewClient(
        appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"),
        appwrite.WithProject("<PROJECT_ID>"),
        appwrite.WithKey("<YOUR_API_KEY>"),
    )

    service := appwrite.NewMysql(client)

    _, err := service.CreateBranch(
        "<DATABASE_ID>",
        mysql.WithCreateBranchBranchId("preview"),
        mysql.WithCreateBranchTtl(86400),
    )
    if err != nil {
        panic(err)
    }
}
```

```server-rust
use appwrite::client::Client;
use appwrite::services::mysql::Mysql;

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

    let mysql = Mysql::new(&client);

    mysql.create_branch("<DATABASE_ID>", Some("preview"), Some(86400)).await?;

    Ok(())
}
```

```bash
curl -X POST \
  -H "X-Appwrite-Project: <PROJECT_ID>" \
  -H "X-Appwrite-Key: <YOUR_API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{
      "branchId": "preview",
      "ttl": 86400
  }' \
  https://<REGION>.cloud.appwrite.io/v1/mysql/<DATABASE_ID>/branches
```

Both fields are optional:

| Field | Default | Purpose |
| ---------- | ------------------ | ------------------------------------------------------------------- |
| `branchId` | auto-generated | Custom ID (`a-z`, `A-Z`, `0-9`, `.`, `-`, `_`, max 36 chars) |
| `ttl` | `86400` (24 hours) | Lifetime in seconds before the branch expires (min 300, max 604800) |

The call is asynchronous and returns immediately while the branch provisions in the background. When the TTL elapses, the branch and its storage are removed automatically.

### List branches and connect

Each entry carries its metadata and connection details, so there is no separate credentials call:

```server-nodejs
import { Client, Mysql } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const mysql = new Mysql(client);

const branches = await mysql.listBranches({
    databaseId: '<DATABASE_ID>',
});
```

```server-deno
import { Client, Mysql } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const mysql = new Mysql(client);

const branches = await mysql.listBranches({
    databaseId: '<DATABASE_ID>',
});
```

```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Mysql;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$mysql = new Mysql($client);

$branches = $mysql->listBranches(
    databaseId: '<DATABASE_ID>',
);
```

```server-python
from appwrite.client import Client
from appwrite.services.mysql import Mysql

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

mysql = Mysql(client)

branches = mysql.list_branches(
    database_id='<DATABASE_ID>',
)
```

```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

mysql = Mysql.new(client)

branches = mysql.list_branches(
    database_id: '<DATABASE_ID>',
)
```

```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Mysql mysql = new Mysql(client);

var branches = await mysql.ListBranches(
    databaseId: "<DATABASE_ID>"
);
```

```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Mysql mysql = Mysql(client);

final branches = await mysql.listBranches(
    databaseId: '<DATABASE_ID>',
);
```

```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Mysql

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val mysql = Mysql(client)

val branches = mysql.listBranches(
    databaseId = "<DATABASE_ID>",
)
```

```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let mysql = Mysql(client)

let branches = try await mysql.listBranches(
    databaseId: "<DATABASE_ID>"
)
```

```server-go
package main

import (
    "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("<YOUR_API_KEY>"),
    )

    service := appwrite.NewMysql(client)

    result, err := service.ListBranches("<DATABASE_ID>")
    if err != nil {
        panic(err)
    }
    _ = result
}
```

```server-rust
use appwrite::client::Client;
use appwrite::services::mysql::Mysql;

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

    let mysql = Mysql::new(&client);

    let branches = mysql.list_branches("<DATABASE_ID>").await?;

    Ok(())
}
```

```bash
curl -X GET \
  -H "X-Appwrite-Project: <PROJECT_ID>" \
  -H "X-Appwrite-Key: <YOUR_API_KEY>" \
  https://<REGION>.cloud.appwrite.io/v1/mysql/<DATABASE_ID>/branches
```

A branch gets its own hostname, and reuses the parent's username and password because it is a snapshot copy of the parent's storage. The port is the standard `3306`; branches have no connection pooler. Connect with the branch's `connectionString` straight from the response:

```bash
mysql --defaults-extra-file=<(printf '[client]\npassword=%s\n' "$BRANCH_PASSWORD") -h <branch host> -P 3306 -u admin.<hash> -D <branch database>
```

### Delete a branch

Deleting a branch removes the branch's instance, its storage volume, and the underlying snapshot. There is no soft delete: once the branch is gone, the data is gone.

```server-nodejs
import { Client, Mysql } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const mysql = new Mysql(client);

await mysql.deleteBranch({
    databaseId: '<DATABASE_ID>',
    branchId: 'preview',
});
```

```server-deno
import { Client, Mysql } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const mysql = new Mysql(client);

await mysql.deleteBranch({
    databaseId: '<DATABASE_ID>',
    branchId: 'preview',
});
```

```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Mysql;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$mysql = new Mysql($client);

$mysql->deleteBranch(
    databaseId: '<DATABASE_ID>',
    branchId: 'preview',
);
```

```server-python
from appwrite.client import Client
from appwrite.services.mysql import Mysql

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

mysql = Mysql(client)

mysql.delete_branch(
    database_id='<DATABASE_ID>',
    branch_id='preview',
)
```

```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

mysql = Mysql.new(client)

mysql.delete_branch(
    database_id: '<DATABASE_ID>',
    branch_id: 'preview',
)
```

```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Mysql mysql = new Mysql(client);

await mysql.DeleteBranch(
    databaseId: "<DATABASE_ID>",
    branchId: "preview"
);
```

```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Mysql mysql = Mysql(client);

await mysql.deleteBranch(
    databaseId: '<DATABASE_ID>',
    branchId: 'preview',
);
```

```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Mysql

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val mysql = Mysql(client)

mysql.deleteBranch(
    databaseId = "<DATABASE_ID>",
    branchId = "preview",
)
```

```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let mysql = Mysql(client)

_ = try await mysql.deleteBranch(
    databaseId: "<DATABASE_ID>",
    branchId: "preview"
)
```

```server-go
package main

import (
    "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("<YOUR_API_KEY>"),
    )

    service := appwrite.NewMysql(client)

    _, err := service.DeleteBranch("<DATABASE_ID>", "preview")
    if err != nil {
        panic(err)
    }
}
```

```server-rust
use appwrite::client::Client;
use appwrite::services::mysql::Mysql;

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

    let mysql = Mysql::new(&client);

    mysql.delete_branch("<DATABASE_ID>", "preview").await?;

    Ok(())
}
```

```bash
curl -X DELETE \
  -H "X-Appwrite-Project: <PROJECT_ID>" \
  -H "X-Appwrite-Key: <YOUR_API_KEY>" \
  https://<REGION>.cloud.appwrite.io/v1/mysql/<DATABASE_ID>/branches/preview
```

### Billing

A branch runs on fixed, lightweight compute, so its cost is dominated by storage. The snapshot is free at the moment of branching; storage cost accumulates as the branch's data diverges from the parent. There is no separate branch line item, branches roll into your regular database storage and compute totals.

### Use case: a development copy of production

Branches also separate daily development from production without maintaining seed scripts. Create a long-lived branch from the production database and point local and staging environments at the branch's hostname. Developers query production-shaped data, and every write stays on the branch, so production is never at risk from a bad migration or a careless `DELETE`.

Branch data diverges from the parent from the moment of branching. To refresh, delete the branch and create a new one with the same `branchId`; the new branch starts from the parent's current state. Set a `ttl` if the branch should clean itself up, or omit it for a permanent development copy.

### Use case: per-PR preview database

A CI pipeline that branches on every pull request and tears down on close:

```yaml
name: preview-database

on:
    pull_request:
        types: [opened, reopened, closed]

jobs:
    branch:
        if: github.event.action != 'closed'
        runs-on: ubuntu-latest
        steps:
            - name: Create branch
              run: |
                  curl -X POST \
                    -H "X-Appwrite-Project: ${{ vars.APPWRITE_PROJECT_ID }}" \
                    -H "X-Appwrite-Key: ${{ secrets.APPWRITE_API_KEY }}" \
                    -H "Content-Type: application/json" \
                    -d '{"branchId": "pr-${{ github.event.number }}", "ttl": 604800}' \
                    https://<REGION>.cloud.appwrite.io/v1/mysql/${{ vars.DATABASE_ID }}/branches || true

    teardown:
        if: github.event.action == 'closed'
        runs-on: ubuntu-latest
        steps:
            - name: Delete branch
              run: |
                  curl -X DELETE \
                    -H "X-Appwrite-Project: ${{ vars.APPWRITE_PROJECT_ID }}" \
                    -H "X-Appwrite-Key: ${{ secrets.APPWRITE_API_KEY }}" \
                    https://<REGION>.cloud.appwrite.io/v1/mysql/${{ vars.DATABASE_ID }}/branches/pr-${{ github.event.number }}
```

The `|| true` on the create call makes the workflow idempotent: if the branch already exists, the call is a no-op.

---

## Security and access control

URL: https://appwrite.io/docs/products/databases/mysql/concepts/access-control

MySQL controls access in two layers:

- An **account** is an identity that can connect.
- **Privileges** decide what an account may do to each schema, table, or column.

This page covers how that model works, what your Appwrite database's `admin` account can and cannot do, and the patterns that restrict access to data in practice.

### The privilege model

A MySQL account is a user name plus a host pattern, such as `'reporting'@'%'`. Privileges attach to accounts at four scopes: global, schema, table, and column. `GRANT` gives them and `REVOKE` takes them away:

| Privilege | Allows |
| --- | --- |
| `SELECT` | Read rows |
| `INSERT` / `UPDATE` / `DELETE` | Write rows |
| `CREATE` / `ALTER` / `DROP` | Change schema objects |
| `INDEX` | Create and drop indexes |
| `EXECUTE` | Run stored procedures |
| `ALL PRIVILEGES` | Everything at the granted scope |

Inspect any account's effective privileges with `SHOW GRANTS`.

### Your database's admin account

Your Appwrite database provides one account, `admin`, with full privileges scoped to your schema:

```sql
SHOW GRANTS FOR CURRENT_USER();
```

```text
GRANT USAGE ON *.* TO `admin`@`%`
GRANT ALL PRIVILEGES ON `your-database`.* TO `admin`@`%`
```

`admin` owns your data completely: all reads, writes, schema changes, and index management. It does not hold global privileges, so instance-level operations stay with the platform, and it cannot create additional MySQL accounts:

```sql
CREATE USER 'reporting'@'%' IDENTIFIED BY 'a-strong-password';
```

```text
ERROR 1227 (42000): Access denied; you need (at least one of) the CREATE USER privilege(s) for this operation
```

This means access control below the `admin` account happens in your application and schema design rather than in MySQL's account system. The sections below cover the tools that work within that model.

### Restrict what queries can see with views

A view exposes a controlled subset of a table: fewer columns, fewer rows, or both. Give your application's read paths a view instead of the raw table, and sensitive columns never leave the database:

```sql
CREATE TABLE users (
    id            BIGINT AUTO_INCREMENT PRIMARY KEY,
    email         VARCHAR(255) NOT NULL,
    display_name  VARCHAR(100) NOT NULL,
    password_hash VARBINARY(255) NOT NULL,
    is_deleted    BOOLEAN NOT NULL DEFAULT FALSE
);

INSERT INTO users (email, display_name, password_hash) VALUES
    ('ada@example.com', 'Ada', 0x01), ('grace@example.com', 'Grace', 0x02);
UPDATE users SET is_deleted = TRUE WHERE display_name = 'Grace';

CREATE VIEW visible_users AS
SELECT id, display_name
FROM users
WHERE is_deleted = FALSE;

SELECT * FROM visible_users;
```

```text
id	display_name
1	Ada
```

The view hides `email` and `password_hash` entirely and filters out soft-deleted rows. Queries against `visible_users` cannot reach what the view doesn't select.

### Enforce integrity in the schema

Constraints are a security layer too: they hold no matter which code path performs the write. `CHECK` constraints, `NOT NULL`, foreign keys, and `ENUM` types stop invalid states at the database boundary; see [Tables and data types](/docs/products/databases/mysql/concepts/tables#constraints). For multi-step changes that must not interleave with other writers, use transactions and row locks; see [Transactions](/docs/products/databases/mysql/concepts/transactions).

### Scope tenants in the application

MySQL has no row-level security policies, so multi-tenant isolation lives in your queries. Keep it reliable by centralizing it:

- Put a `tenant_id` column on every tenant-owned table, indexed and `NOT NULL`, with a foreign key to the tenants table.
- Route all data access through one layer (a repository module or ORM scope) that always applies `WHERE tenant_id = ?`, rather than trusting every call site to remember.
- For read paths, per-tenant views or parameterized views over `tenant_id` make the scoping visible and testable.

### Use Appwrite Auth as the tenant identity

Native MySQL has no built-in link to Appwrite's permission system. [Permissions](/docs/advanced/security/permissions) apply to Appwrite databases, not to the raw engine. You can still scope tenants to Appwrite users: your backend verifies the caller's Appwrite session as a [JWT](/docs/products/auth/jwt) and uses the verified user ID as the `tenant_id` in every query:

```server-nodejs
import { Client, Account } from 'node-appwrite';
import mysql from 'mysql2/promise';

const pool = mysql.createPool(process.env.DATABASE_URL);

export async function listDocuments(jwt) {
    // Verify the Appwrite session. Throws if the JWT is invalid or expired.
    const client = new Client()
        .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
        .setProject('<PROJECT_ID>')
        .setJWT(jwt);
    const user = await new Account(client).get();

    // The verified user ID scopes the query. Never accept a tenant ID from the request body.
    const [rows] = await pool.execute(
        'SELECT * FROM documents WHERE tenant_id = ?',
        [user.$id]
    );
    return rows;
}
```

The user ID comes from the verified session, never from request input, so a client cannot query another tenant by sending a different ID.

### Protect the connection itself

Since the `admin` credentials are the whole story, treat them accordingly:

- Rotate the password when a person or system that held it should lose access. See [Connections](/docs/products/databases/mysql/connections#rotate).
- Restrict which networks can reach the database with an IP allowlist. See [Network security](/docs/products/databases/mysql/network-security).
- All connections require TLS; never disable certificate verification in production drivers.
- Use parameterized queries everywhere. SQL injection against an account with `ALL PRIVILEGES` on the schema is a full compromise of your data.

---

## Data modeling and normalization

URL: https://appwrite.io/docs/products/databases/mysql/concepts/data-modeling

Data modeling decides where each fact lives. Normalization is the discipline of storing every fact exactly once, so it can't contradict itself. This page walks a flat spreadsheet-style table through the normal forms, then covers when to deliberately break the rules, and how views let you reshape data without duplicating it.

### The problem with one big table

Start with an orders table designed the way a spreadsheet would be:

```sql
CREATE TABLE orders_flat (
    order_id       BIGINT,
    customer_name  VARCHAR(100),
    customer_email VARCHAR(255),
    product_names  TEXT,      -- 'Keyboard, Mouse'
    product_prices TEXT       -- '79.00, 29.50'
);

INSERT INTO orders_flat VALUES
    (1, 'Ada', 'ada@example.com', 'Keyboard, Mouse', '79.00, 29.50'),
    (2, 'Ada', 'ada@example.com', 'Monitor', '349.99'),
    (3, 'Grace', 'grace@example.com', 'Mouse', '29.50');
```

Every design flaw here causes a concrete failure:

- Ada's email is stored twice. Update one row and not the other, and the database now disagrees with itself. This is an **update anomaly**.
- Products only exist inside order rows. Delete order 2 and the Monitor, its price, everything, vanishes. A **delete anomaly**.
- A product has no row of its own, so recording one before anyone orders it means a row with empty order fields. An **insert anomaly**.
- "Which orders contain a Mouse?" requires string parsing instead of a `WHERE` clause.

### First normal form: one value per cell

First normal form (1NF) requires each column to hold a single atomic value, no comma-separated lists. Split the line items into rows:

```sql
CREATE TABLE order_lines_1nf (
    order_id       BIGINT,
    customer_name  VARCHAR(100),
    customer_email VARCHAR(255),
    product_name   VARCHAR(200),
    product_price  DECIMAL(10, 2)
);

INSERT INTO order_lines_1nf VALUES
    (1, 'Ada', 'ada@example.com', 'Keyboard', 79.00),
    (1, 'Ada', 'ada@example.com', 'Mouse', 29.50),
    (2, 'Ada', 'ada@example.com', 'Monitor', 349.99),
    (3, 'Grace', 'grace@example.com', 'Mouse', 29.50);
```

Now `WHERE product_name = 'Mouse'` works. But the duplication got worse: Ada's email appears three times, and the Mouse's price twice.

### Second normal form: columns depend on the whole key

Second normal form (2NF) applies to tables whose key spans more than one column. Every non-key column must depend on the whole key, not on part of it. The key here is implicitly `(order_id, product_name)`, and `product_price` depends only on the product, not on which order it is in. The fix is to move the price into a products table, where the product alone is the key.

### Third normal form: no column depends on another non-key column

Third normal form (3NF) says no column may depend on a non-key column. `customer_email` depends on the customer, not the order, so it belongs in a customers table keyed by customer.

Applying both produces the standard shape, where every fact has exactly one home:

```sql
CREATE TABLE customers (
    id    BIGINT AUTO_INCREMENT PRIMARY KEY,
    name  VARCHAR(100) NOT NULL,
    email VARCHAR(255) NOT NULL UNIQUE
);

CREATE TABLE products (
    id    BIGINT AUTO_INCREMENT PRIMARY KEY,
    name  VARCHAR(200) NOT NULL,
    price DECIMAL(10, 2) NOT NULL
);

CREATE TABLE orders (
    id          BIGINT AUTO_INCREMENT PRIMARY KEY,
    customer_id BIGINT NOT NULL,
    created_at  TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (customer_id) REFERENCES customers (id)
);

CREATE TABLE order_items (
    order_id   BIGINT NOT NULL,
    product_id BIGINT NOT NULL,
    quantity   INT NOT NULL DEFAULT 1,
    unit_price DECIMAL(10, 2) NOT NULL,   -- price at time of order, see below
    PRIMARY KEY (order_id, product_id),
    FOREIGN KEY (order_id) REFERENCES orders (id),
    FOREIGN KEY (product_id) REFERENCES products (id)
);

INSERT INTO customers (name, email) VALUES
    ('Ada', 'ada@example.com'), ('Grace', 'grace@example.com');
INSERT INTO products (name, price) VALUES
    ('Keyboard', 79.00), ('Mouse', 29.50), ('Monitor', 349.99);
INSERT INTO orders (customer_id) VALUES (1), (1), (2);
INSERT INTO order_items (order_id, product_id, quantity, unit_price) VALUES
    (1, 1, 1, 79.00), (1, 2, 1, 29.50), (2, 3, 1, 349.99), (3, 2, 1, 29.50);
```

Changing Ada's email is now a one-row `UPDATE` that cannot leave a stale copy anywhere. The anomalies are gone because duplication is gone.

Notice `unit_price` in `order_items` looks like duplication but isn't: the price *at the time of the order* is a different fact from the product's *current* price, and both deserve a home. Normalization is about one home per fact, not zero copies of anything that looks similar.

#### Boyce-Codd normal form

Boyce-Codd normal form (BCNF) is a stricter 3NF: whenever one column determines another, the determining column must be a candidate key. The difference from 3NF only shows up in tables with overlapping composite candidate keys, for example a bookings table `(room, time_slot, teacher)` where each teacher always teaches in one room. That table is in 3NF, yet the teacher-to-room fact still duplicates per booking; BCNF moves it into its own table. Schemas like this are rare, and a schema in 3NF almost always satisfies BCNF too.

#### The normal forms at a glance

| Form | Rule | Duplication it removes |
| --- | --- | --- |
| 1NF | One value per cell, no repeating groups | Lists packed into a single cell |
| 2NF | Every column depends on the whole key | Facts about part of a composite key, copied into every row |
| 3NF | No column depends on a non-key column | Facts about another column, copied into every row |
| BCNF | Every determining column is a candidate key | Dependencies between overlapping composite keys that 3NF misses |

Higher forms (4NF, 5NF) deal with multi-valued and join dependencies and rarely change a practical schema; the forms above cover day-to-day design.

### Reading a normalized schema

Normalized data comes back together with joins:

```sql
SELECT o.id AS order_id, c.name, SUM(oi.quantity * oi.unit_price) AS total
FROM orders o
JOIN customers c ON c.id = o.customer_id
JOIN order_items oi ON oi.order_id = o.id
GROUP BY o.id, c.name
ORDER BY o.id;
```

```text
order_id	name	total
1	Ada	108.50
2	Ada	349.99
3	Grace	29.50
```

If the joins feel verbose, that's what views are for, not denormalization.

### Views: stored queries, not stored data

A view names a query so readers get the convenient shape without the data being duplicated:

```sql
CREATE VIEW order_summaries AS
SELECT o.id AS order_id, c.name AS customer, o.created_at,
       SUM(oi.quantity * oi.unit_price) AS total
FROM orders o
JOIN customers c ON c.id = o.customer_id
JOIN order_items oi ON oi.order_id = o.id
GROUP BY o.id, c.name, o.created_at;

SELECT customer, total FROM order_summaries WHERE order_id = 1;
```

The view runs its query on every read, so it is always current. MySQL has no materialized views. When an aggregation is too expensive to compute on every read, maintain a summary table yourself: recompute it on a schedule, or update it transactionally alongside the source data:

```sql
CREATE TABLE customer_totals (
    name           VARCHAR(100) PRIMARY KEY,
    lifetime_value DECIMAL(12, 2) NOT NULL
);

-- refresh on your schedule:
REPLACE INTO customer_totals
SELECT c.name, SUM(oi.quantity * oi.unit_price)
FROM customers c
JOIN orders o ON o.customer_id = c.id
JOIN order_items oi ON oi.order_id = o.id
GROUP BY c.name;

SELECT * FROM customer_totals ORDER BY lifetime_value DESC;
```

### When to denormalize

Denormalization deliberately duplicates a fact to make reads cheaper, accepting that your code must now keep the copies in sync. It's a real tool with a real cost, so it should be a measured response to a demonstrated problem, not a default:

- **A hot aggregate**, such as showing order totals on every page: store `orders.total` and update it when items change, rather than summing on every read.
- **An access-pattern mismatch**, such as a search page filtering on a joined column at scale.
- **Snapshot semantics**, like `unit_price` above, which isn't really denormalization: the historical value is its own fact.

Before duplicating data, check whether an index or a view solves the read problem, since neither can ever disagree with the source data; a summary table lags until its next refresh but needs no synchronization logic beyond the refresh itself. When you do denormalize, keep the copies consistent inside one transaction, so a crash between the write and the sync can't leave them disagreeing. See [Transactions](/docs/products/databases/mysql/concepts/transactions).

### JSON columns in a relational model

A `JSON` column is controlled denormalization for attributes that vary per row, such as per-event metadata. The rule of thumb: anything you filter on, join on, or aggregate regularly deserves a real column with a real type and real constraints; the long tail can live in `JSON`. See [Tables and data types](/docs/products/databases/mysql/concepts/tables#json).

---

## Indexes

URL: https://appwrite.io/docs/products/databases/mysql/concepts/indexes

An index is a sorted data structure the database maintains next to a table so it can find rows without scanning everything. Reads get faster; writes pay a small tax to keep each index current. Knowing when an index helps, and how to confirm it's being used, is the highest-leverage performance skill in SQL.

### Setup

Index behavior only shows up with enough data on the table, so the setup seeds 100,000 users to give the query optimizer a real choice to make:

```sql
CREATE TABLE users (
    id         BIGINT AUTO_INCREMENT PRIMARY KEY,
    email      VARCHAR(255) NOT NULL,
    country    CHAR(2) NOT NULL,
    created_at DATETIME NOT NULL
);

SET SESSION cte_max_recursion_depth = 100000;

INSERT INTO users (email, country, created_at)
WITH RECURSIVE seq (n) AS (
    SELECT 1 UNION ALL SELECT n + 1 FROM seq WHERE n < 100000
)
SELECT CONCAT('user', n, '@example.com'),
       ELT(1 + n % 4, 'DE', 'US', 'IN', 'BR'),
       NOW() - INTERVAL n MINUTE
FROM seq;

ANALYZE TABLE users;
```

`ANALYZE TABLE` refreshes the statistics the optimizer uses to estimate row counts. InnoDB updates them automatically in the background; running it manually after a bulk load just avoids waiting.

### How an index works

Table rows are stored in no order useful for searching an arbitrary column, so answering `WHERE email = '...'` without an index means reading every row and checking each one. The work grows with the table: ten times the rows, ten times the reads.

A **B-tree** (balanced tree) is the data structure indexes use to fix this. It keeps every value of the indexed column in **sorted order**, stored as a tree of small pages: the top page holds a few boundary values that direct the search into one of its child pages, each child narrows the range further, and the bottom layer, the leaves, holds the actual values with pointers back to their rows. Sorted order is what makes searching cheap:

- The database starts in the middle of the index, checks whether the target value sorts before or after that point, and discards the half that cannot contain it. Repeating this takes a handful of steps even on millions of rows.
- Equal values sit next to each other, so one descent lands on every matching entry at once.
- The same sorted order answers range conditions (`<`, `BETWEEN`) and returns rows already sorted for `ORDER BY`.

Indexes have a price: each one is a second copy of the column that must be kept correct, so every insert, update, and delete on the table also rewrites part of every index. They trade write work and disk space for read speed, which is why you add them for the queries you actually run rather than on every column.

### Reading EXPLAIN ANALYZE

Before adding indexes, you need to see what the database is currently doing. Prefix any query with `EXPLAIN ANALYZE` and instead of returning its result, the database runs it and reports the plan it used:

```sql
EXPLAIN ANALYZE SELECT * FROM users WHERE email = 'user4242@example.com';
```

```text
-> Filter: (users.email = 'user4242@example.com')  (cost=10092 rows=9988) (actual time=0.589..13.2 rows=1 loops=1)
    -> Table scan on users  (cost=10092 rows=99882) (actual time=0.0314..8.87 rows=100000 loops=1)
```

The tree reads bottom-up:

- **Table scan on users** is the chosen strategy: read the table start to finish. Its `actual ... rows=100000` shows all 100,000 rows were produced.
- **Filter** receives those rows and checks the condition against each one; its `rows=1` shows only one survived.
- **actual time=0.589..13.2** is when the first and last row were produced, in milliseconds. The `cost` and estimated `rows` numbers are the optimizer's internal estimates, not measurements.

Exact numbers will differ on your database; the plan shape is what matters, and this shape, an entire table read to return one row, is the signal that an index would help. Plain `EXPLAIN` without `ANALYZE` shows the plan without executing the query, which is safer for slow queries and writes.

### Create an index

`CREATE INDEX` builds the structure from the table's existing rows and keeps it maintained on every future write. Rerunning the earlier query shows the optimizer picking it up immediately:

```sql
CREATE INDEX idx_users_email ON users (email);

EXPLAIN ANALYZE SELECT * FROM users WHERE email = 'user4242@example.com';
```

```text
-> Index lookup on users using idx_users_email (email='user4242@example.com')  (cost=0.35 rows=1) (actual time=0.011..0.0116 rows=1 loops=1)
```

The plan flipped to an `Index lookup`, and execution time dropped from milliseconds to microseconds.

One B-tree index serves several kinds of conditions on its column:

- equality: `=` and `IN`
- ranges: `<`, `>`, `BETWEEN`
- sorting: `ORDER BY`
- prefix patterns like `LIKE 'user42%'`

Primary keys and `UNIQUE` constraints create indexes automatically, so don't add duplicates for those columns. In InnoDB the primary key is special: the table's rows are physically stored in primary-key order (the clustered index), and every secondary index entry carries the primary key to locate the row.

### Composite indexes and column order

An index on multiple columns is sorted by the first column, then the second within it, like a phone book sorted by last name, then first name. Column order decides which queries it serves:

```sql
CREATE INDEX idx_users_email_created ON users (email, created_at);
```

This index answers `WHERE email = ...`, and `WHERE email = ... AND created_at > ...`, but a filter on `created_at` alone can't use the sorted order because entries for every email are interleaved:

```sql
EXPLAIN FORMAT=TREE SELECT * FROM users WHERE created_at > NOW() - INTERVAL 1 DAY;
```

```text
-> Filter: (users.created_at > <cache>((now() - interval 1 day)))  (cost=3433 rows=33291)
    -> Table scan on users  (cost=3433 rows=99882)
```

Rule of thumb: put equality-filtered columns first and range-filtered columns last. If you also query `created_at` on its own, that's a separate index.

### Covering indexes

A normal index lookup is two steps: find the matching entries in the index, then fetch each matching row from the table to read the other columns. That second step is the expensive part, and it's unnecessary when the query only asks for columns the index already stores.

This query selects only `email`, and the index contains `email`, so the answer comes straight out of the index:

```sql
EXPLAIN ANALYZE SELECT email FROM users WHERE email = 'user4242@example.com';
```

```text
-> Covering index lookup on users using idx_users_email (email='user4242@example.com')  (cost=1.1 rows=1) (actual time=0.00846..0.01 rows=1 loops=1)
```

**Covering index lookup** in the plan confirms the table was read zero times; an index that satisfies a query this way is called a covering index. Because every secondary index in InnoDB already includes the primary key, a query selecting only `id` and indexed columns is covered for free.

### Prefix indexes

Long string columns make large indexes. A prefix index stores only the first n characters, trading a little selectivity for a much smaller structure:

```sql
CREATE INDEX idx_users_email_prefix ON users (email(12));
```

Pick a prefix length long enough to stay selective; check with `SELECT COUNT(DISTINCT LEFT(email, 12)) / COUNT(*) FROM users;`, aiming close to 1. Prefix indexes can't serve covering lookups or `ORDER BY`, since the index doesn't hold the full value.

### Functional indexes

Indexing an expression makes queries on that expression fast:

```sql
CREATE INDEX idx_users_email_lower ON users ((LOWER(email)));

EXPLAIN FORMAT=TREE SELECT * FROM users WHERE LOWER(email) = 'user4242@example.com';
```

```text
-> Index lookup on users using idx_users_email_lower (lower(email)='user4242@example.com')  (cost=0.35 rows=1)
```

The query's expression must match the indexed expression exactly. Note the double parentheses in the `CREATE INDEX` syntax; they mark the key part as an expression.

### Invisible indexes

An invisible index is maintained on writes but ignored by the optimizer. It's the safe way to test dropping an index: make it invisible, watch your query performance, then drop it for real or flip it back:

```sql
ALTER TABLE users ALTER INDEX idx_users_email_lower INVISIBLE;
-- observe workload ...
ALTER TABLE users ALTER INDEX idx_users_email_lower VISIBLE;
```

Creating a new index as `INVISIBLE` first also lets you verify plans with targeted queries before exposing it to the whole workload.

### Beyond B-tree

| Type | Use for |
| --- | --- |
| B-tree | Equality and ranges on scalar values (default, InnoDB) |
| `FULLTEXT` | Natural-language search over text columns |
| `SPATIAL` | Geometric data in spatial columns |
| Multi-valued | Entries inside JSON arrays, via `CAST(... AS ... ARRAY)` |

A `FULLTEXT` index enables `MATCH ... AGAINST` relevance search, which behaves very differently from `LIKE '%word%'` and deserves its own evaluation before you rely on it.

### The cost of indexes

Every index consumes disk and slows every `INSERT`, `UPDATE`, and `DELETE` on the table, because each write updates each index. Check what your indexes weigh:

```sql
ANALYZE TABLE users;   -- refresh stats so the sizes are current

SELECT ROUND(data_length / 1024 / 1024, 1)  AS table_mb,
       ROUND(index_length / 1024 / 1024, 1) AS indexes_mb
FROM information_schema.TABLES
WHERE table_schema = DATABASE() AND table_name = 'users';
```

Index the queries you actually run, verify each index earns its keep with `EXPLAIN`, and drop (or first hide) the ones that don't. To find slow queries worth indexing in the first place, see [Monitoring](/docs/products/databases/mysql/monitoring).

---

## Joins and relationships

URL: https://appwrite.io/docs/products/databases/mysql/concepts/joins

Relational databases keep each entity in its own table and connect them through **keys**. A join combines rows from two tables by matching values, usually a **foreign key** on one side against a **primary key** on the other. This page covers how to model the two relationship shapes you'll meet constantly, and the join types you'll use in practice.

### Setup

The examples below use two tables, customers and orders, where every order records which customer placed it. Create and seed them first:

```sql
CREATE TABLE customers (
    id   BIGINT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100) NOT NULL
);

CREATE TABLE orders (
    id          BIGINT AUTO_INCREMENT PRIMARY KEY,
    customer_id BIGINT NOT NULL,
    total       DECIMAL(10, 2) NOT NULL,
    FOREIGN KEY (customer_id) REFERENCES customers (id)
);

INSERT INTO customers (name) VALUES ('Ada'), ('Grace'), ('Linus');
INSERT INTO orders (customer_id, total) VALUES
    (1, 120.00), (1, 80.00), (2, 45.50);
```

Ada has two orders, Grace has one, and Linus has none.

### What a join actually does

A join takes two tables and produces a new, temporary table built from them in three steps:

1. Pair rows from one side with rows from the other.
2. Evaluate the **join condition** on each pair, here `o.customer_id = c.id`.
3. Keep the pairs where it's true; each one becomes a result row carrying the columns of both sides.

```text
customers               orders                        result of the join
id | name               id | customer_id | total     name  | total
 1 | Ada        ←──┬──   1 |           1 | 120.00    Ada   | 120.00
                   └──   2 |           1 |  80.00    Ada   |  80.00
 2 | Grace      ←─────   3 |           2 |  45.50    Grace |  45.50
 3 | Linus      (no order matches)
```

Two things fall out of this model:

- A row that matches several rows on the other side appears several times in the result: Ada shows up twice because two orders point at her.
- A row that matches nothing, like Linus, produces no pair at all, and what happens to such unmatched rows is exactly what distinguishes the join types below.

The database doesn't literally build every combination; indexes let it jump straight to matching rows, but the result is always as if it had.

### One-to-many relationships

One customer has many orders, so `orders` carries a `customer_id` column referencing `customers`. The `FOREIGN KEY` clause makes the database reject any `customer_id` that doesn't exist in `customers`:

```sql
INSERT INTO orders (customer_id, total) VALUES (99, 10.00);
```

```text
ERROR 1452 (23000): Cannot add or update a child row: a foreign key constraint fails
(`your_db`.`orders`, CONSTRAINT `orders_ibfk_1` FOREIGN KEY (`customer_id`) REFERENCES `customers` (`id`))
```

A foreign key also decides what happens when the parent row is deleted. Declare it with the behavior you want:

- The default blocks deleting a customer who still has orders.
- `ON DELETE CASCADE` deletes the customer's orders along with the customer.
- `ON DELETE SET NULL` keeps the orders but clears their `customer_id` (the column must be nullable).

### Inner joins

An inner join returns only rows that match on both sides:

```sql
SELECT c.name, o.total
FROM customers c
JOIN orders o ON o.customer_id = c.id
ORDER BY c.name, o.total;
```

```text
name	total
Ada	80.00
Ada	120.00
Grace	45.50
```

Linus disappears from the result because he has no matching order. `JOIN` and `INNER JOIN` are the same thing.

### Left joins

A left join keeps every row from the left table and fills the right side with `NULL` where nothing matches:

```sql
SELECT c.name, o.total
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id
ORDER BY c.name, o.total;
```

```text
name	total
Ada	80.00
Ada	120.00
Grace	45.50
Linus	NULL
```

This is the join for "all X, with their Y if any". Combined with aggregation it answers questions like order counts per customer, including zero:

```sql
SELECT c.name, COUNT(o.id) AS orders
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id
GROUP BY c.name
ORDER BY orders DESC;
```

Note `COUNT(o.id)`, not `COUNT(*)`: counting a column from the right table skips the `NULL` produced for Linus, yielding 0 instead of 1.

`RIGHT JOIN` mirrors `LEFT JOIN` in the other direction. In practice, most people reorder the tables and use `LEFT JOIN` for consistency.

### No full joins

MySQL does not support `FULL OUTER JOIN`. When you need unmatched rows from both sides, such as reconciling two datasets, combine a left and a right join with `UNION`:

```sql
CREATE TABLE imported_customers (id BIGINT, name VARCHAR(100));
INSERT INTO imported_customers VALUES (1, 'Ada'), (4, 'Margaret');

SELECT c.name AS in_database, i.name AS in_import
FROM customers c
LEFT JOIN imported_customers i ON i.id = c.id
UNION
SELECT c.name, i.name
FROM customers c
RIGHT JOIN imported_customers i ON i.id = c.id;
```

```text
in_database	in_import
Ada	Ada
Grace	NULL
Linus	NULL
NULL	Margaret
```

Note that `UNION` removes duplicate result rows. If your data can legitimately contain rows that project to identical values, use `UNION ALL` and restrict the second branch to right-side-only rows (`WHERE c.id IS NULL`) so nothing is collapsed.

**FULL parses as an alias**

`SELECT * FROM a FULL JOIN b ...` is not a syntax error in MySQL: `FULL` is parsed as a table alias named `FULL`, silently giving you an inner join. If you port a query from another database, this fails quietly rather than loudly.

### Cross joins

A cross join written without any join condition produces every combination of rows from both tables. (In MySQL the `CROSS JOIN` keyword also accepts an `ON` clause, which turns it into an ordinary inner join; it's the absence of a condition that makes it Cartesian.) Use it to generate combinations, such as every product in every size:

```sql
CREATE TABLE sizes (size CHAR(1));
INSERT INTO sizes VALUES ('S'), ('M'), ('L');

SELECT c.name, s.size
FROM customers c
CROSS JOIN sizes s
ORDER BY c.name, s.size;
```

Three customers times three sizes returns nine rows. Be careful with large tables: the result size is the product of both row counts.

### Anti-joins: rows without a match

"Customers with no orders" is an anti-join. Two equivalent spellings:

```sql
SELECT name FROM customers c
WHERE NOT EXISTS (
    SELECT 1 FROM orders o WHERE o.customer_id = c.id
);

SELECT c.name
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id
WHERE o.id IS NULL;
```

Both return only Linus. Prefer `NOT EXISTS` over `NOT IN (SELECT ...)`: if the subquery ever returns a `NULL`, `NOT IN` returns no rows at all, and nothing reports an error.

### Many-to-many relationships

When both sides can have many of the other, such as orders and products, neither table can hold the foreign key. A junction table holds one row per connection:

```sql
CREATE TABLE products (
    id   BIGINT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(200) NOT NULL
);

CREATE TABLE order_items (
    order_id   BIGINT NOT NULL,
    product_id BIGINT NOT NULL,
    quantity   INT NOT NULL DEFAULT 1,
    PRIMARY KEY (order_id, product_id),
    FOREIGN KEY (order_id) REFERENCES orders (id),
    FOREIGN KEY (product_id) REFERENCES products (id)
);

INSERT INTO products (name) VALUES ('Keyboard'), ('Mouse');
INSERT INTO order_items (order_id, product_id, quantity) VALUES
    (1, 1, 1), (1, 2, 2), (2, 2, 1);
```

The composite primary key `(order_id, product_id)` prevents duplicate connections, and the junction table is the natural home for relationship attributes like `quantity`. Traversing the relationship is two joins:

```sql
SELECT o.id AS order_id, p.name, oi.quantity
FROM orders o
JOIN order_items oi ON oi.order_id = o.id
JOIN products p ON p.id = oi.product_id
ORDER BY o.id, p.name;
```

```text
order_id	name	quantity
1	Keyboard	1
1	Mouse	2
2	Mouse	1
```

Each result row is one line item: the first join finds the connections belonging to each order, and the second swaps each `product_id` for the product's actual name. The Mouse appears under both orders because two junction rows point at it, one connection per row.

### Join or separate queries?

Fetching a list and then querying once per row (the N+1 pattern) multiplies round trips and usually loses badly to a single join, even a large one. Let the database combine the data; that is what it is optimized for. If a join's result set explodes because one row matches thousands, paginate or aggregate on the many side instead of splitting into per-row queries.

---

## Querying rows

URL: https://appwrite.io/docs/products/databases/mysql/concepts/queries

Four statements do almost all the work in a relational database:

- **SELECT** reads rows
- **INSERT** adds new rows
- **UPDATE** changes existing rows
- **DELETE** removes rows

SQL is declarative: a query describes the result you want, and the database's optimizer decides how to produce it, choosing between indexes, scans, and join strategies on its own. That's why the same query keeps working as data grows and indexes change.

This page walks through each statement, plus the querying patterns you reach for daily: filtering, aggregation, pagination, and upserts.

### Setup

The examples below work against a small product catalog. Create and seed it first:

```sql
CREATE TABLE products (
    id    BIGINT AUTO_INCREMENT PRIMARY KEY,
    name  VARCHAR(200) NOT NULL,
    price DECIMAL(10, 2) NOT NULL,
    stock INT NOT NULL DEFAULT 0
);

INSERT INTO products (name, price, stock) VALUES
    ('Keyboard', 79.00, 120),
    ('Mouse', 29.50, 200),
    ('Monitor', 349.99, 14),
    ('Webcam', 59.00, 0),
    ('Desk mat', 19.90, 45);
```

### Read rows

A `SELECT` statement has three jobs, each handled by its own clause: choose which columns to return, decide which rows qualify, and put them in order:

```sql
SELECT name, price
FROM products
WHERE stock > 0
ORDER BY price DESC;
```

```text
name	price
Monitor	349.99
Keyboard	79.00
Mouse	29.50
Desk mat	19.90
```

Four of the five products come back: the Webcam is filtered out because its stock is 0, and the rest arrive sorted by price, highest first. The other columns still exist on those rows; the query just didn't ask for them. Without an `ORDER BY`, row order is arbitrary and can change between runs, so always order results the user will see.

`WHERE` accepts any boolean expression, built from a handful of operators:

- **Comparisons** (`=`, `<`, `>`, `<=`, `>=`, `<>`) work on numbers, text, and dates alike.
- **BETWEEN** matches a range, bounds included.
- **LIKE** matches string patterns, where `%` stands for any sequence of characters; under the default `utf8mb4` collation it compares case-insensitively, so `'%mo%'` matches both Mouse and Monitor.
- **IN** matches any value in a list.
- **AND** and **OR** combine conditions, with parentheses to group them.

```sql
SELECT name FROM products WHERE price BETWEEN 20 AND 100;   -- range
SELECT name FROM products WHERE name LIKE '%mo%';           -- pattern match
SELECT name FROM products WHERE stock IN (0, 14);           -- membership
SELECT name FROM products WHERE stock = 0 OR price < 25;    -- combined conditions
```

Avoid `SELECT *` in application code. Naming columns keeps results stable when the table gains columns later and avoids transferring data you don't use.

### Insert rows

`INSERT` names the columns it provides and gives a value for each; omitted columns fall back to their defaults, like the auto-generated `id` here:

```sql
INSERT INTO products (name, price, stock)
VALUES ('USB hub', 24.00, 80);

SELECT LAST_INSERT_ID();
```

`LAST_INSERT_ID()` returns the `AUTO_INCREMENT` value generated by the most recent insert on this connection. It is connection-scoped, so concurrent clients never see each other's IDs. Most drivers surface it directly on the statement result, no extra query needed.

### Update rows

`UPDATE` changes the columns you name on every row matching the `WHERE` clause:

```sql
UPDATE products
SET price = price * 0.90
WHERE stock > 100;
```

The `SET` expression can reference the row's current values, as the discount above does. Without a `WHERE` clause, `UPDATE` rewrites the entire table, so check the target set first with a `SELECT` using the same `WHERE` when running one by hand.

### Delete rows

`DELETE` removes every row matching the `WHERE` clause:

```sql
DELETE FROM products
WHERE stock = 0;
```

Without a `WHERE` clause, `DELETE FROM products` removes every row in the table. For hand-run maintenance, wrap the statement in a transaction so you can inspect the result and roll it back. See [Transactions](/docs/products/databases/mysql/concepts/transactions).

### Aggregate

An aggregate function computes a single value over a set of rows, such as a *count of orders* or a *total spend per customer*:

- **COUNT(*)** counts rows
- **SUM** and **AVG** total and average a numeric column
- **MIN** and **MAX** find the extremes

The demonstration needs a table where several rows belong to each customer:

```sql
CREATE TABLE orders (
    id       BIGINT AUTO_INCREMENT PRIMARY KEY,
    customer VARCHAR(100) NOT NULL,
    total    DECIMAL(10, 2) NOT NULL
);

INSERT INTO orders (customer, total) VALUES
    ('ada', 120.00), ('ada', 80.00), ('grace', 45.50),
    ('grace', 30.00), ('grace', 25.00), ('linus', 999.99);
```

By default an aggregate covers every row in the table: `SELECT SUM(total) FROM orders` returns one number, the total across all six orders. `GROUP BY customer` changes that. The database first gathers rows with the same customer value together, Ada's two orders, Grace's three, Linus's one, then runs the aggregate functions once for each of those groups. The result has one row per customer instead of one row for the whole table:

```sql
SELECT customer,
       COUNT(*)   AS orders,
       SUM(total) AS lifetime_value
FROM orders
GROUP BY customer
HAVING SUM(total) > 100
ORDER BY lifetime_value DESC;
```

```text
customer	orders	lifetime_value
linus	1	999.99
ada	2	200.00
grace	3	100.50
```

Six order rows became three result rows, one per customer:

- Ada's two orders were summed into 200.00
- Grace's three orders were summed into 100.50
- Linus's single order stands alone at 999.99

In a grouped query, every selected column must be either grouped on (like `customer`) or aggregated (like `SUM(total)`), because each result row now represents many source rows.

`WHERE` and `HAVING` both filter, but at different stages:

- **WHERE** filters individual rows, before grouping. It cannot use aggregate results, because they haven't been computed yet.
- **HAVING** filters the groups, after the aggregates are computed.

Here `HAVING SUM(total) > 100` keeps only customers whose orders total more than 100. Grace passes at 100.50; a threshold of 150 would exclude her.

### Paginate

Pagination splits a large result into pages. There are two ways to do it:

- **Offset pagination** asks for "skip the first 20 rows, return the next 10". Simple, but the database still reads and discards every skipped row, so deep pages get slower and slower.
- **Keyset pagination** asks for "return the 10 rows after the last one I saw". The database jumps straight to that position, so every page costs the same as the first.

`LIMIT` and `OFFSET` are the offset approach:

```sql
SELECT id, name FROM products ORDER BY id LIMIT 2 OFFSET 2;
```

Keyset pagination filters on the last value seen instead:

```sql
SELECT id, name
FROM products
WHERE id > 2          -- last id from the previous page
ORDER BY id
LIMIT 2;
```

Keyset pagination stays fast at any depth because the index seeks straight to the boundary, provided the ordering columns are indexed. It requires a deterministic, unique ordering (a single column like `id`, or a composite) and can't jump to an arbitrary page number.

### Upsert

An upsert writes a row without knowing whether it already exists: insert it if it's new, update it if it's not. Stock counts, settings, and sync jobs all need this.

The obvious approach, `SELECT` to check and then `INSERT` or `UPDATE` accordingly, has a race condition: another connection can insert the same key between your check and your write, and your insert fails.

`INSERT ... ON DUPLICATE KEY UPDATE` performs the check and the write as one atomic statement, so the race cannot happen:

```sql
CREATE TABLE inventory (
    sku      VARCHAR(20) PRIMARY KEY,
    quantity INT NOT NULL
);

INSERT INTO inventory (sku, quantity) VALUES ('KB-01', 10);

-- Second write for the same key updates instead of failing
INSERT INTO inventory (sku, quantity) VALUES ('KB-01', 5) AS new
ON DUPLICATE KEY UPDATE quantity = inventory.quantity + new.quantity;

SELECT * FROM inventory;
```

```text
sku	quantity
KB-01	15
```

What happened:

- The first insert creates KB-01 with quantity 10.
- The second insert collides with it on the primary key, so instead of failing, it runs the `ON DUPLICATE KEY UPDATE` clause: existing 10 plus incoming 5 gives 15.
- The `AS new` alias names the row that would have been inserted, which is how the update reaches the incoming values.

To silently ignore duplicates instead, use `INSERT IGNORE`, but note it downgrades other errors to warnings too, so prefer `ON DUPLICATE KEY UPDATE` when in doubt.

### Subqueries and CTEs

A common table expression (CTE) names an intermediate result so a complex query reads top to bottom:

```sql
WITH big_spenders AS (
    SELECT customer
    FROM orders
    GROUP BY customer
    HAVING SUM(total) > 150
)
SELECT customer FROM big_spenders ORDER BY customer;
```

CTEs and subqueries are interchangeable in most positions; prefer whichever keeps the query readable. MySQL also supports `WITH RECURSIVE` for hierarchical data such as category trees.

---

## Tables and data types

URL: https://appwrite.io/docs/products/databases/mysql/concepts/tables

A table is the unit of storage in a relational database, a named grid where:

- each **row** is one record: one customer, one order
- each **column** is one attribute every record shares: name, price, creation time
- each column has a **type** that determines what values it accepts and how they compare, sort, and calculate

Unlike a spreadsheet, the set of columns is declared up front and enforced; every row has exactly those columns. This fixed shape is what lets the database enforce correctness: precise types and constraints reject bad data before it ever reaches your application.

The examples on this page build up pieces of a small store schema: customers, products, and orders.

**Standard MySQL types**

Column types are standard MySQL types, not Appwrite-specific ones. This page covers the types most schemas need; the full list is in the [MySQL data types documentation](https://dev.mysql.com/doc/refman/8.4/en/data-types.html).

### Create a table

`CREATE TABLE` names the table and declares each column with its type and the rules it must obey:

```sql
CREATE TABLE customers (
    id         BIGINT AUTO_INCREMENT PRIMARY KEY,
    name       VARCHAR(200) NOT NULL,
    email      VARCHAR(255) NOT NULL UNIQUE,
    created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
```

This declaration packs in the most common column features:

- `AUTO_INCREMENT` makes the database assign an ascending value. `SERIAL` is shorthand for `BIGINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE`.
- `PRIMARY KEY` uniquely identifies each row; in InnoDB it also physically orders the table (the clustered index).
- `NOT NULL` rejects missing values, and `UNIQUE` rejects duplicates.
- `DEFAULT CURRENT_TIMESTAMP` fills the column when an insert doesn't provide a value.

Tables default to the InnoDB storage engine and `utf8mb4` character set, which is what you want: full transaction support and full Unicode, including emoji.

### Numeric types

| Type | Range (signed) | Use for |
| --- | --- | --- |
| `TINYINT` | −128 to 127 | Flags, small codes |
| `SMALLINT` | −32,768 to 32,767 | Small counters |
| `MEDIUMINT` | −8,388,608 to 8,388,607 | Mid-range counters |
| `INT` | about ±2.1 billion | General whole numbers |
| `BIGINT` | about ±9.2 quintillion | IDs, anything that may grow |
| `DECIMAL(p, s)` | Exact, up to 65 digits | Money, quantities that must not round |
| `FLOAT` / `DOUBLE` | Approximate floating point | Measurements, scientific data |

Each integer type also has an `UNSIGNED` variant that trades negative range for double the positive range.

The critical distinction is exact versus approximate:

- **DECIMAL** stores decimal values exactly: 0.1 + 0.2 is 0.3.
- **DOUBLE** stores binary approximations: the same sum comes back as 0.30000000000000004.

Always use `DECIMAL` for money. A price column like `DECIMAL(10, 2)` holds up to 8 digits before the decimal point and exactly 2 after.

### String types

| Type | Holds | Use for |
| --- | --- | --- |
| `VARCHAR(n)` | Up to n characters | Names, emails, most strings |
| `TEXT` | Up to 64 KB | Long free-form text |
| `MEDIUMTEXT` / `LONGTEXT` | Up to 16 MB / 4 GB | Documents, logs |
| `CHAR(n)` | Exactly n characters, space-padded in storage (trailing spaces stripped on read) | Fixed-length codes only |
| `ENUM(...)` | One value from a fixed list | Status columns |
| `BINARY` / `VARBINARY` / `BLOB` | Raw bytes | Hashes, encrypted data |

`VARCHAR` needs an explicit maximum length, and unlike some databases the limit is enforced, so pick one with headroom:

```sql
CREATE TABLE products (
    id    BIGINT AUTO_INCREMENT PRIMARY KEY,
    name  VARCHAR(200) NOT NULL,
    sku   VARCHAR(20) NOT NULL UNIQUE,
    price DECIMAL(10, 2) NOT NULL CHECK (price >= 0)
);
```

An `ENUM` column stores one value from a list declared in the schema and rejects everything else:

```sql
CREATE TABLE tickets (
    id     BIGINT AUTO_INCREMENT PRIMARY KEY,
    status ENUM('open', 'in_progress', 'closed') NOT NULL DEFAULT 'open'
);
```

Changing the allowed values later requires an `ALTER TABLE`. If the list changes often, a lookup table with a foreign key is more flexible.

By default, string comparison in `utf8mb4` is case-insensitive and accent-insensitive (`utf8mb4_0900_ai_ci` collation), so `WHERE email = 'ADA@EXAMPLE.COM'` matches `ada@example.com`. Use a `_bin` or `_as_cs` collation on columns where case must matter.

### Date and time types

| Type | Stores | Use for |
| --- | --- | --- |
| `TIMESTAMP` | Point in time, stored as UTC | Created/updated timestamps, events |
| `DATETIME` | Wall-clock time, no zone conversion | Scheduled local times |
| `DATE` | Calendar date | Birthdays, due dates |
| `TIME` | Time of day or duration | Opening hours |
| `YEAR` | Year | Rarely needed |

`TIMESTAMP` converts input to UTC on write and back to the session time zone on read, so clients in different time zones agree on the moment an event happened. Its range ends in January 2038; use `DATETIME`, which stores the literal wall-clock value with no conversion, for dates beyond that.

Both accept fractional seconds up to microseconds with an explicit precision, such as `DATETIME(6)`. A common pair of bookkeeping columns:

```sql
CREATE TABLE notes (
    id         BIGINT AUTO_INCREMENT PRIMARY KEY,
    body       TEXT,
    created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
```

`ON UPDATE CURRENT_TIMESTAMP` refreshes the column automatically whenever an update actually changes the row's data; an update that writes identical values leaves it untouched.

### Boolean, UUID, and binary

Flags, identifiers, and raw bytes each have an idiomatic home:

- **BOOLEAN** is an alias for `TINYINT(1)`, with `TRUE` and `FALSE` as literals for 1 and 0; there is no separate boolean storage type.
- **UUIDs** have no dedicated column type: generate them with `UUID()` and store them either readably in `CHAR(36)` or compactly in `BINARY(16)`, converting with `UUID_TO_BIN` and `BIN_TO_UUID`.
- **VARBINARY** and the `BLOB` family hold raw bytes, such as hashes or encrypted data.

```sql
CREATE TABLE api_tokens (
    token_id BINARY(16) PRIMARY KEY DEFAULT (UUID_TO_BIN(UUID())),
    active   BOOLEAN NOT NULL DEFAULT TRUE,
    secret   VARBINARY(255) NOT NULL
);

INSERT INTO api_tokens (secret) VALUES (0xDEADBEEF);

SELECT BIN_TO_UUID(token_id) AS token_id, active FROM api_tokens;
```

### JSON columns

Use `JSON` for schemaless data attached to structured rows. Values are validated and stored in a binary format that supports path extraction:

```sql
CREATE TABLE events (
    id      BIGINT AUTO_INCREMENT PRIMARY KEY,
    kind    VARCHAR(50) NOT NULL,
    payload JSON NOT NULL
);

INSERT INTO events (kind, payload)
VALUES ('signup', '{"plan": "pro", "referrer": "newsletter"}');

SELECT payload->>'$.plan' AS plan
FROM events
WHERE payload->>'$.referrer' = 'newsletter';
```

`->>` extracts a value at a JSON path as unquoted text. Reach for `JSON` when attributes vary per row; keep anything you filter or join on regularly as a real column. MySQL has no array column type, so a JSON array is also the idiomatic home for small value lists, or model them as a separate table; see [Data modeling](/docs/products/databases/mysql/concepts/data-modeling).

### Constraints

Constraints are rules the database enforces on every write, no matter which application or migration performs it:

| Constraint | Enforces |
| --- | --- |
| `PRIMARY KEY` | Unique, non-null row identifier |
| `UNIQUE` | No duplicate values in a column or column group (multiple `NULL`s are allowed) |
| `NOT NULL` | Value must be present |
| `CHECK` | Boolean expression per row (deterministic functions only) |
| `FOREIGN KEY` | Value must exist in the referenced table |

```sql
CREATE TABLE orders (
    id          BIGINT AUTO_INCREMENT PRIMARY KEY,
    customer_id BIGINT NOT NULL,
    status      ENUM('pending', 'paid', 'shipped', 'cancelled') NOT NULL DEFAULT 'pending',
    total       DECIMAL(10, 2) NOT NULL CHECK (total >= 0),
    FOREIGN KEY (customer_id) REFERENCES customers (id)
);
```

A foreign key also controls what happens when the referenced row disappears: `ON DELETE CASCADE` removes dependent rows, `ON DELETE SET NULL` orphans them explicitly, and the default blocks the delete. Foreign keys are covered in depth in [Joins and relationships](/docs/products/databases/mysql/concepts/joins).

### Generated columns

A generated column is one the database computes for you from the row's other columns, instead of accepting a value from the application. You declare the formula once in the schema; every write runs it automatically. This removes a whole class of bugs where a derived value, like a line total, is computed in application code and drifts out of sync with the columns it came from. Two variants exist: `VIRTUAL` computes the value on every read and stores nothing, while `STORED` computes on write and saves the result on disk like a normal column.

```sql
CREATE TABLE line_items (
    quantity   INT NOT NULL,
    unit_price DECIMAL(10, 2) NOT NULL,
    line_total DECIMAL(12, 2) GENERATED ALWAYS AS (quantity * unit_price) STORED
);

INSERT INTO line_items (quantity, unit_price) VALUES (3, 19.99);

SELECT line_total FROM line_items;
```

```text
line_total
59.97
```

The insert supplies only `quantity` and `unit_price`; the database fills in `line_total` as 3 × 19.99. Writing to the column directly is an error, and if a later `UPDATE` changes the quantity, the total is recomputed in the same statement, so it can never disagree with its inputs.

### Change a table

`ALTER TABLE` evolves a schema in place:

```sql
ALTER TABLE customers ADD COLUMN phone VARCHAR(30);
ALTER TABLE customers MODIFY COLUMN phone VARCHAR(30) NOT NULL;
ALTER TABLE customers RENAME COLUMN phone TO phone_number;
ALTER TABLE customers DROP COLUMN phone_number;
```

MySQL 8 performs many alterations, including adding a column, as instant metadata changes. Others, such as changing a column's type, rebuild the table, so on large tables schedule those during a quiet period.

**Full type catalog**

MySQL also ships spatial types (`GEOMETRY`, `POINT`, `LINESTRING`, `POLYGON`, and their `MULTI*` and `GEOMETRYCOLLECTION` variants), `SET` for multi-valued flags, and `BIT` for bit fields. Query `information_schema.COLUMNS` to see the exact type of every column in your schema.

---

## Transactions

URL: https://appwrite.io/docs/products/databases/mysql/concepts/transactions

A transaction groups statements into a single all-or-nothing unit. Either every statement takes effect, or none do, and no other connection ever sees a half-finished state.

### How a transaction works

Think of a transaction as a private draft of the database that only your connection can see:

- `START TRANSACTION` opens the draft. From here, your changes apply to the draft, not the shared database.
- Reads inside the transaction see the draft, so your own changes look real to you. Everyone else still sees the database as it was.
- `COMMIT` publishes the whole draft at once. Other connections go from seeing none of your changes to seeing all of them; there is no in-between state.
- `ROLLBACK` throws the draft away, and the database is as if the transaction never started.
- If your connection drops mid-transaction, the database rolls the draft back automatically.

Everything else on this page, isolation levels, locks, deadlocks, is about what happens when several connections work on their drafts at the same time.

### What goes wrong without transactions

Two distinct things go wrong without them:

- **Partial failure**: a crash or dropped connection after the debit but before the credit leaves data in a state that was never supposed to exist.
- **Interleaving**: two concurrent processes both read a balance of 100, both compute a new value, both write, and one update silently vanishes.

Transactions address both: statements apply atomically, and the database isolates transactions from each other's unfinished work. InnoDB, the default storage engine, provides full transaction support.

### Setup

The examples below move money between two accounts, an operation where a half-applied change must be impossible. Create and seed the table first:

```sql
CREATE TABLE accounts (
    id      BIGINT AUTO_INCREMENT PRIMARY KEY,
    owner   VARCHAR(100) NOT NULL,
    balance DECIMAL(10, 2) NOT NULL CHECK (balance >= 0)
);

INSERT INTO accounts (owner, balance) VALUES ('ada', 100.00), ('grace', 50.00);
```

### Commit and roll back

By default MySQL runs in autocommit mode: every statement commits on its own. `START TRANSACTION` suspends that so several statements commit together:

```sql
START TRANSACTION;
UPDATE accounts SET balance = balance - 30 WHERE owner = 'ada';
UPDATE accounts SET balance = balance + 30 WHERE owner = 'grace';
COMMIT;
```

If the connection drops between the two updates, the whole transaction rolls back and the money never left Ada's account. You can also abandon a transaction deliberately:

```sql
START TRANSACTION;
DELETE FROM accounts;   -- oops
SELECT COUNT(*) FROM accounts;   -- returns 0 inside this transaction
ROLLBACK;
SELECT COUNT(*) FROM accounts;   -- returns 2, nothing happened
```

Inside the transaction the delete looks real, but only to that connection. `ROLLBACK` discards it. This makes `START TRANSACTION` an effective safety net for hand-run maintenance SQL.

The ACID properties describe what transactions guarantee: **atomicity** (all or nothing), **consistency** (constraints hold before and after), **isolation** (concurrent transactions don't see each other's partial work), and **durability** (committed data survives a crash).

**DDL commits implicitly**

Statements like `CREATE TABLE`, `ALTER TABLE`, and `DROP TABLE` commit the current transaction immediately. Don't mix schema changes into a transaction and expect them to roll back.

### A failed statement does not abort the transaction

Unlike some databases, an error rolls back only the statement that failed. The transaction stays open and earlier work stays intact:

```sql
START TRANSACTION;
UPDATE accounts SET balance = balance + 5 WHERE owner = 'grace';
UPDATE accounts SET balance = balance - 200 WHERE owner = 'ada';   -- fails
```

```text
ERROR 3819 (HY000): Check constraint 'accounts_chk_1' is violated.
```

Despite the error, the transaction is still open and healthy, and committing keeps the update that succeeded:

```sql
COMMIT;   -- commits the first update; the failed one never happened
```

This is convenient, but it means your application must check errors per statement: blindly running a script and committing at the end can commit a half-applied change. When any statement in a unit fails and the unit must be all-or-nothing, issue `ROLLBACK` yourself.

### Savepoints

A savepoint marks a spot you can roll back to without abandoning the whole transaction:

```sql
START TRANSACTION;
UPDATE accounts SET balance = balance - 10 WHERE owner = 'ada';

SAVEPOINT before_bonus;
UPDATE accounts SET balance = balance - 200 WHERE owner = 'ada';  -- fails
ROLLBACK TO SAVEPOINT before_bonus;

UPDATE accounts SET balance = balance + 10 WHERE owner = 'grace';
COMMIT;
```

Everything before the savepoint and after the rollback commits. Drivers and ORMs use savepoints to implement nested transactions.

### Isolation levels

Isolation levels trade strictness for concurrency. They answer one question: what may this transaction see of other transactions' concurrent work?

| Level | Prevents | Notes |
| --- | --- | --- |
| `READ UNCOMMITTED` | Nothing | Can read uncommitted (dirty) data; avoid |
| `READ COMMITTED` | Dirty reads | Each statement sees the latest committed data |
| `REPEATABLE READ` | + non-repeatable reads | Default. The whole transaction reads one consistent snapshot |
| `SERIALIZABLE` | + all anomalies | Reads take shared locks; conflicts can deadlock or wait |

MySQL's default is `REPEATABLE READ`, stricter than many other databases' default. Set the level for the next transaction:

```sql
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
START TRANSACTION;
SELECT SUM(balance) FROM accounts;
COMMIT;
```

Under `REPEATABLE READ`, InnoDB serves the whole transaction from a snapshot taken at its first read, so two reads of the same data always agree even while other transactions commit changes. Plain reads never block writers; InnoDB's multi-version concurrency control keeps old row versions around for readers.

### Row locking

Writers automatically lock the rows they modify until commit; a second transaction updating the same row waits. When you read a value in order to update it, that implicit protection isn't enough, because two transactions can read the same balance concurrently and both write results based on stale data. `SELECT ... FOR UPDATE` locks rows on read:

```sql
START TRANSACTION;
SELECT balance FROM accounts WHERE owner = 'ada' FOR UPDATE;
-- other transactions now wait to read-for-update or modify this row
UPDATE accounts SET balance = balance - 30 WHERE owner = 'ada';
COMMIT;
```

Locks follow the rows InnoDB *scans*, not just the rows that match: an `UPDATE` whose `WHERE` clause has no usable index scans the whole table and locks every row in it until commit, serializing all concurrent writers. Indexing the columns your write paths filter on is a concurrency fix, not only a speed fix. See [Indexes](/docs/products/databases/mysql/concepts/indexes).

For queue-like workloads where workers grab rows, add `SKIP LOCKED` so each worker takes the next unclaimed row instead of waiting. The claim only holds while the transaction is open, so the grab and the work's final update must share one transaction:

```sql
START TRANSACTION;
SELECT id FROM accounts ORDER BY id LIMIT 1 FOR UPDATE SKIP LOCKED;
-- process the claimed row, then mark it done in the same transaction
COMMIT;
```

A concurrent worker running the same statement while the first transaction is open skips the claimed row and returns the next one.

### Deadlocks

A deadlock occurs when two transactions each hold a lock the other needs. Session A locks Ada's row and wants Grace's; session B holds Grace's and wants Ada's. Neither can proceed, so InnoDB detects the cycle immediately and kills one:

```text
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
```

The aborted transaction rolls back; the survivor continues. Two habits prevent most deadlocks:

- Lock rows in a consistent order across your codebase, for example always by ascending `id`.
- Keep transactions short. Don't hold one open across network calls or user input.

The error message says it directly: the application should retry the aborted transaction. `SHOW ENGINE INNODB STATUS` includes a `LATEST DETECTED DEADLOCK` section with both queries when you need to diagnose one.

### Transactions and pooling

Transaction state lives on the connection, which matters when connecting through a pooler in transaction mode: session-level settings such as `SET SESSION TRANSACTION ISOLATION LEVEL` won't stick between statements, and every multi-statement unit must be wrapped in an explicit `START TRANSACTION`/`COMMIT` so it lands on one server connection. See [Connection pooling](/docs/products/databases/mysql/connection-pooling) for how this applies to your Appwrite database.

---

## Connection pooling

URL: https://appwrite.io/docs/products/databases/mysql/connection-pooling

MySQL creates one backend process per connection, which makes each connection relatively expensive. Serverless functions, edge runtimes, and horizontally scaled application servers can easily exhaust the connection limit of your specification. The connection pooler sits in front of your database and multiplexes many client connections onto a small pool of server connections.

The pooler runs next to your database and is reachable on port `6033` on the same hostname. Your application connects to the pooler exactly like it would connect to MySQL directly, same credentials, same TLS. The pooler is available on every specification.

### Pool modes

| Mode | Behavior | Use for |
|---------------|-----------------------------------------------------------------------------|------------------------------------------------|
| `transaction` | A server connection is assigned for the duration of a transaction, then returned to the pool | Serverless and most applications (default) |
| `session` | A server connection is held for the entire client session | Session-level features: server-side prepared statements, user variables, temporary tables |

Transaction mode gives the highest connection multiplexing but does not support session-level state. If your framework relies on session state such as server-side prepared statements or temporary tables, use session mode.

### Configure the pooler

Read the current pooler configuration with `getPooler`, and tune the pool mode and sizes with `updatePooler`. All parameters are optional; omitted values keep their current setting.

```server-nodejs
import { Client, Mysql } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const mysql = new Mysql(client);

const pooler = await mysql.updatePooler({
    databaseId: '<DATABASE_ID>',
    mode: 'transaction',
    maxConnections: 500,
    defaultPoolSize: 25,
});
```
```server-deno
import { Client, Mysql } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const mysql = new Mysql(client);

const pooler = await mysql.updatePooler({
    databaseId: '<DATABASE_ID>',
    mode: 'transaction',
    maxConnections: 500,
    defaultPoolSize: 25,
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Mysql;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$mysql = new Mysql($client);

$pooler = $mysql->updatePooler(
    databaseId: '<DATABASE_ID>',
    mode: 'transaction',
    maxConnections: 500,
    defaultPoolSize: 25,
);
```
```server-python
from appwrite.client import Client
from appwrite.services.mysql import Mysql

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

mysql = Mysql(client)

pooler = mysql.update_pooler(
    database_id='<DATABASE_ID>',
    mode='transaction',
    max_connections=500,
    default_pool_size=25,
)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

mysql = Mysql.new(client)

pooler = mysql.update_pooler(
    database_id: '<DATABASE_ID>',
    mode: 'transaction',
    max_connections: 500,
    default_pool_size: 25,
)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Mysql mysql = new Mysql(client);

var pooler = await mysql.UpdatePooler(
    databaseId: "<DATABASE_ID>",
    mode: "transaction",
    maxConnections: 500,
    defaultPoolSize: 25
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Mysql mysql = Mysql(client);

final pooler = await mysql.updatePooler(
    databaseId: '<DATABASE_ID>',
    mode: 'transaction',
    maxConnections: 500,
    defaultPoolSize: 25,
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Mysql

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val mysql = Mysql(client)

val pooler = mysql.updatePooler(
    databaseId = "<DATABASE_ID>",
    mode = "transaction",
    maxConnections = 500,
    defaultPoolSize = 25,
)
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let mysql = Mysql(client)

let pooler = try await mysql.updatePooler(
    databaseId: "<DATABASE_ID>",
    mode: "transaction",
    maxConnections: 500,
    defaultPoolSize: 25
)
```
```server-go
package main

import (
    "github.com/appwrite/sdk-for-go/appwrite"
    "github.com/appwrite/sdk-for-go/mysql"
)

func main() {
    client := appwrite.NewClient(
        appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"),
        appwrite.WithProject("<PROJECT_ID>"),
        appwrite.WithKey("<YOUR_API_KEY>"),
    )

    mysqlService := appwrite.NewMysql(client)

    result, err := mysqlService.UpdatePooler(
        "<DATABASE_ID>",
        mysql.WithUpdatePoolerMode("transaction"),
        mysql.WithUpdatePoolerMaxConnections(500),
        mysql.WithUpdatePoolerDefaultPoolSize(25),
    )
    if err != nil {
        panic(err)
    }
    _ = result
}
```
```server-rust
use appwrite::client::Client;
use appwrite::services::Mysql;

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

    let mysql = Mysql::new(&client);

    let pooler = mysql.update_pooler("<DATABASE_ID>", Some("transaction"), Some(500), Some(25), None, None, None, None, None).await?;

    Ok(())
}
```
```bash
curl -X PATCH \
  -H "X-Appwrite-Project: <PROJECT_ID>" \
  -H "X-Appwrite-Key: <YOUR_API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{
      "mode": "transaction",
      "maxConnections": 500,
      "defaultPoolSize": 25
  }' \
  https://<REGION>.cloud.appwrite.io/v1/mysql/<DATABASE_ID>/pooler
```

| Parameter | Range | Description |
|----------------------|----------------|--------------------------------------------------------------------------------|
| `mode` | `transaction`, `session` | How long a server connection stays assigned to a client |
| `maxConnections` | 10 - 10,000 | Maximum pooled client connections. Cannot exceed the connection cap of your specification |
| `defaultPoolSize` | 1 - 1,000 | Server connections per user in the pool |
| `readWriteSplitting` | boolean | Route `SELECT`s to replicas, writes and locked reads to the primary. Defaults to on when high availability is enabled |

### Connect through the pooler

Take your normal connection string and change the port to `6033`:

```bash
mysql://admin.<hash>:<password>@db-<hash>.<region>.appwrite.center:6033/default?ssl=true
```

Point your application's runtime traffic at the pooler port. Keep migrations and long-lived administrative sessions on the direct port `3306`, schema changes and tools like `mysqldump` expect session semantics and can misbehave in transaction mode.

### Read/write splitting

When [high availability](/docs/products/databases/mysql/high-availability) is enabled, the pooler can route read-only statements to replicas and everything else to the primary. `SELECT ... FOR UPDATE` and statements inside explicit transactions go to the primary. Replicas replicate asynchronously by default, so a read that immediately follows a write can be stale; use `sync` or `quorum` replication mode if you need read-your-writes consistency through the pooler.

### Sizing guidance

A useful starting point for `defaultPoolSize` is `4 x CPU cores` of your specification, and it rarely helps to go above your specification's connection cap divided by the number of databases sharing the workload. Watch the connection metrics and increase the pool only when clients queue for a connection.

### When not to use the pooler

The pooler adds a network hop, and transaction mode trades session-level features for connection multiplexing. Connect to the direct port `3306` instead when any of the following applies:

- **A small, fixed fleet.** A few long-lived application servers that each maintain a driver-level pool, with a combined connection count that fits in your specification's limit, gain nothing from an extra hop.
- **Session state.** Workloads that rely on server-side prepared statements, user variables, or temporary tables break in transaction mode. Use session mode or the direct port.
- **Migrations and administration.** Schema changes and tools like `mysqldump` expect one session for the whole run. Always run them against the direct port.
- **Single latency-sensitive queries.** A workload of few, fast queries on an idle database pays the extra hop on every round trip without ever hitting the connection limit the pooler protects against.

---

## Connections

URL: https://appwrite.io/docs/products/databases/mysql/connections

A native MySQL database exposes a MySQL endpoint over TLS. You connect to it the same way you would connect to any MySQL server: with the `mysql` client, any driver in any language, or any ORM.

### Get connection details with the API

The connection details are returned on the database object itself. Fetch the database with an API key that has the `databases.read` scope:

```server-nodejs
import { Client, Mysql } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const mysql = new Mysql(client);

const database = await mysql.get({
    databaseId: '<DATABASE_ID>',
});

console.log(database.connectionString);
```
```server-deno
import { Client, Mysql } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const mysql = new Mysql(client);

const database = await mysql.get({
    databaseId: '<DATABASE_ID>',
});

console.log(database.connectionString);
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Mysql;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$mysql = new Mysql($client);

$database = $mysql->get(databaseId: '<DATABASE_ID>');

echo $database['connectionString'];
```
```server-python
from appwrite.client import Client
from appwrite.services.mysql import Mysql

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

mysql = Mysql(client)

database = mysql.get(database_id='<DATABASE_ID>')

print(database.connection_string)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

mysql = Mysql.new(client)

database = mysql.get(database_id: '<DATABASE_ID>')

puts database.connection_string
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Mysql mysql = new Mysql(client);

var database = await mysql.Get(databaseId: "<DATABASE_ID>");

Console.WriteLine(database.ConnectionString);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Mysql mysql = Mysql(client);

final database = await mysql.get(
    databaseId: '<DATABASE_ID>',
);

print(database.connectionString);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Mysql

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val mysql = Mysql(client)

val database = mysql.get(
    databaseId = "<DATABASE_ID>",
)

println(database.connectionString)
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let mysql = Mysql(client)

let database = try await mysql.get(
    databaseId: "<DATABASE_ID>"
)

print(database.connectionString)
```
```server-go
package main

import (
    "fmt"

    "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("<YOUR_API_KEY>"),
    )

    mysql := appwrite.NewMysql(client)

    database, err := mysql.Get("<DATABASE_ID>")
    if err != nil {
        panic(err)
    }
    fmt.Println(database.ConnectionString)
}
```
```server-rust
use appwrite::client::Client;
use appwrite::services::mysql::Mysql;

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

    let mysql = Mysql::new(&client);

    let database = mysql.get("<DATABASE_ID>").await?;

    println!("{}", database.connection_string);

    Ok(())
}
```
```bash
curl -X GET \
  -H "X-Appwrite-Project: <PROJECT_ID>" \
  -H "X-Appwrite-Key: <YOUR_API_KEY>" \
  https://<REGION>.cloud.appwrite.io/v1/mysql/<DATABASE_ID>
```

The response includes the connection fields alongside the database configuration:

```json
{
  "$id": "<DATABASE_ID>",
  "name": "main",
  "engine": "mysql",
  "version": "8.4",
  "status": "ready",
  "hostname": "db-<hash>.<region>.appwrite.center",
  "connectionPort": 3306,
  "connectionUser": "admin.<hash>",
  "connectionPassword": "<password>",
  "connectionString": "mysql://admin.<hash>:<password>@db-<hash>.<region>.appwrite.center:3306/default?ssl=true"
}
```

The username is `admin.<hash>`, where the hash is the same one that appears in the hostname, and the database name is `default` for every database. The proxy routes your connection by this username, so always use the full value from the connection string.

### Connect with the mysql client

Pass the individual connection values to the `mysql` command-line client and enter the password when prompted:

```bash
mysql -h db-<hash>.<region>.appwrite.center -P 3306 -u admin.<hash> -p -D default
```

### Rotate the primary password

If your password is compromised, or your security policy requires regular rotation, you can issue a new password for the primary user. The change is applied atomically in the engine, and the response carries the new connection details. Existing sessions stay alive until they disconnect, then have to authenticate with the new password. The API key needs the `databases.write` scope.

```server-nodejs
import { Client, Mysql } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const mysql = new Mysql(client);

const database = await mysql.updateCredentials({
    databaseId: '<DATABASE_ID>',
});
```
```server-deno
import { Client, Mysql } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const mysql = new Mysql(client);

const database = await mysql.updateCredentials({
    databaseId: '<DATABASE_ID>',
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Mysql;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$mysql = new Mysql($client);

$database = $mysql->updateCredentials(databaseId: '<DATABASE_ID>');
```
```server-python
from appwrite.client import Client
from appwrite.services.mysql import Mysql

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

mysql = Mysql(client)

database = mysql.update_credentials(database_id='<DATABASE_ID>')
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

mysql = Mysql.new(client)

database = mysql.update_credentials(database_id: '<DATABASE_ID>')
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Mysql mysql = new Mysql(client);

var database = await mysql.UpdateCredentials(databaseId: "<DATABASE_ID>");
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Mysql mysql = Mysql(client);

final database = await mysql.updateCredentials(
    databaseId: '<DATABASE_ID>',
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Mysql

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val mysql = Mysql(client)

val database = mysql.updateCredentials(
    databaseId = "<DATABASE_ID>",
)
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let mysql = Mysql(client)

let database = try await mysql.updateCredentials(
    databaseId: "<DATABASE_ID>"
)
```
```server-go
package main

import (
    "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("<YOUR_API_KEY>"),
    )

    mysql := appwrite.NewMysql(client)

    _, err := mysql.UpdateCredentials("<DATABASE_ID>")
    if err != nil {
        panic(err)
    }
}
```
```server-rust
use appwrite::client::Client;
use appwrite::services::mysql::Mysql;

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

    let mysql = Mysql::new(&client);

    let database = mysql.update_credentials("<DATABASE_ID>").await?;

    Ok(())
}
```
```bash
curl -X PATCH \
  -H "X-Appwrite-Project: <PROJECT_ID>" \
  -H "X-Appwrite-Key: <YOUR_API_KEY>" \
  https://<REGION>.cloud.appwrite.io/v1/mysql/<DATABASE_ID>/credentials
```

### TLS

Connections on Appwrite Cloud are encrypted with TLS, terminated at the edge and forwarded to your database over the internal network. The connection string returned on the database object carries the right SSL settings for your environment, so drivers need no extra configuration.

For IP allowlists and other network controls, see [network security](/docs/products/databases/mysql/network-security).

### Connecting from an application

There is nothing Appwrite-specific about the driver setup. A few example snippets:

```server-nodejs
import mysql from 'mysql2/promise';

const connection = await mysql.createConnection(process.env.DATABASE_URL);

const [rows] = await connection.query('SELECT NOW() AS now');
console.log(rows);

await connection.end();
```
```server-python
import os
import mysql.connector

conn = mysql.connector.connect(
    host=os.environ['DB_HOST'],
    port=3306,
    user='admin.<hash>',
    password=os.environ['DB_PASSWORD'],
    database=os.environ['DB_NAME'],
)
cur = conn.cursor()
cur.execute('SELECT NOW()')
print(cur.fetchone())
conn.close()
```
```server-php
<?php

$pdo = new PDO(
    sprintf('mysql:host=%s;port=3306;dbname=%s', getenv('DB_HOST'), getenv('DB_NAME')),
    'admin.<hash>',
    getenv('DB_PASSWORD')
);

$rows = $pdo->query('SELECT NOW()')->fetchAll();
print_r($rows);
```
```server-go
package main

import (
    "database/sql"
    "fmt"
    "os"

    _ "github.com/go-sql-driver/mysql"
)

func main() {
    db, err := sql.Open("mysql", os.Getenv("MYSQL_DSN"))
    if err != nil {
        panic(err)
    }
    defer db.Close()

    var now string
    if err := db.QueryRow("SELECT NOW()").Scan(&now); err != nil {
        panic(err)
    }
    fmt.Println(now)
}
```
```server-rust
use sqlx::mysql::MySqlPoolOptions;
use sqlx::Row;

#[tokio::main]
async fn main() -> Result<(), sqlx::Error> {
    let url = std::env::var("DATABASE_URL").expect("DATABASE_URL");
    let pool = MySqlPoolOptions::new().connect(&url).await?;

    let row = sqlx::query("SELECT NOW() AS now").fetch_one(&pool).await?;
    let now: chrono::DateTime<chrono::Utc> = row.get("now");
    println!("{now}");

    Ok(())
}
```

Set the environment variables from the connection details on the database object. Once you can run a query, you can use any tool that talks the MySQL wire protocol: MySQL Workbench, DataGrip, your ORM of choice, your migration tool of choice. Appwrite gets out of the way.

---

## High availability

URL: https://appwrite.io/docs/products/databases/mysql/high-availability

A single database instance is a single point of failure. High availability (HA) adds streaming replicas next to your primary: they replicate continuously, serve read traffic through the [connection pooler](/docs/products/databases/mysql/connection-pooling), and take over automatically when the primary becomes unhealthy.

High availability is available on every specification. The maximum number of replicas depends on your plan.

### How it works

Replicas receive changes from the primary through MySQL binary log replication. Each replica is a full copy of the database on its own compute. When the primary fails, the most caught-up replica is promoted to primary and the hostname is repointed, your application keeps connecting to the same host and port.

### Replication modes

| Mode | Behavior | Trade-off |
|----------|----------------------------------------------------------------------------------|--------------------------------------------------|
| `async` | The primary commits without waiting for replicas | Fastest writes; a failover can lose the last moments of writes |
| `sync` | The primary waits for one replica to confirm each commit | No data loss on single failure; slightly higher write latency |
| `quorum` | The primary waits for a majority of replicas to confirm each commit | Strongest durability; highest write latency |

`async` is the default. For production workloads that cannot lose acknowledged writes, use `sync` with at least two replicas.

### Enable high availability

Set the replica count and replication mode on the database through the API:

```server-nodejs
import { Client, Mysql } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const mysql = new Mysql(client);

await mysql.update({
    databaseId: '<DATABASE_ID>',
    replicas: 2,
    syncMode: 'sync',
});
```
```server-deno
import { Client, Mysql } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const mysql = new Mysql(client);

await mysql.update({
    databaseId: '<DATABASE_ID>',
    replicas: 2,
    syncMode: 'sync',
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Mysql;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$mysql = new Mysql($client);

$mysql->update(
    databaseId: '<DATABASE_ID>',
    replicas: 2,
    syncMode: 'sync',
);
```
```server-python
from appwrite.client import Client
from appwrite.services.mysql import Mysql

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

mysql = Mysql(client)

mysql.update(
    database_id='<DATABASE_ID>',
    replicas=2,
    sync_mode='sync',
)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

mysql = Mysql.new(client)

mysql.update(
    database_id: '<DATABASE_ID>',
    replicas: 2,
    sync_mode: 'sync',
)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Mysql mysql = new Mysql(client);

await mysql.Update(
    databaseId: "<DATABASE_ID>",
    replicas: 2,
    syncMode: "sync"
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Mysql mysql = Mysql(client);

await mysql.update(
    databaseId: '<DATABASE_ID>',
    replicas: 2,
    syncMode: 'sync',
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Mysql

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val mysql = Mysql(client)

mysql.update(
    databaseId = "<DATABASE_ID>",
    replicas = 2,
    syncMode = "sync",
)
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let mysql = Mysql(client)

_ = try await mysql.update(
    databaseId: "<DATABASE_ID>",
    replicas: 2,
    syncMode: "sync"
)
```
```server-go
package main

import (
    "github.com/appwrite/sdk-for-go/appwrite"
    "github.com/appwrite/sdk-for-go/mysql"
)

func main() {
    client := appwrite.NewClient(
        appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"),
        appwrite.WithProject("<PROJECT_ID>"),
        appwrite.WithKey("<YOUR_API_KEY>"),
    )

    service := appwrite.NewMysql(client)

    _, err := service.Update(
        "<DATABASE_ID>",
        mysql.WithUpdateReplicas(2),
        mysql.WithUpdateSyncMode("sync"),
    )
    if err != nil {
        panic(err)
    }
}
```
```server-rust
use appwrite::client::Client;
use appwrite::services::mysql::Mysql;

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

    let mysql = Mysql::new(&client);

    mysql.update("<DATABASE_ID>", None, None, None, Some(2), Some("sync"), None, None, None, None, None, None, None, None, None, None, None, None, None, None, None).await?;

    Ok(())
}
```
```bash
curl -X PATCH \
  -H "X-Appwrite-Project: <PROJECT_ID>" \
  -H "X-Appwrite-Key: <YOUR_API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{
      "replicas": 2,
      "syncMode": "sync"
  }' \
  https://<REGION>.cloud.appwrite.io/v1/mysql/<DATABASE_ID>
```

Adding replicas provisions them online; the primary keeps serving traffic while each replica seeds from a snapshot and catches up. Setting `replicas` back to `0` disables HA.

### Check replication status

You can check each replica's role, health, and replication lag:

```server-nodejs
import { Client, Mysql } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const mysql = new Mysql(client);

const replicas = await mysql.getReplicas({
    databaseId: '<DATABASE_ID>',
});
```
```server-deno
import { Client, Mysql } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const mysql = new Mysql(client);

const replicas = await mysql.getReplicas({
    databaseId: '<DATABASE_ID>',
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Mysql;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$mysql = new Mysql($client);

$replicas = $mysql->getReplicas(
    databaseId: '<DATABASE_ID>',
);
```
```server-python
from appwrite.client import Client
from appwrite.services.mysql import Mysql

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

mysql = Mysql(client)

replicas = mysql.get_replicas(
    database_id='<DATABASE_ID>',
)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

mysql = Mysql.new(client)

replicas = mysql.get_replicas(
    database_id: '<DATABASE_ID>',
)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Mysql mysql = new Mysql(client);

var replicas = await mysql.GetReplicas(
    databaseId: "<DATABASE_ID>"
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Mysql mysql = Mysql(client);

final replicas = await mysql.getReplicas(
    databaseId: '<DATABASE_ID>',
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Mysql

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val mysql = Mysql(client)

val replicas = mysql.getReplicas(
    databaseId = "<DATABASE_ID>",
)
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let mysql = Mysql(client)

let replicas = try await mysql.getReplicas(
    databaseId: "<DATABASE_ID>"
)
```
```server-go
package main

import (
    "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("<YOUR_API_KEY>"),
    )

    service := appwrite.NewMysql(client)

    result, err := service.GetReplicas("<DATABASE_ID>")
    if err != nil {
        panic(err)
    }
    _ = result
}
```
```server-rust
use appwrite::client::Client;
use appwrite::services::mysql::Mysql;

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

    let mysql = Mysql::new(&client);

    let replicas = mysql.get_replicas("<DATABASE_ID>").await?;

    Ok(())
}
```
```bash
curl -X GET \
  -H "X-Appwrite-Project: <PROJECT_ID>" \
  -H "X-Appwrite-Key: <YOUR_API_KEY>" \
  https://<REGION>.cloud.appwrite.io/v1/mysql/<DATABASE_ID>/replicas
```

### Automatic failover

Appwrite continuously health-checks the primary. When it becomes unresponsive, the platform promotes the replica with the least replication lag, repoints the database hostname, and marks the old primary for replacement. Your application reconnects to the same hostname; a well-configured driver pool retries and recovers without intervention.

With `async` replication, writes that had not yet reached the promoted replica are lost in a failover. Use `sync` or `quorum` if that is unacceptable.

### Manual failover

Trigger a failover yourself, for example to test your application's recovery behavior. Optionally pass `targetReplicaId` to promote a specific replica.

```server-nodejs
import { Client, Mysql } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const mysql = new Mysql(client);

await mysql.createFailover({
    databaseId: '<DATABASE_ID>',
});
```
```server-deno
import { Client, Mysql } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const mysql = new Mysql(client);

await mysql.createFailover({
    databaseId: '<DATABASE_ID>',
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Mysql;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$mysql = new Mysql($client);

$mysql->createFailover(
    databaseId: '<DATABASE_ID>',
);
```
```server-python
from appwrite.client import Client
from appwrite.services.mysql import Mysql

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

mysql = Mysql(client)

mysql.create_failover(
    database_id='<DATABASE_ID>',
)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

mysql = Mysql.new(client)

mysql.create_failover(
    database_id: '<DATABASE_ID>',
)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Mysql mysql = new Mysql(client);

await mysql.CreateFailover(
    databaseId: "<DATABASE_ID>"
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Mysql mysql = Mysql(client);

await mysql.createFailover(
    databaseId: '<DATABASE_ID>',
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Mysql

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val mysql = Mysql(client)

mysql.createFailover(
    databaseId = "<DATABASE_ID>",
)
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let mysql = Mysql(client)

_ = try await mysql.createFailover(
    databaseId: "<DATABASE_ID>"
)
```
```server-go
package main

import (
    "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("<YOUR_API_KEY>"),
    )

    service := appwrite.NewMysql(client)

    _, err := service.CreateFailover("<DATABASE_ID>")
    if err != nil {
        panic(err)
    }
}
```
```server-rust
use appwrite::client::Client;
use appwrite::services::mysql::Mysql;

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

    let mysql = Mysql::new(&client);

    mysql.create_failover("<DATABASE_ID>", None).await?;

    Ok(())
}
```
```bash
curl -X POST \
  -H "X-Appwrite-Project: <PROJECT_ID>" \
  -H "X-Appwrite-Key: <YOUR_API_KEY>" \
  https://<REGION>.cloud.appwrite.io/v1/mysql/<DATABASE_ID>/failovers
```

### Reading from replicas

Replicas serve read traffic when [read/write splitting](/docs/products/databases/mysql/connection-pooling#read-write-splitting) is enabled on the connection pooler. With `async` replication, a read that immediately follows a write can return stale data. Route reads that must see the latest write to the primary, or use `sync` replication.

### Limits and billing

- Up to 5 replicas per database; the maximum depends on your plan.
- Each replica runs on the same specification as the primary and is billed as an add-on. See [pricing](/pricing).
- Replicas live in the same region as the primary.

---

## Auth.js

URL: https://appwrite.io/docs/products/databases/mysql/integrations/auth-js

[Auth.js](https://authjs.dev/) (formerly NextAuth.js) persists users, accounts, sessions, and verification tokens through a database adapter. When you configure an adapter, those records live in your database, which makes database sessions, account linking, and email sign-in possible. An Appwrite [native MySQL database](/docs/products/databases/mysql) is a standard MySQL engine, so Auth.js works through the same ORM adapters you use with other MySQL databases.

**Before you start**

You'll need a native MySQL database in a `ready` state and its credentials. See [native MySQL databases](/docs/products/databases/mysql) to create one with the create-database wizard, then use [Connections](/docs/products/databases/mysql/connections#credentials) to read the hostname, password, database name, and connection string with `mysql.get()`.

### Choose an adapter

Auth.js talks to your database through an adapter. For a native MySQL database, use the adapter that matches your ORM:

- **Prisma** through [`@auth/prisma-adapter`](https://authjs.dev/getting-started/adapters/prisma). This page shows the Prisma setup with Prisma's MySQL provider and `@prisma/adapter-mariadb`.
- **Drizzle** through [`@auth/drizzle-adapter`](https://authjs.dev/getting-started/adapters/drizzle). Use Drizzle's MySQL schema and a `mysql2` database instance, then pass `DrizzleAdapter(db)` to Auth.js. See the [Drizzle guide](/docs/products/databases/mysql/integrations/drizzle) for the MySQL driver and migration setup.

### Install packages

Install Auth.js, the Prisma adapter, Prisma Client, Prisma's MySQL driver adapter, and the MariaDB driver used by the adapter:

```bash
npm install next-auth@beta @auth/prisma-adapter @prisma/client @prisma/adapter-mariadb mariadb dotenv
npm install -D prisma typescript tsx @types/node
```

### Set the connection strings

Store the Appwrite connection string in environment variables and do not commit it. Prisma uses `sslaccept=strict` for TLS with certificate verification on MySQL connections:

```env
DATABASE_URL="mysql://admin.<hash>:<password>@db-<hash>.<region>.appwrite.center:3306/default?sslaccept=strict"
DIRECT_URL="mysql://admin.<hash>:<password>@db-<hash>.<region>.appwrite.center:3306/default?sslaccept=strict"

### Runtime pooler
### DATABASE_URL="mysql://admin.<hash>:<password>@db-<hash>.<region>.appwrite.center:6033/default?sslaccept=strict"
```

`DATABASE_URL` is the runtime connection used by Prisma Client. `DIRECT_URL` is the direct MySQL connection used by Prisma CLI commands for migrations and introspection. Keep migrations on port `3306`; if you use the [connection pooler](/docs/products/databases/mysql/connection-pooling) for runtime traffic, only move `DATABASE_URL` to port `6033`.

### Create the adapter schema

Auth.js expects four Prisma models: `User`, `Account`, `Session`, and `VerificationToken`. Add them to `prisma/schema.prisma` with the MySQL provider. The table names below use an `auth_js_` prefix so they stay separate from your application tables.

```prisma
generator client {
  provider = "prisma-client"
  output   = "../generated/prisma"
}

datasource db {
  provider = "mysql"
}

model User {
  id            String    @id @default(cuid())
  name          String?
  email         String?   @unique
  emailVerified DateTime?
  image         String?
  accounts      Account[]
  sessions      Session[]

  @@map("auth_js_users")
}

model Account {
  id                String  @id @default(cuid())
  userId            String
  type              String
  provider          String
  providerAccountId String
  refresh_token     String? @db.Text
  access_token      String? @db.Text
  expires_at        Int?
  token_type        String?
  scope             String?
  id_token          String? @db.Text
  session_state     String?
  user              User    @relation(fields: [userId], references: [id], onDelete: Cascade)

  @@unique([provider, providerAccountId])
  @@map("auth_js_accounts")
}

model Session {
  id           String   @id @default(cuid())
  sessionToken String   @unique
  userId       String
  expires      DateTime
  user         User     @relation(fields: [userId], references: [id], onDelete: Cascade)

  @@map("auth_js_sessions")
}

model VerificationToken {
  identifier String
  token      String
  expires    DateTime

  @@unique([identifier, token])
  @@map("auth_js_verification_tokens")
}
```

Configure Prisma CLI commands in `prisma.config.ts`. The CLI uses `DIRECT_URL` because migrations need a direct MySQL session.

```ts
import 'dotenv/config';
import { defineConfig, env } from 'prisma/config';

export default defineConfig({
    schema: 'prisma/schema.prisma',
    migrations: {
        path: 'prisma/migrations'
    },
    datasource: {
        url: env('DIRECT_URL')
    }
});
```

### Run the migration

Create a migration file from the Auth.js schema:

```bash
mkdir -p prisma/migrations/20260708160000_authjs_init
npx prisma migrate diff --from-empty --to-schema prisma/schema.prisma --script --output prisma/migrations/20260708160000_authjs_init/migration.sql
```

Apply committed migrations through the direct MySQL connection:

```bash
npx prisma migrate deploy
```

Generate Prisma Client after you install dependencies or change `prisma/schema.prisma`:

```bash
npx prisma generate
```

Prisma's `migrate dev` command uses a shadow database to detect schema drift. Use it against a local MySQL database, a separate Appwrite database, or an Appwrite branch, then apply committed migrations to this database with `migrate deploy`.

### Wire the adapter into Auth.js

Create one Prisma Client with Prisma's MySQL driver adapter:

```ts
import 'dotenv/config';
import { PrismaMariaDb } from '@prisma/adapter-mariadb';
import { PrismaClient } from '../generated/prisma/client';

const connectionString = process.env.DATABASE_URL;
if (!connectionString) throw new Error('DATABASE_URL is required');

const adapter = new PrismaMariaDb(connectionString);

export const prisma = new PrismaClient({ adapter });
```

Pass the Prisma Client to your Auth.js config through the `adapter` key:

```ts
import NextAuth from 'next-auth';
import { PrismaAdapter } from '@auth/prisma-adapter';
import { prisma } from './prisma';

export const { handlers, auth, signIn, signOut } = NextAuth({
    adapter: PrismaAdapter(prisma),
    session: { strategy: 'database' },
    providers: [
        // your providers, e.g. GitHub, Google, Resend
    ]
});
```

### Database vs JWT sessions

Auth.js has two session strategies:

- **`database`**: a session row is written to the `Session` model and only an opaque session ID is stored in an `HttpOnly` cookie. Each request looks the session up in the native MySQL database, and sessions can be revoked server-side.
- **`jwt`**: session state lives in a signed cookie, and the database is not read on the session path.

When you set `strategy: "database"`, keep the `Session` model in your Prisma schema. With `strategy: "jwt"`, the adapter still persists users and linked accounts, so account linking and user management continue to use the database.

### Pool connections from serverless

On serverless and edge platforms, each running instance opens its own database connections. Route runtime traffic through the [connection pooler](/docs/products/databases/mysql/connection-pooling) on port `6033` by setting `DATABASE_URL` to the pooler URL while keeping `DIRECT_URL` on port `3306`.

The pooler defaults to transaction mode, which gives the highest connection multiplexing. Transaction mode does not keep a backend connection across statements, so session-level features such as user variables, temporary tables, and session-scoped prepared statements need session mode or a direct connection.

### Use a branch for previews

[Branches](/docs/products/databases/mysql/branches) are isolated copies of a database with their own hostname and connection string. They are useful for pull-request previews and integration-test jobs that sign users in and out against throwaway data:

1. Create a branch from the API and read its `connectionString`.
2. Export it as both `DIRECT_URL` and `DATABASE_URL`. Branches have no pooler.
3. Run `npx prisma migrate deploy` and your auth flow against the branch.
4. Delete the branch when the job finishes.

Because a branch starts from a storage snapshot, the Auth.js tables and data match the source database at branch time, so preview sign-ins behave like production without touching it.

### Related

- [Prisma](/docs/products/databases/mysql/integrations/prisma): Datasource, driver adapter, and migrations for Prisma.
- [Drizzle](/docs/products/databases/mysql/integrations/drizzle): Driver connection, schema definitions, and drizzle-kit migrations for MySQL.
- [Connection pooler](/docs/products/databases/mysql/connection-pooling): Pool modes, ports, and serverless connection handling.
- [Better Auth](/docs/products/databases/mysql/integrations/better-auth): The same pattern for Better Auth on a native MySQL database.

---

## Better Auth

URL: https://appwrite.io/docs/products/databases/mysql/integrations/better-auth

[Better Auth](https://www.better-auth.com/) is a framework-agnostic authentication library for TypeScript that stores users, sessions, accounts, and verification records in your database. An Appwrite [native MySQL database](/docs/products/databases/mysql) gives Better Auth a standard MySQL engine, so you can use `mysql2`, run the Better Auth CLI, and keep auth data in your Appwrite project.

**Before you start**

You'll need a native MySQL database in a `ready` state and its credentials. See [native MySQL databases](/docs/products/databases/mysql) to create one and [Connections](/docs/products/databases/mysql/connections) to retrieve the connection string. The username is `admin.<hash>`, and the database name is `default`.

### Set the connection strings

Better Auth uses one database URL at runtime and another for schema work. Use the pooler URL for application traffic, and use the direct engine URL for schema generation and migrations.

```env
### Runtime: pooled, transaction mode
DATABASE_URL="mysql://admin.<hash>:<password>@db-<hash>.<region>.appwrite.center:6033/default"

### Schema generation and migrations: direct connection to the engine
DIRECT_URL="mysql://admin.<hash>:<password>@db-<hash>.<region>.appwrite.center:3306/default"

MYSQL_SSL="true"
BETTER_AUTH_URL="https://example.com"
BETTER_AUTH_SECRET="replace-with-a-long-random-secret"
```

The MySQL pooler runs on port `6033`, and the engine runs on port `3306`. Connections on Appwrite Cloud are encrypted with TLS. The `MYSQL_SSL` variable below lets local development use an unencrypted port-forward while Cloud uses TLS verification. See [Connections](/docs/products/databases/mysql/connections) for connection fields and [Connection pooling](/docs/products/databases/mysql/connection-pooling#modes) for pool modes.

### Install Better Auth and mysql2

Install Better Auth with the MySQL driver:

```bash
npm install better-auth mysql2
```

### Configure Better Auth

Pass a `mysql2/promise` pool to Better Auth. Better Auth drives that pool through its built-in Kysely adapter, so the Better Auth CLI can generate and apply the schema for you.

The example below gives Better Auth tables a `better_auth_` prefix. Keep those model names if you want the auth tables grouped together, or choose names that match your schema conventions.

```ts
import { betterAuth } from 'better-auth';
import { createPool } from 'mysql2/promise';

if (!process.env.DATABASE_URL) {
    throw new Error('DATABASE_URL is required');
}

const ssl =
    process.env.MYSQL_SSL === 'true'
        ? { rejectUnauthorized: true }
        : undefined;

export const auth = betterAuth({
    baseURL: process.env.BETTER_AUTH_URL,
    secret: process.env.BETTER_AUTH_SECRET,
    database: createPool({
        uri: process.env.DATABASE_URL,
        timezone: 'Z',
        ssl
    }),
    emailAndPassword: { enabled: true },
    user: { modelName: 'better_auth_user' },
    session: { modelName: 'better_auth_session' },
    account: { modelName: 'better_auth_account' },
    verification: { modelName: 'better_auth_verification' }
});
```

`timezone: 'Z'` keeps timestamp values consistent between your application and MySQL. Leave mysql2's default `FOUND_ROWS` behavior enabled, because Better Auth relies on matched-row counts for some updates.

### Generate and migrate the schema

The [Better Auth CLI](https://www.better-auth.com/docs/concepts/cli) reads your `auth` config and creates the tables it needs. Run schema commands with the direct URL so DDL runs over a session connection:

```bash
DATABASE_URL="$DIRECT_URL" npx auth@latest generate --config ./auth.ts --yes
```

Then apply the schema:

```bash
DATABASE_URL="$DIRECT_URL" npx auth@latest migrate --config ./auth.ts --yes
```

`migrate` is available for Better Auth's built-in Kysely adapter. If you use the Better Auth Prisma or Drizzle adapter, run `generate` to produce that ORM's schema, then apply it with the ORM migration tool over `DIRECT_URL`. See the [Prisma](/docs/products/databases/mysql/integrations/prisma) and [Drizzle](/docs/products/databases/mysql/integrations/drizzle) guides for their MySQL configuration.

### Create a user

After the schema exists, mount Better Auth's handler in your framework and call the API methods from your server code:

```ts
import { auth } from './auth';

const result = await auth.api.signUpEmail({
    body: {
        email: 'ada@example.com',
        password: 'a-strong-password',
        name: 'Ada Lovelace'
    }
});

console.log(result.user.id);
```

On a long-running server, create the `auth` instance once at module scope and reuse it. On serverless, keep a single instance per module scope so warm invocations reuse the mysql2 pool.

### Pooling and session state

Auth workloads, including sign-up, login, and session lookups, are short database transactions. Pointing runtime traffic at port `6033` lets many application instances share a smaller backend connection pool when the pooler is available.

The pooler's transaction mode does not keep a backend connection across statements. Better Auth's built-in MySQL path works through Kysely and mysql2 text queries, which fit transaction pooling. If your surrounding application code uses session-bound features such as server-side prepared statements, user variables, or temporary tables, use the direct port or switch the pooler to session mode. See [pooler modes](/docs/products/databases/mysql/connection-pooling#modes) for the trade-offs.

### Related

- [Connections](/docs/products/databases/mysql/connections): Retrieve credentials and rotate the primary password.
- [Connection pooling](/docs/products/databases/mysql/connection-pooling): Pool modes, ports, and read/write splitting for serverless workloads.
- [Prisma](/docs/products/databases/mysql/integrations/prisma): Configure Prisma with pooled and direct MySQL URLs.
- [Drizzle](/docs/products/databases/mysql/integrations/drizzle): Configure Drizzle ORM with mysql2 and Drizzle Kit migrations.
- [Branches](/docs/products/databases/mysql/branches): Ephemeral database copies for preview environments and CI.
- [Auth.js](/docs/products/databases/mysql/integrations/auth-js): Use a native MySQL database as the Auth.js database.

---

## dbt

URL: https://appwrite.io/docs/products/databases/mysql/integrations/dbt

Use dbt Core with an Appwrite native MySQL database through the community [`dbt-mysql`](https://docs.getdbt.com/docs/local/connect-data-platform/mysql-setup) adapter. Appwrite exposes standard MySQL on port `3306`, so dbt connects with the same host, username, password, and database name you use with other MySQL clients.

dbt compiles your models into `CREATE TABLE` and `CREATE VIEW` statements, then runs them in dependency order to build transformed tables and views inside the MySQL database you configure.

**Adapter support**

dbt Labs lists MySQL as a community adapter. The published [`dbt-mysql`](https://pypi.org/project/dbt-mysql/) package is experimental and is not dbt-supported, so pin and test the adapter version you deploy.

**Before you start**

You'll need a native MySQL database in a `ready` state and its credentials. See [MySQL](/docs/products/databases/mysql) to create one and [Connections](/docs/products/databases/mysql/connections) to retrieve them. The username is `admin.<hash>`, the database name is `default`, and the engine listens on port `3306`.

### Install the adapter

Install dbt Core and the MySQL adapter in the Python environment where you run dbt:

```bash
python -m pip install dbt-mysql
```

### Choose where dbt builds

In MySQL, dbt's `schema` setting is the MySQL database name. Set `schema` to `default`, the database name Appwrite uses for every MySQL database.

Use a naming convention such as a `dbt_` prefix for dbt models and seeds, for example `dbt_orders` and `dbt_customer_revenue`, to keep transformed tables separate from application tables in the same database. For stricter isolation, use another native MySQL database or validate changes against a branch.

### Configure profiles.yml

dbt reads connection details from `~/.dbt/profiles.yml` or the directory in `DBT_PROFILES_DIR`. Configure a `mysql` target against the database host on port `3306`:

```yaml
analytics:
  target: dev
  outputs:
    dev:
      type: mysql
      server: db-<hash>.<region>.appwrite.center
      port: 3306
      username: admin.<hash>
      password: "{{ env_var('APPWRITE_DB_PASSWORD') }}"
      schema: default
      threads: 4
      charset: utf8mb4
      collation: utf8mb4_0900_ai_ci
```

The `dbt-mysql` adapter uses `server`, `username`, `password`, and `schema`. Read the password from an environment variable with `env_var` rather than committing it. The `schema` value is `default`, the database name in your Appwrite connection string.

### Test the connection

`dbt debug` validates your project files and opens a connection to confirm the credentials and host are correct:

```bash
dbt debug
```

A successful run reports `Connection test: OK connection ok`. If it fails, recheck the host, port, password, and database name.

### Run transformations

Build your models into the configured MySQL database:

```bash
dbt run
```

`dbt run` executes models only, materializing each as a table or view. Use `dbt build` to run models, tests, seeds, and snapshots together in DAG order:

```bash
dbt build
```

### Choose the right connection

dbt opens one database connection per thread and runs DDL during model builds. Point dbt at a connection that preserves the session for each thread:

- **Direct engine port (`3306`)** is the recommended target. Each thread gets a backend session with the DDL privileges dbt needs. This is what the `profiles.yml` above uses.
- **Session-mode pooler** works too. Connect on the pooler port (`6033`) and set the pooler to `session` mode.

Avoid the transaction-mode pooler for dbt. Transaction mode returns the backend connection to the pool after each transaction, which can break workflows that rely on session state. See the [connection pooling](/docs/products/databases/mysql/connection-pooling#modes) page for the mode trade-offs.

### Size threads to your connection budget

The `threads` setting controls how many models dbt builds in parallel, and dbt opens one connection per thread. A `threads: 8` run can hold up to eight backend connections at once. dbt also respects model dependencies, so it never runs more models concurrently than your DAG allows.

Pick a `threads` value that fits the connection budget for your database specification, and leave headroom for application traffic sharing the same database. Start at `4` and raise it only while connections stay within budget.

### Related

- [Connections](/docs/products/databases/mysql/connections): Retrieve credentials and rotate the primary password.
- [Connection pooler](/docs/products/databases/mysql/connection-pooling): Pool modes and ports. Use session mode for dbt.
- [Branches](/docs/products/databases/mysql/branches): Ephemeral database copies for CI and preview environments.
- [Network](/docs/products/databases/mysql/network-security): TLS behavior, certificate verification, and IP allowlists.

[dbt MySQL adapter reference](https://docs.getdbt.com/docs/local/connect-data-platform/mysql-setup)

---

## Django

URL: https://appwrite.io/docs/products/databases/mysql/integrations/django

A native MySQL database is a standard MySQL engine, so Django's ORM works against it with no Appwrite-specific configuration. Point the `DATABASES` setting at the credentials from the [Connections](/docs/products/databases/mysql/connections) page, then use migrations, models, and the rest of Django as you would against any MySQL server.

**Before you start**

You'll need a native MySQL database in a `ready` state and its credentials. See [native MySQL databases](/docs/products/databases/mysql) to create one with the create-database wizard, then use [`mysql.get()`](/docs/products/databases/mysql/connections#credentials) to read the hostname, port, username, and password. The username is `admin.<hash>` and the database name is `default`.

### Install a driver

Django talks to MySQL through a DB API driver. Django's current recommended driver is [mysqlclient](https://pypi.org/project/mysqlclient/):

```bash
pip install mysqlclient
```

`mysqlclient` builds against MySQL client libraries. If installation fails, install the MySQL development headers and `pkg-config` for your operating system, then run the same command again.

Django's MySQL backend uses the `ENGINE` value `django.db.backends.mysql`.

### Configure `DATABASES`

In `settings.py`, point the `default` connection at your native MySQL database. Appwrite Cloud requires TLS on the public hostname, and the certificate is signed by a public certificate authority. Read every value from the environment so credentials stay out of source control:

```python
import os

DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.mysql",
        "NAME": os.environ["DB_NAME"],
        "USER": os.environ["DB_USER"],
        "PASSWORD": os.environ["DB_PASSWORD"],
        "HOST": os.environ["DB_HOST"],
        "PORT": os.environ["DB_PORT"],
        "OPTIONS": {
            "charset": "utf8mb4",
            "ssl_mode": os.environ.get("DB_SSL_MODE", "REQUIRED"),
        },
    }
}
```

The MySQL backend passes `OPTIONS` to `mysqlclient`. `charset` keeps client encoding aligned with Django's UTF-8 expectations, and `ssl_mode` controls TLS for the MySQL connection. Use `REQUIRED` for encrypted Cloud connections. For full certificate and hostname verification, set `DB_SSL_MODE=VERIFY_IDENTITY`. See [Network security](/docs/products/databases/mysql/network-security) for TLS and IP allowlist guidance.

Populate the environment with values from `mysql.get()` in the [API credentials flow](/docs/products/databases/mysql/connections#credentials):

```env
DB_NAME=default
DB_USER=admin.<hash>
DB_PASSWORD=<password>
DB_HOST=db-<hash>.<region>.appwrite.center
DB_PORT=3306
DB_SSL_MODE=REQUIRED
```

Port `3306` is the direct MySQL port. Keep migrations on this port, and see [pooling](#pooling) below for when to add the pooler.

### Run migrations

Generate migrations from your models, then apply them against the direct MySQL port:

```bash
python manage.py makemigrations
python manage.py migrate
```

`migrate` needs a session connection with DDL privileges. The `admin.<hash>` user owns the database and can run schema changes. Always run `migrate` on the direct MySQL port, not through the transaction-mode pooler.

### Define a model

Models map to tables in your native MySQL database. Define one in an app's `models.py`:

```python
from django.db import models

class Article(models.Model):
    title = models.CharField(max_length=200)
    body = models.TextField()
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        db_table = "django_articles"
        ordering = ["-created_at"]
```

Run `makemigrations` and `migrate` again to create the table, then query it through the ORM:

```python
from blog.models import Article

Article.objects.create(title="Hello", body="First post")

recent = Article.objects.order_by("-created_at")[:10]
```

### Persistent connections and pooling

By default Django opens a new connection per request (`CONN_MAX_AGE = 0`). On a long-running WSGI server (Gunicorn, uWSGI), you can reuse connections by raising it. Each worker thread keeps its own connection, so the database must allow at least as many connections as you run worker threads:

```python
DATABASES["default"]["CONN_MAX_AGE"] = 60
DATABASES["default"]["CONN_HEALTH_CHECKS"] = True
```

`CONN_HEALTH_CHECKS` revalidates a reused connection once per request, reducing errors after an engine restart. Do not enable persistent connections under the development server, because it spawns a thread per request and gains nothing. Keep persistent connections disabled under ASGI.

**Persistent connections need a session**

A worker holding a connection across requests behaves like a long-lived session. Connect it to the direct MySQL port or the **session-mode** pooler. The default **transaction-mode** pooler can hand statements to different backend connections, which makes it a better fit for short-lived runtimes that open and close a connection per request or invocation.

If you front the database with the transaction-mode [connection pooler](/docs/products/databases/mysql/connection-pooling), point `HOST` and `PORT` at the MySQL pooler on port `6033` and keep `CONN_MAX_AGE = 0`:

```env
DB_HOST=db-<hash>.<region>.appwrite.center
DB_PORT=6033
```

Transaction mode does not preserve session state between statements. Use the direct port or **session mode** for migrations, temporary tables, user variables, named locks, and other session-scoped behavior. See the [pooler](/docs/products/databases/mysql/connection-pooling#modes) page for the trade-offs.

### Use a branch for previews and CI

[Branches](/docs/products/databases/mysql/branches) are instant, isolated copies of a database with their own hostname and connection details. They're ideal for running migrations against throwaway data in a pull-request preview or an integration-test job:

1. Create a branch from the API and read its connection details.
2. Export them as the `DB_*` environment variables your settings read.
3. Run `python manage.py migrate` and your test suite against the branch.
4. Delete the branch when the job finishes.

Because a branch starts from a storage snapshot, its schema and data match the source database at branch time, so migrations run against realistic data without touching production.

### Related

- [Connections](/docs/products/databases/mysql/connections): Retrieve credentials, rotate the password, and connect with common clients.
- [Connection pooler](/docs/products/databases/mysql/connection-pooling): Pool modes, ports, and read/write splitting for serverless workloads.
- [Branches](/docs/products/databases/mysql/branches): Ephemeral database copies for preview environments and CI.
- [Network security](/docs/products/databases/mysql/network-security): TLS, certificate verification, and IP allowlists.

---

## Node.js drivers

URL: https://appwrite.io/docs/products/databases/mysql/integrations/drivers

A native MySQL database works with standard Node.js drivers that speak the MySQL wire protocol. The [Connections](/docs/products/databases/mysql/connections#drivers) page shows the smallest query. This guide covers driver pools, TLS verification, serverless connection management, and common connection errors.

**Before you start**

You'll need a native MySQL database in a `ready` state and the connection values returned by the database object: host, port, username, and password. The username is `admin.<hash>` and the database name is `default`. Store the password in an environment variable and keep it out of source control.

### Raw driver, ORM, or SQL API?

There are three common ways to query a native MySQL database from Node.js. Pick by workload:

| Approach | Use it when |
|------------------------------|----------------------------------------------------------------------------------------------|
| **Raw driver** (this page) | You want a connection pool you control, hot-path queries, or a thin data layer with little overhead. |
| **ORM** ([Prisma](/docs/products/databases/mysql/integrations/prisma), [Drizzle](/docs/products/databases/mysql/integrations/drizzle)) | You want migrations, a typed schema, and query building. The ORM still uses a MySQL driver underneath. |
| **[SQL API](/docs/products/databases/mysql/quick-start#first-queries)** | You're on a runtime that cannot open TCP sockets, or you're scripting a query over HTTPS. |

The rest of this page is for long-running or serverless Node.js servers that can open TCP connections.

### mysql2 promise API

Install mysql2 with `npm install mysql2`. For a long-running server, create one promise pool at module scope and reuse it for every request. The pool opens connections lazily up to `connectionLimit`.

```js
import mysql from 'mysql2/promise';
import process from 'node:process';

const pool = mysql.createPool({
  host: 'db-<hash>.<region>.appwrite.center',
  port: 3306,
  user: 'admin.<hash>',
  password: process.env.DB_PASSWORD,
  database: 'default',
  ssl: { rejectUnauthorized: true },
  waitForConnections: true,
  connectionLimit: 10,
  idleTimeout: 30000,
  enableKeepAlive: true,
});

await pool.query(`
  CREATE TABLE IF NOT EXISTS drivers_events (
    id INT AUTO_INCREMENT PRIMARY KEY,
    email VARCHAR(255) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
  )
`);

const [result] = await pool.execute(
  'INSERT INTO drivers_events (email) VALUES (?)',
  ['ada@example.com']
);

const [rows] = await pool.execute(
  'SELECT id, email FROM drivers_events WHERE id = ?',
  [result.insertId]
);

console.log(rows[0]);

await pool.end();
```

Use `execute` for parameterized statements on the direct MySQL port. mysql2 prepares these statements on the server and caches them per connection, which is a good fit for a stable application pool.

### mysql2 callback API

Use the callback API when your application already follows callback patterns. Keep the pool shared, and close it only during process shutdown.

```js
import mysql from 'mysql2';
import process from 'node:process';

const pool = mysql.createPool({
  host: 'db-<hash>.<region>.appwrite.center',
  port: 3306,
  user: 'admin.<hash>',
  password: process.env.DB_PASSWORD,
  database: 'default',
  ssl: { rejectUnauthorized: true },
  waitForConnections: true,
  connectionLimit: 10,
  idleTimeout: 30000,
  enableKeepAlive: true,
});

pool.query(`
  CREATE TABLE IF NOT EXISTS drivers_events_callback (
    id INT AUTO_INCREMENT PRIMARY KEY,
    email VARCHAR(255) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
  )
`, (createErr) => {
  if (createErr) throw createErr;

  pool.execute(
    'INSERT INTO drivers_events_callback (email) VALUES (?)',
    ['grace@example.com'],
    (insertErr, result) => {
      if (insertErr) throw insertErr;

      pool.execute(
        'SELECT id, email FROM drivers_events_callback WHERE id = ?',
        [result.insertId],
        (selectErr, rows) => {
          if (selectErr) throw selectErr;
          console.log(rows[0]);
          pool.end();
        }
      );
    }
  );
});
```

### MariaDB Connector/Node.js

MariaDB Connector/Node.js is a maintained JavaScript driver for MariaDB and MySQL databases. Install it with `npm install mariadb`. Its default API is promise-based and it exposes a pool with `connectionLimit`.

```js
import mariadb from 'mariadb';
import process from 'node:process';

const pool = mariadb.createPool({
  host: 'db-<hash>.<region>.appwrite.center',
  port: 3306,
  user: 'admin.<hash>',
  password: process.env.DB_PASSWORD,
  database: 'default',
  ssl: true,
  connectionLimit: 10,
  connectTimeout: 5000,
});

let connection;

try {
  connection = await pool.getConnection();

  await connection.query(`
    CREATE TABLE IF NOT EXISTS drivers_events_mariadb (
      id INT AUTO_INCREMENT PRIMARY KEY,
      email VARCHAR(255) NOT NULL,
      created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
    )
  `);

  const result = await connection.query(
    'INSERT INTO drivers_events_mariadb (email) VALUES (?)',
    ['lin@example.com']
  );

  const rows = await connection.query(
    'SELECT id, email FROM drivers_events_mariadb WHERE id = ?',
    [result.insertId]
  );

  console.log(rows[0]);
} finally {
  if (connection) connection.release();
  await pool.end();
}
```

The connector uses Node.js trusted root CAs when `ssl: true` is enabled. If your runtime image does not ship with a current trust store, pass a CA bundle with `ssl: { ca: fs.readFileSync('./ca-bundle.crt') }`.

### TLS and CA verification

Connections on Appwrite Cloud are encrypted with TLS, terminated at the edge and forwarded to your database over the internal network. Use certificate verification in production:

| Driver | TLS option |
|--------------------------|-------------------------------------------------|
| `mysql2` | `ssl: { rejectUnauthorized: true }` |
| `mariadb` | `ssl: true` |
| Custom CA bundle | Add `ca` inside the driver's `ssl` object |

The database hostname uses a certificate signed by a public CA, so Node.js can verify it with the built-in trust store in standard runtimes. Use a custom CA bundle only for images that do not include one, or when your organization requires a pinned bundle.

### Pool sizing

Pool sizing is a connection budget. Each connection in `connectionLimit`, across every running instance, counts against your specification's connection cap. A single app server with `connectionLimit: 10` is usually modest. Ten replicas with `connectionLimit: 50` each can open 500 connections and exhaust smaller specifications.

Size each driver pool to the connection cap divided by replica count, then leave headroom for migrations, background workers, and admin tools. If you need more client concurrency than the engine can accept directly, route runtime traffic through the [connection pooler](/docs/products/databases/mysql/connection-pooling).

### Serverless connection management

On Lambda, Cloud Run, Vercel, and similar platforms, every cold start is a fresh instance with its own pool. Keep the pool at module scope so warm invocations reuse it, and keep each pool small, usually `connectionLimit: 1` or `2`.

Appwrite's MySQL pooler is ProxySQL on port `6033`. It is designed for high fan-out runtime traffic and defaults to transaction mode. In transaction mode, use text queries with placeholders through `query`, and keep server-side prepared statements, temporary tables, user variables, and named locks on the direct port (`3306`) or a session-mode pooler.

Run migrations, schema changes, and dump or restore tools on the direct port. These workflows rely on a stable session and DDL privileges.

### Troubleshooting

| Symptom | Cause and fix |
|----------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------|
| `self-signed certificate` / `unable to verify the first certificate` | TLS is reaching the wrong host, or the runtime has a stale or incomplete CA bundle. Verify the database hostname and supply a current public CA bundle only when the runtime lacks one. |
| `ER_CON_COUNT_ERROR` / connection attempts rejected at the proxy | Aggregate pool size across all instances exceeds the database specification's connection cap. Lower each pool's `connectionLimit`, or use the [connection pooler](/docs/products/databases/mysql/connection-pooling) on port `6033` with small per-instance pools. |
| `Unknown prepared statement handler` or prepared statement errors on port `6033` | A prepared statement cache is being used through the transaction-mode pooler. Use `query` with placeholders for pooled runtime traffic, or use the direct port or [session mode](/docs/products/databases/mysql/connection-pooling#modes). |
| `ETIMEDOUT` / `connect ETIMEDOUT` | The database may be cold-starting on smaller specifications or blocked by an [IP allowlist](/docs/products/databases/mysql/network-security#ip-allowlist). Confirm your egress IP is allowed and raise `connectTimeout` to absorb cold starts. |
| Connections drop after a period of inactivity | The proxy closes idle connections after its network idle timeout. Keep mysql2 `idleTimeout` below that window, and enable keep-alive where the driver supports it. |

### Related

- [Connections](/docs/products/databases/mysql/connections): Retrieve credentials, rotate the password, and connect with common clients.
- [Connection pooler](/docs/products/databases/mysql/connection-pooling): Pool modes, ports, and read/write splitting for serverless workloads.
- [SQL API](/docs/products/databases/mysql/quick-start#first-queries): Run SQL over HTTPS with no TCP connection for edge runtimes and scripts.
- [Network security](/docs/products/databases/mysql/network-security): TLS, certificate verification, and IP allowlists.

---

## Drizzle

URL: https://appwrite.io/docs/products/databases/mysql/integrations/drizzle

Appwrite's native MySQL database is a standard MySQL engine, so [Drizzle ORM](https://orm.drizzle.team/) works against it with no Appwrite-specific configuration. Point Drizzle's mysql2 driver at the connection string from the [Connections](/docs/products/databases/mysql/connections) page and use Drizzle Kit, the query builder, and the rest of the toolchain as you would against any MySQL server.

**Before you start**

You'll need a native MySQL database in a `ready` state and its credentials. See [native MySQL databases](/docs/products/databases/mysql) to create one and [Connections](/docs/products/databases/mysql/connections) to retrieve the connection string. The username is `admin.<hash>`, and the database name is `default`.

### Set the connection string

Fetch the connection details with [`mysql.get()`](/docs/products/databases/mysql/connections#credentials), then put the returned connection string in your environment. Never commit it:

```env
DATABASE_URL="mysql://admin.<hash>:<password>@db-<hash>.<region>.appwrite.center:3306/default"
```

Connections on Appwrite Cloud are encrypted with TLS. If your runtime or mysql2 configuration does not infer TLS from the connection string, configure mysql2 to require TLS. See [Network security](/docs/products/databases/mysql/network-security) for TLS and network controls.

### Install and configure the driver

Drizzle talks to MySQL through `mysql2`:

```bash
npm install drizzle-orm mysql2
npm install -D drizzle-kit
```

Import `drizzle` from `drizzle-orm/mysql2` and hand it the connection string:

```ts
import { drizzle } from 'drizzle-orm/mysql2';

export const db = drizzle(process.env.DATABASE_URL!);
```

Define your tables in a schema file Drizzle Kit can read. Use the MySQL column builders from `drizzle-orm/mysql-core`:

```ts
import { int, mysqlTable, timestamp, varchar } from 'drizzle-orm/mysql-core';

export const users = mysqlTable('users', {
  id: int('id').primaryKey().autoincrement(),
  email: varchar('email', { length: 255 }).notNull().unique(),
  createdAt: timestamp('created_at').notNull().defaultNow()
});
```

### Configure Drizzle Kit

Drizzle Kit reads `drizzle.config.ts` for migrations and introspection. Set the `dialect`, point `schema` at your table definitions, and pass the connection string through `dbCredentials`:

```ts
import { defineConfig } from 'drizzle-kit';

export default defineConfig({
  dialect: 'mysql',
  schema: './src/schema.ts',
  out: './drizzle',
  dbCredentials: {
    url: process.env.DATABASE_URL!
  }
});
```

### Run migrations

Generate SQL migration files from your schema, then apply them:

```bash
npx drizzle-kit generate
npx drizzle-kit migrate
```

`generate` diffs your schema against the last snapshot and writes a timestamped `.sql` file into the `out` directory. `migrate` applies any pending files to the database. Point Drizzle Kit at the direct engine port (`3306`), not the pooler, because migrations issue DDL that needs a session-level connection. The `admin.<hash>` user owns the `default` database and can run schema changes.

For prototypes or preview databases where you do not need committed SQL migration files, Drizzle Kit can push the current schema directly:

```bash
npx drizzle-kit push
```

To apply migrations from your application at startup instead of the CLI, use the mysql2 migrator:

```ts
import { migrate } from 'drizzle-orm/mysql2/migrator';
import { db } from './db';

await migrate(db, { migrationsFolder: './drizzle' });
```

### Query with Drizzle

Once the schema is migrated, use the query builder. The mysql2 dialect returns an insert result instead of the inserted row, so read the row back with the inserted ID:

```ts
import { desc, eq } from 'drizzle-orm';
import { db } from './db';
import { users } from './schema';

const result = await db.insert(users).values({ email: 'ada@example.com' });

const [user] = await db.select().from(users).where(eq(users.id, Number(result[0].insertId)));

const recent = await db
  .select()
  .from(users)
  .orderBy(desc(users.createdAt))
  .limit(10);
```

On long-running servers, create the `db` instance once at module scope and reuse it. On serverless, keep a single instance per module scope so warm invocations reuse it, and use the pooler for runtime traffic.

### Pool connections from serverless

Each running instance opens its own connections to the engine. On serverless and edge platforms such as Vercel, Netlify, and Cloudflare, short-lived instances can fan out into more backend connections than the engine allows. Route runtime traffic through the [connection pooler](/docs/products/databases/mysql/connection-pooling) by connecting on port `6033` on the same hostname.

Keep `drizzle.config.ts` and the startup migrator pointed at `DIRECT_URL` so DDL still runs over a session-level connection:

```env
### Runtime: pooler port
DATABASE_URL="mysql://admin.<hash>:<password>@db-<hash>.<region>.appwrite.center:6033/default"

### Migrations & introspection: direct connection to the engine
DIRECT_URL="mysql://admin.<hash>:<password>@db-<hash>.<region>.appwrite.center:3306/default"
```

The Drizzle Kit config then reads the direct URL:

```ts
import { defineConfig } from 'drizzle-kit';

export default defineConfig({
  dialect: 'mysql',
  schema: './src/schema.ts',
  out: './drizzle',
  dbCredentials: {
    url: process.env.DIRECT_URL!
  }
});
```

The pooler defaults to transaction mode, which does not keep a backend connection across statements. Drizzle's regular mysql2 query builder sends text queries and works with transaction pooling, but session-level features such as explicit prepared queries, user variables, and temporary tables need the direct port or a session-mode pooler. See the [pooler modes](/docs/products/databases/mysql/connection-pooling#modes) page for the trade-offs.

### Use a branch for previews and CI

[Branches](/docs/products/databases/mysql/branches) are isolated copies of a database with their own hostname and connection string. They're ideal for running migrations against throwaway data in a pull-request preview or an integration-test job:

1. Create a branch from the API and read its `connectionString`.
2. Export it as `DIRECT_URL` and `DATABASE_URL` for the preview or CI job.
3. Run `drizzle-kit migrate` or `drizzle-kit push`, then run your test suite against the branch.
4. Delete the branch when the job finishes.

Because a branch starts from a storage snapshot, the schema and data match the source database at branch time, so migrations run against representative data without touching production.

### Related

- [Connections](/docs/products/databases/mysql/connections): Retrieve credentials and rotate the primary password.
- [Connection pooler](/docs/products/databases/mysql/connection-pooling): Pool modes, ports, and read/write splitting for serverless workloads.
- [Branches](/docs/products/databases/mysql/branches): Ephemeral database copies for preview environments and CI.
- [Network security](/docs/products/databases/mysql/network-security): TLS and network controls for native MySQL databases.

---

## EF Core

URL: https://appwrite.io/docs/products/databases/mysql/integrations/ef-core

A native MySQL database is a standard MySQL engine, so [Entity Framework Core](https://learn.microsoft.com/ef/core/) works with it through a MySQL EF Core provider. Point the provider at the connection details from the [Connections](/docs/products/databases/mysql/connections) page, then use `DbContext`, migrations, and LINQ queries as you would with any MySQL server.

**Before you start**

You'll need a native MySQL database in a `ready` state and its credentials. See [native MySQL databases](/docs/products/databases/mysql) to create one and [Connections](/docs/products/databases/mysql/connections) to retrieve the connection details.

### Install the provider

Add Oracle's EF Core provider for MySQL, the EF Core design-time package, and the `dotnet-ef` CLI tool:

```bash
dotnet add package MySql.EntityFrameworkCore
dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet new tool-manifest
dotnet tool install dotnet-ef
```

Keep the MySQL provider and EF Core design package on the same major version as your app's EF Core packages.

### Set the connection string

ADO.NET providers use key/value connection strings rather than a URL. Store the connection string in `appsettings.json` under `ConnectionStrings`, and keep the password out of source control with [user secrets](https://learn.microsoft.com/aspnet/core/security/app-secrets) or environment variables:

```json
{
  "ConnectionStrings": {
    "Default": "Server=db-<hash>.<region>.appwrite.center;Port=3306;Database=default;User=admin.<hash>;Password=<password>;SslMode=Required"
  }
}
```

The Appwrite MySQL host accepts TLS connections on port `3306`. `SslMode=Required` encrypts the connection. For full certificate and hostname verification, use `SslMode=VerifyFull`:

```text
Server=db-<hash>.<region>.appwrite.center;Port=3306;Database=default;User=admin.<hash>;Password=<password>;SslMode=VerifyFull
```

### Configure the DbContext

Define your model and `DbContext`. The model is ordinary EF Core code, and the table name in this example is prefixed so it is easy to identify the objects created by the guide:

```csharp
using Microsoft.EntityFrameworkCore;

public class User
{
    public int Id { get; set; }
    public string Email { get; set; } = string.Empty;
    public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}

public class AppDbContext : DbContext
{
    public AppDbContext(DbContextOptions<AppDbContext> options)
        : base(options)
    {
    }

    public DbSet<User> Users => Set<User>();

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<User>(entity =>
        {
            entity.ToTable("ef_core_users");
            entity.HasIndex(user => user.Email).IsUnique();
            entity.Property(user => user.Email).HasMaxLength(255).IsRequired();
            entity.Property(user => user.CreatedAt).HasDefaultValueSql("CURRENT_TIMESTAMP(6)");
        });
    }
}
```

### Register the provider

Register the `DbContext` in `Program.cs`, reading the connection string with `GetConnectionString`. Use `UseMySQL(...)` for Oracle's MySQL EF Core provider:

```csharp
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDbContext<AppDbContext>(options =>
    options.UseMySQL(
        builder.Configuration.GetConnectionString("Default")!,
        mySqlOptions => mySqlOptions.MigrationsHistoryTable("ef_core_migrations_history")));
```

The `MigrationsHistoryTable` option keeps EF Core's migration tracking table prefixed with the rest of this guide's example objects.

### Run migrations

Migrations need a session connection and full DDL privileges, so run them against the direct MySQL host on port `3306`, not the [pooler](/docs/products/databases/mysql/connection-pooling). The `admin.<hash>` user owns the database.

Create the first migration, then apply it:

```bash
dotnet tool run dotnet-ef migrations add InitialCreate
dotnet tool run dotnet-ef database update
```

`dotnet tool run dotnet-ef database update` reads the same `Default` connection string and applies any pending migrations. EF Core records applied migrations in the configured migration history table so it only applies new migrations next time.

### Wire up an ASP.NET server

The route handlers use ordinary EF Core queries:

```csharp
var app = builder.Build();

app.MapGet("/users", async (AppDbContext db) =>
    await db.Users.OrderByDescending(user => user.CreatedAt).Take(10).ToListAsync());

app.MapPost("/users", async (AppDbContext db, CreateUser request) =>
{
    var user = new User { Email = request.Email };
    db.Users.Add(user);
    await db.SaveChangesAsync();

    return Results.Created($"/users/{user.Id}", user);
});

app.Run();

public record CreateUser(string Email);
```

### Connection pooling

Connector/NET has connection pooling enabled by default. Each process keeps its own pool keyed on the connection string, so a long-running ASP.NET server usually connects directly to MySQL and tunes the provider pool with connection string options:

```text
Server=db-<hash>.<region>.appwrite.center;Port=3306;Database=default;User=admin.<hash>;Password=<password>;SslMode=Required;Max Pool Size=50
```

Keep the sum across all your processes under the engine's connection limit for the database's specification.

If you run many short-lived instances that each open their own provider pool, route runtime traffic through the Appwrite [connection pooler](/docs/products/databases/mysql/connection-pooling) on port `6033`:

```text
Server=db-<hash>.<region>.appwrite.center;Port=6033;Database=default;User=admin.<hash>;Password=<password>;SslMode=Required;Max Pool Size=20
```

Keep migrations and administrative jobs on the direct port `3306`. The pooler defaults to **transaction mode**, which does not hold a backend connection across statements. If your application relies on session state such as server-side prepared statements, user variables, or temporary tables, switch the pooler to **session mode**, see the [pooler](/docs/products/databases/mysql/connection-pooling#modes) page for the trade-offs.

### Use a branch for previews and CI

Database [branches](/docs/products/databases/mysql/branches) are isolated copies of a database with their own hostname and credentials. They're useful for running migrations against throwaway data in a pull-request preview or integration-test job:

1. Create a branch from the API and read its connection details.
2. Build the connection string for the branch host and pass it as the `Default` connection string.
3. Run `dotnet tool run dotnet-ef database update` and your test suite against the branch.
4. Delete the branch when the job finishes.

Because a branch starts from a storage snapshot, the schema and data match the source database at branch time, so migrations run against representative data without touching the source database.

### Related

- [Connect](/docs/products/databases/mysql/connections): Retrieve credentials, rotate the password, and create scoped connection users.
- [Connection pooler](/docs/products/databases/mysql/connection-pooling): Pool modes, ports, and read/write splitting for serverless workloads.
- [Branches](/docs/products/databases/mysql/branches): Ephemeral database copies for preview environments and CI.
- [Network](/docs/products/databases/mysql/network-security): TLS, certificate verification, and IP allowlists.

---

## FastAPI

URL: https://appwrite.io/docs/products/databases/mysql/integrations/fastapi

An Appwrite native MySQL database is a standard MySQL engine, so [FastAPI](https://fastapi.tiangolo.com/) with [SQLAlchemy](https://docs.sqlalchemy.org/) and an async MySQL driver works against it with no Appwrite-specific runtime code. You point `create_async_engine` at the connection string from the [connections](/docs/products/databases/mysql/connections) page and use the SQLAlchemy ORM, the FastAPI dependency system, and Alembic the same way you would against any self-hosted MySQL server.

**Before you start**

You'll need a native MySQL database in a `ready` state and its credentials. See [native MySQL databases](/docs/products/databases/mysql) to create one and [connections](/docs/products/databases/mysql/connections) to retrieve the connection string. The username is `admin.<hash>`, and the database name is `default`.

### Install dependencies

```bash
pip install "fastapi[standard]" "sqlalchemy[asyncio]" asyncmy alembic
```

### Set the connection string

Fetch the connection string with the [API](/docs/products/databases/mysql/connections#credentials). Put it in your environment, never commit it. SQLAlchemy's asyncmy dialect uses the `mysql+asyncmy://` scheme, so swap the leading `mysql://` for it and add `charset=utf8mb4`:

```env
DATABASE_URL="mysql+asyncmy://admin.<hash>:<password>@db-<hash>.<region>.appwrite.center:3306/default?charset=utf8mb4"
DATABASE_SSL=true
```

This guide uses the [asyncmy](https://github.com/long2ice/asyncmy) driver. SQLAlchemy also supports the async `mysql+aiomysql://` dialect, but the snippets below use `mysql+asyncmy://`.

### Create the async engine

Cloud connections require TLS. Build an `ssl.SSLContext` and pass it through `connect_args`; use `DATABASE_SSL=false` only for a local MySQL server that does not offer TLS:

```python
import ssl

from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine

connect_args = (
    {"ssl": ssl.create_default_context()}
    if settings.database_ssl
    else {}
)

engine = create_async_engine(
    settings.database_url,
    connect_args=connect_args,
    pool_size=10,
    max_overflow=5,
    pool_pre_ping=True,
)

SessionLocal = async_sessionmaker(engine, expire_on_commit=False)
```

The server certificate is signed by a well-known public CA, so `ssl.create_default_context()` validates the certificate chain against the system trust store. See the [Network](/docs/products/databases/mysql/network-security) page for TLS and network access controls.

### Define a model

```python
from datetime import datetime

from sqlalchemy import String, func
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column

class Base(DeclarativeBase):
    pass

class User(Base):
    __tablename__ = "fastapi_users"

    id: Mapped[int] = mapped_column(primary_key=True)
    email: Mapped[str] = mapped_column(String(255), unique=True)
    created_at: Mapped[datetime] = mapped_column(server_default=func.now())
```

SQLAlchemy renders the integer primary key as `AUTO_INCREMENT` for MySQL. The explicit `String(255)` keeps the unique email index within MySQL's indexed column limits.

### Inject a session per request

FastAPI's dependency system gives each request its own `AsyncSession` and closes it when the request finishes. Define a dependency that yields a session, then annotate path operations with it:

```python
from typing import Annotated

from fastapi import Depends, FastAPI
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

app = FastAPI()

async def get_session():
    async with SessionLocal() as session:
        yield session

SessionDep = Annotated[AsyncSession, Depends(get_session)]

@app.post("/users")
async def create_user(email: str, session: SessionDep):
    user = User(email=email)
    session.add(user)
    await session.commit()
    await session.refresh(user)
    return user

@app.get("/users")
async def list_users(session: SessionDep):
    result = await session.scalars(select(User).order_by(User.created_at.desc()))
    return result.all()
```

Create the engine once at module scope and reuse it for the whole process, the pool lives inside it. Don't open a new engine per request.

### Run migrations

Generate Alembic's async scaffold, which opens the connection asynchronously and hands a sync connection to the migration context:

```bash
alembic init -t async migrations
```

Point `target_metadata` at `Base.metadata` in `migrations/env.py`, then autogenerate and apply:

```bash
alembic revision --autogenerate -m "init"
alembic upgrade head
```

Review the generated migration before applying it. If the database contains tables that Alembic should not manage, configure Alembic's include filters or run the migration against a branch so autogenerate only emits changes for your FastAPI app.

Run migrations against the **direct** engine port (`3306`), not the pooler. DDL needs a session-level connection, and Alembic's async template already uses a `NullPool`, so a fresh connection is opened and closed per run. The `admin.<hash>` user owns the default database and can run schema changes.

### Pool sizing

A long-running `uvicorn` server holds a SQLAlchemy pool for its lifetime, so connect to the **direct** engine port (`3306`) with a sized pool. Keep `pool_size` multiplied by the number of server processes under the connection limit of your [specification](/docs/products/databases/mysql#specifications), and let `max_overflow` absorb short bursts:

```python
engine = create_async_engine(
    settings.database_url,
    connect_args=connect_args,
    pool_size=10,
    max_overflow=5,
    pool_pre_ping=True,
)
```

When the same app runs on a serverless platform (for example an Appwrite [function](/docs/products/functions)) where each invocation is a fresh instance, that can fan out into more backend connections than the engine allows. Route runtime traffic through the [connection pooler](/docs/products/databases/mysql/connection-pooling) on port `6033` instead, and avoid keeping another application pool on top of it:

```env
DATABASE_URL="mysql+asyncmy://admin.<hash>:<password>@db-<hash>.<region>.appwrite.center:6033/default?charset=utf8mb4"
DATABASE_SSL=true
```

And in the engine setup:

```python
from sqlalchemy import NullPool

engine = create_async_engine(
    settings.database_url,
    connect_args=connect_args,
    poolclass=NullPool,
    pool_pre_ping=True,
)
```

The pooler defaults to **transaction mode**, which returns the backend connection to the pool after each transaction. Use **session mode** if your application relies on session-level state such as user variables, temporary tables, or server-side prepared statements, see the [pooler](/docs/products/databases/mysql/connection-pooling#modes) page for the trade-offs.

### Use a branch for previews and CI

[Branches](/docs/products/databases/mysql/branches) are instant, isolated copies of a database with their own hostname and connection string. They're ideal for running migrations against throwaway data in a pull-request preview or an integration-test job:

1. Create a branch from the API and read its `connectionString`.
2. Rewrite the scheme to `mysql+asyncmy://`, add `charset=utf8mb4`, and export it as `DATABASE_URL`.
3. Run `alembic upgrade head` against the branch's direct port, then your test suite.
4. Delete the branch when the job finishes.

Because a branch starts from a storage snapshot, the schema and data match the source database at branch time, so migrations run against representative data without touching production.

### Related

- [Connections](/docs/products/databases/mysql/connections): Retrieve credentials and rotate the primary password.
- [Connection pooler](/docs/products/databases/mysql/connection-pooling): Pool modes, ports, and read/write splitting for serverless workloads.
- [Branches](/docs/products/databases/mysql/branches): Ephemeral database copies for preview environments and CI.
- [Network](/docs/products/databases/mysql/network-security): TLS, certificate verification, and IP allowlists.

---

## GORM

URL: https://appwrite.io/docs/products/databases/mysql/integrations/gorm

A native MySQL database is a standard MySQL engine, so [GORM](https://gorm.io/) talks to it through the regular MySQL driver. Build a go-sql-driver DSN from the credentials returned by Appwrite, hand it to `gorm.io/driver/mysql`, and use models, `AutoMigrate`, and the query API as you would against any MySQL server.

**Before you start**

You'll need a native MySQL database in a `ready` state and its credentials. See [native MySQL databases](/docs/products/databases/mysql) to create one and [Connections](/docs/products/databases/mysql/connections) to retrieve the hostname, username, and password. The username is `admin.<hash>` and the database name is `default`.

### Build the DSN

Keep the password out of source and read the connection details from the environment:

```env
DB_HOST="db-<hash>.<region>.appwrite.center"
DB_PORT="3306"
DB_NAME="default"
DB_USER="admin.<hash>"
DB_PASSWORD="<password>"
DB_TLS="true"
```

GORM's MySQL driver uses [go-sql-driver/mysql](https://github.com/go-sql-driver/mysql) DSNs. The wire format is `admin.<hash>:<password>@tcp(db-<hash>.<region>.appwrite.center:3306)/default?parseTime=true&tls=true`. Use `mysql.Config` to format the DSN so passwords and database names are escaped correctly:

```go
cfg := mysqlcfg.Config{
    User:      "admin.<hash>",
    Passwd:    os.Getenv("DB_PASSWORD"),
    Net:       "tcp",
    Addr:      net.JoinHostPort(os.Getenv("DB_HOST"), os.Getenv("DB_PORT")),
    DBName:    os.Getenv("DB_NAME"),
    ParseTime: true,
    TLSConfig: os.Getenv("DB_TLS"),
}

dsn := cfg.FormatDSN()
```

`parseTime=true` lets the driver scan MySQL `DATE`, `DATETIME`, and `TIMESTAMP` values into Go `time.Time` values. Keep `DB_TLS=true` for Appwrite Cloud connections. Local development environments that terminate no TLS can set `DB_TLS=false`.

### Open a connection

Pass the DSN to `mysql.Open`, then call `gorm.Open`:

```go
package main

import (
    "fmt"
    "net"
    "os"
    "time"

    mysqlcfg "github.com/go-sql-driver/mysql"
    gormmysql "gorm.io/driver/mysql"
    "gorm.io/gorm"
)

type User struct {
    ID        uint      `gorm:"primaryKey"`
    Email     string    `gorm:"size:255;not null;uniqueIndex"`
    CreatedAt time.Time
}

func (User) TableName() string {
    return "gorm_users"
}

func main() {
    cfg := mysqlcfg.Config{
        User:      "admin.<hash>",
        Passwd:    os.Getenv("DB_PASSWORD"),
        Net:       "tcp",
        Addr:      net.JoinHostPort(os.Getenv("DB_HOST"), os.Getenv("DB_PORT")),
        DBName:    os.Getenv("DB_NAME"),
        ParseTime: true,
        TLSConfig: os.Getenv("DB_TLS"),
    }

    db, err := gorm.Open(gormmysql.Open(cfg.FormatDSN()), &gorm.Config{})
    if err != nil {
        panic(err)
    }

    if err := db.AutoMigrate(&User{}); err != nil {
        panic(err)
    }

    user := User{Email: fmt.Sprintf("ada+%d@example.com", time.Now().UnixNano())}
    if err := db.Create(&user).Error; err != nil {
        panic(err)
    }

    var saved User
    if err := db.First(&saved, "email = ?", user.Email).Error; err != nil {
        panic(err)
    }

    fmt.Println(saved.ID, saved.Email)
}
```

Connect to the direct engine port (`3306`) for a long-running server and cap the underlying pool yourself, see [pool sizing](#pool) below. If you route runtime traffic through the [connection pooler](/docs/products/databases/mysql/connection-pooling) on port `6033`, enable `InterpolateParams` in the driver config so placeholder values are sent as one text query in transaction mode:

```go
cfg := mysqlcfg.Config{
    User:              "admin.<hash>",
    Passwd:            os.Getenv("DB_PASSWORD"),
    Net:               "tcp",
    Addr:              net.JoinHostPort(os.Getenv("DB_HOST"), "6033"),
    DBName:            os.Getenv("DB_NAME"),
    ParseTime:         true,
    TLSConfig:         os.Getenv("DB_TLS"),
    InterpolateParams: true,
}

db, err := gorm.Open(gormmysql.Open(cfg.FormatDSN()), &gorm.Config{})
```

### Define a model and migrate

Declare your models as Go structs and let GORM create the tables with `AutoMigrate`:

```go
type User struct {
    ID        uint      `gorm:"primaryKey"`
    Email     string    `gorm:"size:255;not null;uniqueIndex"`
    CreatedAt time.Time
}

func (User) TableName() string {
    return "gorm_users"
}

if err := db.AutoMigrate(&User{}); err != nil {
    panic(err)
}
```

`AutoMigrate` creates the table if it's missing and adds any missing columns and indexes. It does not drop columns or change existing column types, so it's convenient in development but not a substitute for versioned migrations in production. Run schema changes against the direct engine port because DDL needs a session-level connection. The `admin.<hash>` user owns the `default` database and can run schema changes.

For versioned migrations, [golang-migrate](https://github.com/golang-migrate/migrate) runs ordered up/down files. Point it at the direct engine port:

```bash
migrate -path ./migrations \
  -database "mysql://$DB_USER:$DB_PASSWORD@tcp($DB_HOST:$DB_PORT)/$DB_NAME?tls=$DB_TLS&x-migrations-table=gorm_schema_migrations" \
  up
```

### Size the connection pool

GORM manages a `database/sql` pool under the hood. A long-running Go server holds that pool for its whole lifetime, so connect to the direct engine port and cap the pool against the engine's connection limit. Reach the underlying `*sql.DB` with `db.DB()`:

```go
sqlDB, err := db.DB()
if err != nil {
    panic(err)
}

sqlDB.SetMaxOpenConns(25)
sqlDB.SetMaxIdleConns(25)
sqlDB.SetConnMaxLifetime(time.Hour)
```

Keep the sum of `SetMaxOpenConns` across every instance below the specification's `maxConnections`. If you run many instances or a serverless runtime that opens a fresh pool per cold start, route runtime traffic through the [connection pooler](/docs/products/databases/mysql/connection-pooling) and keep each instance's pool small. The pooler multiplexes them onto a smaller set of backend connections.

### Run SQL with GORM

Use GORM's raw SQL helpers when you need a query that is clearer as SQL than as model operations. MySQL uses `?` placeholders:

```go
type Result struct {
    Email string
}

var result Result
if err := db.Raw(
    "SELECT email FROM gorm_users WHERE email = ? LIMIT 1",
    user.Email,
).Scan(&result).Error; err != nil {
    panic(err)
}
```

The same connection rules apply: use the direct port for migrations and session-level features, and use the pooler for high fan-out runtime queries.

### Use a branch for previews and CI

[Branches](/docs/products/databases/mysql/branches) are isolated copies of a database with their own hostname and connection string. They're ideal for running migrations against throwaway data in a pull-request preview or an integration-test job:

1. Create a branch from the API and read its `connectionString`.
2. Export the branch host, username, and password into the environment your tests read.
3. Run `migrate ... up` or `AutoMigrate`, then run your test suite against the branch's direct port.
4. Delete the branch when the job finishes.

Because a branch starts from a storage snapshot, the schema and data match the source database at branch time, so migrations run against representative data without touching production.

### Related

- [Connections](/docs/products/databases/mysql/connections): Retrieve credentials and rotate the primary password.
- [Connection pooler](/docs/products/databases/mysql/connection-pooling): Pool modes, ports, and read/write splitting for serverless workloads.
- [Branches](/docs/products/databases/mysql/branches): Ephemeral database copies for preview environments and CI.
- [Network security](/docs/products/databases/mysql/network-security): TLS and network controls for native MySQL databases.

---

## Grafana

URL: https://appwrite.io/docs/products/databases/mysql/integrations/grafana

An Appwrite [native MySQL database](/docs/products/databases/mysql) exposes a standard managed MySQL 8.4 or 8.0 engine, so [Grafana](https://grafana.com/) connects to it through the built-in **MySQL data source** with no Appwrite-specific configuration. Point the data source at your database hostname, authenticate with your database credentials, and query your tables to build dashboards and alerts.

**Before you start**

You'll need a native MySQL database in a `ready` state and its credentials. Call `mysql.get()` from the Appwrite API to read `hostname`, `connectionPort`, `connectionUser`, `connectionPassword`, and `connectionString`. The username is `admin.<hash>`, and the database name is `default`. See [Connections](/docs/products/databases/mysql/connections) to retrieve the values.

### Protect dashboard credentials

Grafana can execute any SQL allowed by the data source user. Keep panel queries read-only, avoid saving write statements in dashboards, and restrict network access to trusted Grafana hosts with an [IP allowlist](/docs/products/databases/mysql/network-security#ip-allowlist).

### Choose a connection target

Grafana holds its data source connections open for the lifetime of the process. Long-lived connections should use either the direct MySQL port `3306` or a [connection pooler](/docs/products/databases/mysql/connection-pooling) on port `6033` running in **session mode**.

Do not point Grafana at the **transaction-mode** pooler. Transaction mode hands a backend connection back to the pool after every statement, which breaks the session assumptions Grafana relies on for connection reuse and prepared statements. For a typical dashboard workload the direct MySQL port is the simplest choice. See the [pooler modes](/docs/products/databases/mysql/connection-pooling#modes) page for the trade-offs.

### Add the MySQL data source

In Grafana, open **Connections** > **Data sources** > **Add data source** and select **MySQL**. Fill in the connection details using the values from your native MySQL database:

| Field | Value |
| --- | --- |
| **Host URL** | `db-<hash>.<region>.appwrite.center:3306` |
| **Database** | `default` |
| **Username** | `admin.<hash>` |
| **Password** | `<password>` |

Regions are `fra`, `nyc`, `sfo`, `sgp`, `syd`, and `tor`. Appwrite Cloud encrypts connections to the public hostname with TLS. Grafana's MySQL data source uses MySQL-specific TLS fields such as `tlsAuthWithCACert`, `tlsSkipVerify`, and TLS certificate values in `secureJsonData`.

If you use Grafana Cloud and restrict database access with an IP allowlist, add the Grafana Cloud outbound IP ranges for your stack to the database allowlist. Grafana Cloud can reach the public Appwrite database hostname directly. Private connectivity features are only needed when the database is on a private network.

Under **Connection limits**, keep the connection counts modest so Grafana doesn't exhaust the engine's connection budget. **Max open** caps total connections from this Grafana instance, **Max idle** caps pooled idle connections, and **Max lifetime** recycles connections after the given number of seconds. Select **Save & test** to verify connectivity.

### Provision from YAML

Instead of configuring the data source by hand, you can [provision](https://grafana.com/docs/grafana/latest/administration/provisioning/) it declaratively. Drop a file into Grafana's `provisioning/datasources/` directory and read the password from an environment variable so it never lands in source control:

```yaml
apiVersion: 1

datasources:
  - name: Appwrite native MySQL
    type: mysql
    url: db-<hash>.<region>.appwrite.center:3306
    user: admin.<hash>
    jsonData:
      database: default
      maxOpenConns: 5
      maxIdleConns: 2
      maxIdleConnsAuto: true
      connMaxLifetime: 14400
    secureJsonData:
      password: $GRAFANA_DB_PASSWORD
    editable: false
```

The MySQL data source uses `type: mysql`, keeps the database name, `default`, under `jsonData.database`, and reads the password from `secureJsonData.password`. Grafana expands `$GRAFANA_DB_PASSWORD` from the process environment when it loads the provisioning file. Add Grafana's MySQL TLS fields if your deployment requires explicit TLS configuration. See the [MySQL data source](https://grafana.com/docs/grafana/latest/datasources/mysql/configure/) docs for every available field.

### Build a panel

With the data source connected, create a dashboard and add a panel backed by it. Switch the query editor to code mode and write a read-only query against your tables. For example, to plot daily sign-ups from a `grafana_users` table over time:

```text
SELECT
  CAST(DATE(created_at) AS DATETIME) AS time,
  COUNT(*) AS signups
FROM grafana_users
GROUP BY CAST(DATE(created_at) AS DATETIME)
ORDER BY time;
```

Grafana maps the `time` column to the panel's time axis and `signups` to the value. MySQL 8.4 enables `ONLY_FULL_GROUP_BY`, so the selected time expression must match the grouped expression.

### Related

- [MySQL databases](/docs/products/databases/mysql): Overview of native MySQL database engines, versions, and regions.
- [Connect](/docs/products/databases/mysql/connections): Retrieve credentials and rotate the primary password.
- [Connection pooler](/docs/products/databases/mysql/connection-pooling): Pool modes and ports. Use the direct MySQL port or session mode for Grafana.
- [Network](/docs/products/databases/mysql/network-security): TLS modes, certificate verification, and IP allowlists.

[Connect to a native MySQL database](/docs/products/databases/mysql/connections)

---

## Laravel

URL: https://appwrite.io/docs/products/databases/mysql/integrations/laravel

A native MySQL database is a standard MySQL engine, so [Laravel](https://laravel.com/docs) works against it with no Appwrite-specific configuration. Point the `mysql` connection in `config/database.php` at the credentials from the [Connections](/docs/products/databases/mysql/connections) page, then use Eloquent, the query builder, migrations, and queues as you would against any MySQL server.

**Before you start**

You'll need a native MySQL database in a `ready` state and its credentials. See [MySQL databases](/docs/products/databases/mysql) to create one. To retrieve credentials, call [`mysql.get()`](/docs/products/databases/mysql/connections#credentials) and use the returned hostname, port, username, and password. The username is `admin.<hash>`, and the database name is `default`.

### Configure the connection

Laravel reads database credentials from `.env`. Fetch them with [`mysql.get()`](/docs/products/databases/mysql/connections#credentials), then set the matching connection. Never commit `.env`:

```env
DB_CONNECTION=mysql
DB_HOST=db-<hash>.<region>.appwrite.center
DB_PORT=3306
DB_DATABASE=default
DB_USERNAME=admin.<hash>
DB_PASSWORD=<password>
DB_CHARSET=utf8mb4
DB_COLLATION=utf8mb4_unicode_ci
```

The scaffolded `config/database.php` wires these variables into the `mysql` connection:

```php
'mysql' => [
    'driver' => 'mysql',
    'url' => env('DB_URL'),
    'host' => env('DB_HOST', '127.0.0.1'),
    'port' => env('DB_PORT', '3306'),
    'database' => env('DB_DATABASE', 'laravel'),
    'username' => env('DB_USERNAME', 'root'),
    'password' => env('DB_PASSWORD', ''),
    'unix_socket' => env('DB_SOCKET', ''),
    'charset' => env('DB_CHARSET', 'utf8mb4'),
    'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'),
    'prefix' => '',
    'prefix_indexes' => true,
    'strict' => true,
    'engine' => null,
    'options' => extension_loaded('pdo_mysql') ? array_filter([
        Mysql::ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
    ]) : [],
],
```

Laravel imports `Pdo\Mysql` at the top of this file in new projects. Appwrite Cloud requires TLS on the public hostname. If your PHP runtime needs an explicit CA bundle for MySQL TLS verification, set `MYSQL_ATTR_SSL_CA` to a trusted root store path and keep the `options` entry in the `mysql` connection:

```php
'options' => extension_loaded('pdo_mysql') ? array_filter([
    Mysql::ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
]) : [],
```

### Run migrations

Define your schema with a migration:

```php
Schema::create('posts', function (Blueprint $table) {
    $table->id();
    $table->string('title');
    $table->text('body');
    $table->timestamps();
});
```

Apply migrations from your machine or a deploy step:

```bash
php artisan migrate

### non-interactive, for CI and production deploys
php artisan migrate --force
```

Run `migrate` against the **direct** MySQL port (`3306`), not the pooler. Migrations issue DDL and schema inspection queries that should use a stable backend connection. The `admin.<hash>` user owns the database and can run schema changes.

### Query with Eloquent

Once the schema is migrated, use Eloquent models and the query builder as usual:

```php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    protected $fillable = [
        'title',
        'body',
    ];
}
```

Create and query posts through the model:

```php
use App\Models\Post;

$post = Post::create([
    'title' => 'Hello from a native MySQL database',
    'body' => 'Stored in a native MySQL database.',
]);

$recent = Post::query()
    ->orderByDesc('created_at')
    ->limit(10)
    ->get();
```

Nothing about the native MySQL database changes how Eloquent, relationships, transactions, or the query builder behave. It is a standard MySQL server behind a TLS connection.

### Pool connections from serverless

The right port depends on how your app runs.

A **long-running** PHP process, traditional PHP-FPM with persistent connections, [Laravel Octane](https://laravel.com/docs/octane), or a queue worker, holds its own backend connection for its lifetime. Point these at the **direct** MySQL port (`3306`), or at the [connection pooler](/docs/products/databases/mysql/connection-pooling) in **session mode**. Don't put a long-lived process behind the transaction-mode pooler.

A **serverless** or per-request deployment (Vercel, AWS Lambda, Cloud Run) opens a fresh connection on every invocation and can fan out into more backend connections than the engine allows. Route runtime traffic through the pooler's **transaction-mode** port (`6033`) on the same hostname, and keep a named direct connection for migrations and other schema operations:

```env
### Runtime: pooled, transaction mode
DB_HOST=db-<hash>.<region>.appwrite.center
DB_PORT=6033
DB_DATABASE=default
DB_USERNAME=admin.<hash>
DB_PASSWORD=<password>
DB_CHARSET=utf8mb4
DB_COLLATION=utf8mb4_unicode_ci

### Migrations and schema operations: direct MySQL port
DB_DIRECT_HOST=db-<hash>.<region>.appwrite.center
DB_DIRECT_PORT=3306
DB_DIRECT_DATABASE=default
DB_DIRECT_USERNAME=admin.<hash>
DB_DIRECT_PASSWORD=<password>
```

Add a second MySQL connection in `config/database.php` for direct schema work:

```php
'mysql_direct' => [
    'driver' => 'mysql',
    'url' => env('DB_DIRECT_URL'),
    'host' => env('DB_DIRECT_HOST', env('DB_HOST', '127.0.0.1')),
    'port' => env('DB_DIRECT_PORT', env('DB_PORT', '3306')),
    'database' => env('DB_DIRECT_DATABASE', env('DB_DATABASE', 'laravel')),
    'username' => env('DB_DIRECT_USERNAME', env('DB_USERNAME', 'root')),
    'password' => env('DB_DIRECT_PASSWORD', env('DB_PASSWORD', '')),
    'unix_socket' => env('DB_SOCKET', ''),
    'charset' => env('DB_CHARSET', 'utf8mb4'),
    'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'),
    'prefix' => '',
    'prefix_indexes' => true,
    'strict' => true,
    'engine' => null,
    'options' => extension_loaded('pdo_mysql') ? array_filter([
        Mysql::ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
    ]) : [],
],
```

Run schema commands against that connection:

```bash
php artisan migrate --database=mysql_direct --force
```

For manual schema work, call `DB::connection('mysql_direct')`. The transaction-mode pooler does not keep session state across transactions, so user variables, temporary tables, and server-side prepared statements should use the direct port or **session mode**. See the [pooler](/docs/products/databases/mysql/connection-pooling#modes) page for the trade-offs.

### Queues and Horizon

A queue worker is a long-running process. `php artisan queue:work` boots once and processes jobs for its whole lifetime, holding a persistent database connection the entire time. The same applies to every worker that [Laravel Horizon](https://laravel.com/docs/horizon) supervises. Treat workers like any other long-lived process:

- Connect them to the **direct** MySQL port (`3306`) or the **session-mode** pooler, never the transaction-mode pooler.
- Restart workers periodically with `--max-time` or `--max-jobs` so a fresh process reclaims memory and reopens its connection. Supervisor or Horizon restarts them automatically.

```bash
php artisan queue:work --max-time=3600 --max-jobs=500
```

Each worker counts as one backend connection, so size your worker pool (and Horizon's `maxProcesses`) against the connection budget of your [specification](/docs/products/databases/mysql). Horizon itself requires Redis for the queue backend; only your application's data connection touches the native MySQL database.

### Use a branch for previews and CI

MySQL [branches](/docs/products/databases/mysql/branches) are instant, isolated copies of a database with their own hostname. Create them from the API for running migrations against throwaway data in a pull-request preview or an integration-test job:

1. Create a branch from the API and read its connection details.
2. Export them as the `DB_*` variables for the job.
3. Run `php artisan migrate --force` and your test suite against the branch.
4. Delete the branch when the job finishes.

Because a branch starts from a storage snapshot, the schema and data match the source database at branch time, so migrations and tests run against realistic data without touching production.

### Related

- [Connect](/docs/products/databases/mysql/connections): Retrieve credentials, rotate the password, and create scoped connection users.
- [Connection pooler](/docs/products/databases/mysql/connection-pooling): Pool modes, ports, and read/write splitting for serverless workloads.
- [Branches](/docs/products/databases/mysql/branches): Ephemeral database copies for preview environments and CI.
- [Network](/docs/products/databases/mysql/network-security): TLS, certificate verification, and IP allowlists.

---

## Metabase

URL: https://appwrite.io/docs/products/databases/mysql/integrations/metabase

Appwrite's native MySQL database is a standard MySQL engine, so [Metabase](https://www.metabase.com/) can connect to it without an Appwrite-specific adapter. Add the database in Metabase, use the host and credentials from the [Connections](/docs/products/databases/mysql/connections) page, and Metabase will sync the schema so your team can build questions and dashboards.

**Before you start**

You'll need a native MySQL database in a `ready` state and its credentials. Fetch them with [`mysql.get()`](/docs/products/databases/mysql/connections#credentials). The response includes `hostname`, `connectionPort`, `connectionUser`, `connectionPassword`, and `connectionString`. The username is `admin.<hash>`, and the database name is `default`.

### Choose credentials for Metabase

Metabase connects with the `admin.<hash>` user returned by Appwrite. That account can read and write data and schema, and it cannot grant reduced privileges to other MySQL accounts, so reserve it for trusted Metabase instances and keep dashboards on read-only questions.

Metabase features such as model actions and editable table data need write privileges. Enable them only on a connection whose MySQL account has the specific write permissions that workflow needs.

### Add the database in Metabase

In Metabase, click the grid icon, then open **Admin > Databases > Add a database**. Choose **MySQL** as the database type and fill in the connection form with the values from the [Connections](/docs/products/databases/mysql/connections) page.

| Field | Value |
|-------|-------|
| Host | `db-<hash>.<region>.appwrite.center` |
| Port | `3306` |
| Database name | `default` |
| Username | `admin.<hash>` |
| Password | `<password>` |

Turn on **Use a secure connection (SSL)**. Appwrite Cloud requires TLS for native MySQL connections on the public hostname. If your Metabase deployment needs an explicit certificate chain, add it in Metabase's **SSL certificate** field. See [Network security](/docs/products/databases/mysql/network-security) for Appwrite TLS and network controls.

**Use a session-safe connection**

Metabase keeps database sessions open and can use session-level MySQL behavior while exploring data. Connect it to the direct MySQL port (`3306`). If you connect Metabase through the [connection pooler](/docs/products/databases/mysql/connection-pooling) instead, set the pooler to `session` mode. Transaction-mode pooling can break session-level features because a client session is not bound to one backend connection.

Click **Save changes**. Metabase verifies the connection and starts its first schema sync.

### Build a question or dashboard

Once the first sync finishes, your tables appear in the data picker. To build your first chart:

1. Click **+ New > Question** and pick your Appwrite MySQL database as the data source.
2. Choose a table, add a summary such as count, sum, or average, and group by a column such as a timestamp.
3. Switch the visualization to a line, bar, or table view, then save the question.
4. Add saved questions to a dashboard and use filters to slice data across cards.

Use read-only native SQL questions for analytics dashboards. Use Metabase permissions to limit who can run native SQL or enable write-capable features.

### How Metabase syncs your schema

After you connect, Metabase scans the database to discover tables, columns, constraints, and field metadata, then keeps that metadata current on a schedule:

- A lightweight schema sync runs hourly by default.
- A more intensive field-value scan runs daily by default to populate filter dropdowns.

New tables and columns appear after the next sync. To pull them in immediately, open **Admin > Databases > your database** and click **Sync database schema**. You can also restrict which schemas Metabase tracks and adjust the sync and scan cadence from the database settings. Refer to the [Metabase documentation](https://www.metabase.com/docs/latest/databases/sync-scan) for the full set of sync and scan options.

### Use a branch for testing

[Branches](/docs/products/databases/mysql/branches) are isolated copies of a native MySQL database with their own host and credentials. Point a second Metabase database connection at a branch when you want to validate a dashboard against a snapshot of production data without querying the live database. Branches are managed through the API, and you can delete the branch when testing is complete.

### Related

- [MySQL](/docs/products/databases/mysql): Provision and manage a native MySQL database for your project.
- [Connections](/docs/products/databases/mysql/connections): Retrieve connection details and rotate the primary password.
- [Connection pooling](/docs/products/databases/mysql/connection-pooling): Pool modes and ports, including why BI tools need session or direct connections.
- [Network security](/docs/products/databases/mysql/network-security): TLS, certificate verification, IP allowlists, and idle connection timeouts.

---

## Next.js

URL: https://appwrite.io/docs/products/databases/mysql/integrations/nextjs

An Appwrite native MySQL database works with standard MySQL drivers and ORMs, so a [Next.js](https://nextjs.org/) App Router application can query it from server-side code. Point your driver at the connection string from the [Connections](/docs/products/databases/mysql/connections) page and keep all database access on the server.

**Before you start**

You'll need a native MySQL database in a `ready` state and its credentials. See [MySQL](/docs/products/databases/mysql) to create one and [Connections](/docs/products/databases/mysql/connections) to retrieve the connection string. The username is `admin.<hash>`, and the database name is `default`.

### Where to connect

In the App Router, every server-side execution context runs on the **Node.js runtime by default**, and the Node.js runtime can open raw TCP sockets. That means you can use a standard database driver from any of these:

- **Route Handlers** (`app/api/.../route.ts`) for public routes, webhooks, and REST-style APIs.
- **Server Actions** (`'use server'` functions) for form submissions and app-internal mutations.
- **Server Components** (`async` components) for read queries that render straight into the page.

Never import a database driver into a Client Component (`'use client'`) or ship the connection string to the browser. Keep all database access on the server.

The one exception is the **Edge runtime** (`export const runtime = 'edge'`), which runs in a constrained environment and cannot use Node.js TCP drivers. If a route opts into Edge, use the [SQL API](/docs/products/databases/mysql/quick-start#first-queries) over HTTPS, see [the Edge runtime section](#edge) below.

### Install the driver

Install the MySQL driver for Node.js:

```bash
npm install mysql2
```

### Environment variables

Put the connection strings in your environment and never commit them. For local development, use `.env.local`, which Next.js loads automatically and the default `create-next-app` template excludes from Git:

```env
DATABASE_URL="mysql://admin.<hash>:<password>@db-<hash>.<region>.appwrite.center:6033/default"
DIRECT_URL="mysql://admin.<hash>:<password>@db-<hash>.<region>.appwrite.center:3306/default"
MYSQL_SSL="true"
```

`DATABASE_URL` points at the [connection pooler](/docs/products/databases/mysql/connection-pooling) port (`6033`) for runtime traffic, and `DIRECT_URL` points at the MySQL engine port (`3306`) for migrations and schema changes. The connection string returned by Appwrite uses the engine port, so change only the port when you connect through the pooler.

Appwrite Cloud serves MySQL over TLS. The examples below use `MYSQL_SSL="true"` to make `mysql2` request TLS and verify the server certificate with the runtime's trusted CAs. For certificate verification controls, see the [Network security](/docs/products/databases/mysql/network-security) page.

### Create a table for the examples

Run this SQL once through your migration workflow or the `mysql` client:

```sql
CREATE TABLE IF NOT EXISTS nextjs_users (
    id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
    email VARCHAR(255) NOT NULL UNIQUE,
    created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);

INSERT INTO nextjs_users (email)
VALUES ('ada@example.com')
ON DUPLICATE KEY UPDATE email = nextjs_users.email;
```

### Pool serverless connections

When you deploy to Vercel, Netlify, or another serverless platform, each invocation can spin up a fresh instance with its own connection pool. Hundreds of concurrent invocations fan out into far more backend connections than the engine allows. Route runtime traffic through the [connection pooler](/docs/products/databases/mysql/connection-pooling) on the pooler port so it can multiplex those instances over a small number of backend connections.

The pooler defaults to **transaction mode**, which does not keep a backend connection across statements, so session-level features such as server-side prepared statements and temporary tables are unavailable. Use `pool.query()` with `?` placeholders for pooled runtime queries. Reserve the engine port for migrations, schema changes, and workloads that need a full session.

### Use a singleton client

Instantiate one pool per module scope and reuse it across invocations, so warm serverless instances do not reconnect on every request. In development, Next.js hot reload re-evaluates modules, which can leak connections, so cache the pool on `globalThis`.

With [mysql2](https://sidorares.github.io/node-mysql2/):

```ts
// lib/db.ts
import mysql, { type Pool } from 'mysql2/promise';

const globalForDb = globalThis as unknown as { mysqlPool?: Pool };

function createPool() {
    const url = new URL(process.env.DATABASE_URL!);

    return mysql.createPool({
        host: url.hostname,
        port: Number(url.port || 3306),
        user: decodeURIComponent(url.username),
        password: decodeURIComponent(url.password),
        database: decodeURIComponent(url.pathname.slice(1)),
        waitForConnections: true,
        connectionLimit: 5,
        ssl: process.env.MYSQL_SSL === 'true' ? { rejectUnauthorized: true } : undefined
    });
}

export const pool = globalForDb.mysqlPool ?? createPool();

if (process.env.NODE_ENV !== 'production') globalForDb.mysqlPool = pool;
```

Query it from a Server Component or Route Handler:

```ts
// app/users/route.ts
import type { RowDataPacket } from 'mysql2';

import { pool } from '@/lib/db';

type UserRow = RowDataPacket & {
    id: number;
    email: string;
    created_at: Date;
};

export async function GET() {
    const [users] = await pool.query<UserRow[]>(
        'SELECT id, email, created_at FROM nextjs_users ORDER BY created_at DESC LIMIT ?',
        [10]
    );

    return Response.json(users);
}
```

### Use Prisma or Drizzle

For a typed schema, migrations, and a query builder, reach for an ORM. Both integrate with the pooled `DATABASE_URL` plus direct `DIRECT_URL` pattern above.

- **Prisma**: point the runtime at the pooled connection string and keep the Prisma CLI on the engine port for migrations, then run `prisma migrate deploy`. See the [Prisma](/docs/products/databases/mysql/integrations/prisma) guide for the full config and migration flow.
- **Drizzle**: use Drizzle's MySQL driver for runtime queries and the direct URL for `drizzle-kit` migrations. See the [Drizzle](/docs/products/databases/mysql/integrations/drizzle) guide.

[Set up Prisma against native MySQL](/docs/products/databases/mysql/integrations/prisma)

### Edge runtime: use the SQL API

If a Route Handler or route segment opts into the Edge runtime, it cannot use a Node.js MySQL driver:

```ts
export const runtime = 'edge';
```

From the Edge runtime, you can execute one parameterized SQL statement over HTTPS and get JSON back. `SELECT`, `INSERT`, `UPDATE`, and `DELETE` statements are allowed by default. Use the global `fetch` available in the Edge runtime:

```ts
// app/edge-users/route.ts
export const runtime = 'edge';

export async function GET() {
    const response = await fetch(
        'https://<REGION>.cloud.appwrite.io/v1/mysql/<DATABASE_ID>/executions',
        {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'X-Appwrite-Project': process.env.APPWRITE_PROJECT_ID!,
                'X-Appwrite-Key': process.env.APPWRITE_API_KEY!
            },
            body: JSON.stringify({
                sql: 'SELECT id, email FROM nextjs_users WHERE created_at > ? ORDER BY created_at DESC LIMIT ?',
                bindings: ['2026-05-01 00:00:00', 10]
            })
        }
    );

    if (!response.ok) {
        throw new Error(await response.text());
    }

    const { rows } = await response.json();
    return Response.json(rows);
}
```

The response is `{ rows, rowCount, columns, durationMs, truncated, bytes }`. Bindings are sent separately and never interpolated into the SQL string. Add `APPWRITE_PROJECT_ID` and `APPWRITE_API_KEY` to your environment alongside the database URLs. Use an API key with the `dedicatedDatabases.execute` scope.

[Read the SQL API reference](/docs/products/databases/mysql/quick-start#first-queries)

### Local development

`next dev` runs on the Node.js runtime, so a local server can connect to the native MySQL database with the same server-only driver code. Keep `DATABASE_URL`, `DIRECT_URL`, and `MYSQL_SSL` in `.env.local`.

For throwaway data in tests or experiments, create a [branch](/docs/products/databases/mysql/branches), an instant, isolated copy with its own connection string, and point `.env.local` at it. Delete the branch when you're done.

### Deploy

When you deploy, set the same `DATABASE_URL`, `DIRECT_URL`, `MYSQL_SSL`, `APPWRITE_PROJECT_ID`, and `APPWRITE_API_KEY` as environment variables on your hosting platform. Run migrations from the direct connection in your build or release step, and use the SQL API from any Edge Functions.

### Related

- [Prisma](/docs/products/databases/mysql/integrations/prisma): Datasource config, pooled and direct URLs, and the migration workflow.
- [SQL API](/docs/products/databases/mysql/quick-start#first-queries): Query over HTTPS from the Edge runtime without a TCP connection.
- [Connection pooler](/docs/products/databases/mysql/connection-pooling): Pool modes, ports, and read/write splitting for serverless workloads.

---

## Prisma

URL: https://appwrite.io/docs/products/databases/mysql/integrations/prisma

[Prisma ORM](https://www.prisma.io/) works with Appwrite's native MySQL database as a standard MySQL target. Configure Prisma with the connection string from Appwrite, apply schema changes through the direct database connection, and use Prisma Client from your application code.

**Before you start**

You'll need a native MySQL database in a `ready` state and its credentials. The database object returned by the Appwrite API includes `hostname`, `connectionUser`, `connectionPassword`, and `connectionString`; you can read it with `mysql.get()` or from the response when you create the database.

### Initialize Prisma

Install Prisma, Prisma Client, the MySQL driver adapter, the MariaDB driver used by the adapter, and the TypeScript tools used by the examples:

```bash
npm install -D prisma typescript tsx @types/node
npm install @prisma/client @prisma/adapter-mariadb dotenv mariadb
```

Initialize Prisma for MySQL:

```bash
npx prisma init --datasource-provider mysql --output ../generated/prisma
```

Prisma 7 generates a `prisma.config.ts` file and a `prisma/schema.prisma` file. Set `"type": "module"` in `package.json` if your project does not already use ECMAScript modules.

### Configure connection strings

Store the Appwrite connection string in environment variables and do not commit it:

```env
DATABASE_URL="mysql://admin.<hash>:<password>@db-<hash>.<region>.appwrite.center:3306/default?sslaccept=strict"
DIRECT_URL="mysql://admin.<hash>:<password>@db-<hash>.<region>.appwrite.center:3306/default?sslaccept=strict"
```

`DATABASE_URL` is the runtime connection used by Prisma Client. `DIRECT_URL` is the direct database connection used by Prisma CLI commands.

Appwrite Cloud uses TLS for MySQL connections. Prisma's MySQL connector uses `sslaccept=strict` for TLS with certificate verification. For network controls, see [Network security](/docs/products/databases/mysql/network-security).

Prisma's `migrate dev` command uses a shadow database to detect drift. The `admin.<hash>` user holds `CREATE` and `DROP` globally, so Prisma can create the shadow database on the direct port. If you prefer not to create extra databases next to production data, generate development migrations against a local MySQL database, a separate Appwrite database, or an Appwrite branch, then apply committed migrations to this database with `migrate deploy`.

### Configure Prisma

In `prisma.config.ts`, read the CLI connection string from the environment:

```ts
import "dotenv/config";
import { defineConfig } from "prisma/config";

export default defineConfig({
  schema: "prisma/schema.prisma",
  migrations: {
    path: "prisma/migrations",
    seed: "tsx prisma/seed.ts",
  },
  datasource: {
    url: process.env["DIRECT_URL"] ?? process.env["DATABASE_URL"],
  },
});
```

In `prisma/schema.prisma`, keep the datasource provider in the schema file and generate Prisma Client into the output directory created by `prisma init`:

```prisma
generator client {
  provider = "prisma-client"
  output   = "../generated/prisma"
}

datasource db {
  provider = "mysql"
}

model User {
  id        String   @id @default(uuid())
  email     String   @unique
  createdAt DateTime @default(now())

  @@map("prisma_User")
}
```

Rename the mapped table for your application. If you are connecting to an existing database, use Prisma introspection after you configure the environment variables so your schema matches the current tables.

### Pool connections from serverless

Prisma Client opens database connections from each running application instance. On serverless platforms, many cold starts can quickly multiply the number of backend MySQL connections. Route runtime traffic through the [connection pooler](/docs/products/databases/mysql/connection-pooling) port, `6033`, for `DATABASE_URL` while keeping `DIRECT_URL` on the direct MySQL port, `3306`, for migrations and introspection:

```env
### Runtime: pooled connection through the Appwrite connection pooler
DATABASE_URL="mysql://admin.<hash>:<password>@db-<hash>.<region>.appwrite.center:6033/default?sslaccept=strict"

### Prisma CLI: direct MySQL connection for migrations and introspection
DIRECT_URL="mysql://admin.<hash>:<password>@db-<hash>.<region>.appwrite.center:3306/default?sslaccept=strict"
```

The pooler defaults to transaction mode, which gives the highest connection multiplexing. If your application depends on session-level features such as user variables, temporary tables, or session-scoped prepared statements, use session mode or keep that workload on the direct MySQL port. See [Connection pooling](/docs/products/databases/mysql/connection-pooling#modes) for pool mode trade-offs.

### Apply schema changes

For an initial migration generated from the Prisma schema file, create a migration directory and write the SQL diff:

```bash
mkdir -p prisma/migrations/20260708160000_init
npx prisma migrate diff --from-empty --to-schema prisma/schema.prisma --script --output prisma/migrations/20260708160000_init/migration.sql
```

If this database already contains tables and Prisma has not created its migration history, baseline it once before applying migrations:

```bash
mkdir -p prisma/migrations/00000000000000_baseline
touch prisma/migrations/00000000000000_baseline/migration.sql
npx prisma migrate resolve --applied 00000000000000_baseline
```

Apply committed migrations through the direct MySQL connection:

```bash
npx prisma migrate deploy
```

Generate Prisma Client after you install dependencies or change `prisma/schema.prisma`:

```bash
npx prisma generate
```

The `admin.<hash>` user owns its assigned database and can run schema changes inside that database. Keep migrations and introspection on the direct MySQL port because they need session-level behavior.

### Seed data

With the seed command configured in `prisma.config.ts`, add a seed script:

```ts
import "dotenv/config";
import { PrismaMariaDb } from "@prisma/adapter-mariadb";
import { PrismaClient } from "../generated/prisma/client";

const adapter = new PrismaMariaDb(process.env.DATABASE_URL!);
const prisma = new PrismaClient({ adapter });

await prisma.user.upsert({
  where: { email: "ada@example.com" },
  update: {},
  create: { email: "ada@example.com" },
});

await prisma.$disconnect();
```

Run the seed command:

```bash
npx prisma db seed
```

### Query with Prisma Client

Instantiate Prisma Client with the MySQL driver adapter:

```ts
import "dotenv/config";
import { PrismaMariaDb } from "@prisma/adapter-mariadb";
import { PrismaClient } from "./generated/prisma/client";

const adapter = new PrismaMariaDb(process.env.DATABASE_URL!);
const prisma = new PrismaClient({ adapter });

const user = await prisma.user.create({
  data: { email: "grace@example.com" },
});

const recent = await prisma.user.findMany({
  orderBy: { createdAt: "desc" },
  take: 10,
});

console.log({ user, recent });

await prisma.$disconnect();
```

On long-running servers, instantiate `PrismaClient` once and reuse it. On serverless platforms, keep a single client per module scope so warm invocations reuse it, and rely on the pooler to absorb cold-start connection churn.

### Use a branch for previews and CI

[Branches](/docs/products/databases/mysql/branches) are instant, isolated copies of a database with their own hostname and connection string. They are useful for running migrations against throwaway data in a pull-request preview or integration-test job:

1. Create a branch from the API and read its `connectionString`.
2. Export it as both `DIRECT_URL` and `DATABASE_URL`. Branches have no pooler.
3. Run `npx prisma migrate deploy` and your test suite against the branch.
4. Delete the branch when the job finishes.

Because a branch starts from a storage snapshot, the schema and data match the source database at branch time, so migrations run against realistic data without touching production.

### Related

- [Connections](/docs/products/databases/mysql/connections): Retrieve credentials, rotate the primary password, and connect with MySQL tools.
- [Connection pooling](/docs/products/databases/mysql/connection-pooling): Pool modes, ports, and read/write splitting for serverless workloads.
- [Branches](/docs/products/databases/mysql/branches): Ephemeral database copies for preview environments and CI.
- [Network security](/docs/products/databases/mysql/network-security): TLS, certificate verification, and IP allowlists.

---

## Rails

URL: https://appwrite.io/docs/products/databases/mysql/integrations/rails

A native MySQL database is a standard MySQL engine, so [Ruby on Rails](https://rubyonrails.org/) works against it through ActiveRecord with no Appwrite-specific configuration. Point `config/database.yml` at the connection details from the [Connections](/docs/products/databases/mysql/connections) page and use ActiveRecord, migrations, and the rest of the Rails toolchain exactly as you would against any MySQL server.

**Before you start**

You'll need a native MySQL database in a `ready` state and its credentials. Call `mysql.get()` from the Appwrite API to read `hostname`, `connectionUser`, `connectionPassword`, and `connectionString`. The username is `admin.<hash>`, and the database name is `default`.

### Install the database driver

ActiveRecord talks to MySQL through the `mysql2` driver gem. Add it to your `Gemfile`:

```ruby
### Gemfile
gem "mysql2", "~> 0.5"
```

The `mysql2` gem builds against MySQL client libraries, so the MySQL or MariaDB client headers must be available at install time.

Then install:

```bash
bundle install
```

### Set the connection string

Fetch the connection string with the [API](/docs/products/databases/mysql/connections#credentials). Keep it in an environment variable and do not commit it:

```env
DATABASE_URL="mysql://admin.<hash>:<password>@db-<hash>.<region>.appwrite.center:3306/default"
DB_SSL_MODE="required"
```

Rails maps the `mysql://` URL scheme to the `mysql2` adapter by default. Set `ssl_mode` in `database.yml` so mysql2 requires TLS on Appwrite Cloud. For certificate verification (`verify_identity`), see the [Network](/docs/products/databases/mysql/network-security) page.

### Configure database.yml

Rails reads `DATABASE_URL` automatically. The simplest configuration points the `url` at the environment variable and lets ActiveRecord parse the host, port, database, and credentials out of it:

```yaml
### config/database.yml
production:
  adapter: mysql2
  url: <%= ENV["DATABASE_URL"] %>
  ssl_mode: <%= ENV.fetch("DB_SSL_MODE", "required") %>
  max_connections: <%= ENV.fetch("RAILS_MAX_THREADS", 5) %>
```

If you'd rather set the fields explicitly, the discrete keys map one-to-one to the values from the [Connections](/docs/products/databases/mysql/connections#credentials) response. Use ERB to read each value from the environment so no secret lands in source control:

```yaml
### config/database.yml
production:
  adapter: mysql2
  encoding: utf8mb4
  host: <%= ENV["DB_HOST"] %>       # db-<hash>.<region>.appwrite.center
  port: <%= ENV.fetch("DB_PORT", 3306) %>
  database: <%= ENV["DB_NAME"] %>   # default
  username: <%= ENV["DB_USER"] %>
  password: <%= ENV["DB_PASSWORD"] %>
  ssl_mode: <%= ENV.fetch("DB_SSL_MODE", "required") %>
  max_connections: <%= ENV.fetch("RAILS_MAX_THREADS", 5) %>
```

When both `DATABASE_URL` and explicit keys are present, Rails merges them. `ssl_mode` and `max_connections` can still be set in `database.yml`, so a `url`-based config can keep secrets in the environment and tune the connection pool in YAML.

### Size the connection pool

ActiveRecord manages a per-process connection pool. In current Rails apps, `max_connections:` caps how many backend connections a single Rails process holds. Older apps may use the `pool:` name for the same setting. The value defaults to `5` and must be large enough for every thread that checks out a connection, your Puma worker threads plus any background job threads in the same process.

```yaml
production:
  adapter: mysql2
  url: <%= ENV["DATABASE_URL"] %>
  ssl_mode: <%= ENV.fetch("DB_SSL_MODE", "required") %>
  max_connections: <%= ENV.fetch("RAILS_MAX_THREADS", 5) %>
```

Tying `max_connections` to `RAILS_MAX_THREADS` keeps it aligned with Puma's thread count. Each Puma **worker** is a separate process with its own pool, so the backend connection count is roughly `max_connections × workers × server instances`. Keep that product within your specification's `maxConnections`, see [specifications](/docs/products/databases/mysql#specifications).

### Run migrations

Generate and apply migrations the usual way:

```bash
bin/rails db:migrate
```

Migrations issue DDL and need a session-level connection, so run them against the direct MySQL port `3306`, not the transaction-mode pooler. Point `DATABASE_URL` (or a separate migration URL) at the direct port when you run `db:migrate`. The `admin.<hash>` user owns the database and can run schema changes.

### Use ActiveRecord

Once `database.yml` is configured, models work with no further setup. Define a migration and model, then query through ActiveRecord:

```ruby
### db/migrate/20240101000000_create_rails_users.rb
class CreateRailsUsers < ActiveRecord::Migration[8.1]
  def change
    create_table :rails_users do |t|
      t.string :email, null: false
      t.timestamps
    end
    add_index :rails_users, :email, unique: true
  end
end
```

Define the matching model:

```ruby
### app/models/rails_user.rb
class RailsUser < ApplicationRecord
  validates :email, presence: true, uniqueness: true
end
```

Create and query records as usual:

```ruby
RailsUser.create!(email: 'ada@example.com')

recent = RailsUser.order(created_at: :desc).limit(10)
```

ActiveRecord opens connections lazily and reuses them from the pool, so a long-running Puma server keeps a small, stable set of backend connections rather than opening one per request.

### Pooling for a long-running server

A Rails app under Puma is a long-running process: it holds an ActiveRecord pool for its lifetime. That pairs naturally with the direct MySQL port `3306`, sized so `max_connections × workers` stays within your connection budget. This is the recommended setup for a persistent server.

If you instead route through the [connection pooler](/docs/products/databases/mysql/connection-pooling) to absorb spikes or many app instances, prefer **session mode**, which keeps a backend connection for the whole client session and behaves like a direct connection to ActiveRecord. The pooler defaults to **transaction mode**, which hands out a different backend connection per transaction. If your Rails configuration enables server-side prepared statements, disable them for transaction-mode pooling or use session mode:

```yaml
production:
  adapter: mysql2
  url: <%= ENV["DATABASE_URL"] %>       # pooler host, port 6033
  ssl_mode: <%= ENV.fetch("DB_SSL_MODE", "required") %>
  max_connections: <%= ENV.fetch("RAILS_MAX_THREADS", 5) %>
  prepared_statements: false
```

See the [pooler](/docs/products/databases/mysql/connection-pooling#modes) page for the mode trade-offs. Whichever runtime connection you choose, always run `bin/rails db:migrate` against the direct MySQL port.

### Use a branch for previews and CI

[Branches](/docs/products/databases/mysql/branches) are instant, isolated copies of a database with their own hostname and connection string. They're ideal for running migrations against throwaway data in a pull-request preview or an integration-test job:

1. Create a branch from the API and read its `connectionString`.
2. Export it as `DATABASE_URL` for the job.
3. Run `bin/rails db:migrate` and your test suite against the branch.
4. Delete the branch when the job finishes.

A branch has no pooler and exposes the MySQL port directly, which gives migrations the session-level connection they need. Because a branch starts from a storage snapshot, the schema and data match the source database at branch time, so migrations run against realistic data without touching production.

### Related

- [Connections](/docs/products/databases/mysql/connections): Retrieve credentials and rotate the primary password.
- [Connection pooler](/docs/products/databases/mysql/connection-pooling): Pool modes, ports, and read/write splitting for high-concurrency workloads.
- [Branches](/docs/products/databases/mysql/branches): Ephemeral database copies for preview environments and CI.
- [Network](/docs/products/databases/mysql/network-security): TLS, certificate verification, and IP allowlists.

For ActiveRecord and migration details beyond this guide, see the [Rails configuration guide](https://guides.rubyonrails.org/configuring.html).

---

## Retool

URL: https://appwrite.io/docs/products/databases/mysql/integrations/retool

An Appwrite [native MySQL database](/docs/products/databases/mysql) exposes a standard MySQL connection, so [Retool](https://retool.com/) connects to it through the built-in **MySQL** resource. Use the database hostname and credentials from Appwrite, then build queries, tables, and forms in Retool for dashboards and admin panels.

**Before you start**

You'll need a native MySQL database in a `ready` state, an Appwrite API key with `databases.read`, and permission to create resources in Retool. Fetch connection details by calling `mysql.get()`, which returns `hostname`, `connectionPort`, `connectionUser`, `connectionPassword`, and `connectionString`. See [Connections](/docs/products/databases/mysql/connections) for the full flow.

### Choose credentials

The username is `admin.<hash>`, and the database name is `default`. The connection string has this form: `mysql://admin.<hash>:<password>@db-<hash>.<region>.appwrite.center:3306/default?ssl=true`.

Use the returned `admin.<hash>` credentials for the Retool resource and keep access limited through Retool resource permissions, Appwrite network controls, TLS, and narrowly scoped queries. Retool stores resource credentials server-side, so app users query through the Retool resource instead of connecting to MySQL from the browser.

### Create the MySQL resource

In Retool, go to **Resources**, click **Create new** > **Resource**, search for `MySQL`, and select the MySQL tile. Give the resource a clear **Name** and optional **Description** that identifies the Appwrite database and environment.

In **Resource credentials**, either paste the Appwrite connection string or fill in the fields manually:

| Retool setting | Value |
|----------------|-------|
| Host | `db-<hash>.<region>.appwrite.center` |
| Port | `3306` |
| Database name | `default` |
| Connection options | Optional MySQL query parameters, such as `timezone=UTC` or `connectTimeout=10000` |
| Authentication | **Username and password** |
| Username | `admin.<hash>` |
| Password | The value from `connectionPassword` |

In **Advanced options**, choose an outbound region if your Retool organization uses regional egress and you want the resource traffic to originate near your Appwrite database.

Enable **SSL/TLS**. Appwrite Cloud terminates TLS at the edge, and the certificate is signed by a public CA. If Retool shows **Reject unauthorized**, keep it enabled. If Retool shows **Verification mode**, choose **Full verification**. Leave **CA certificate** empty.

Click **Test connection**. If the test succeeds, click **Create resource**.

### Pick the connection path

For most Retool apps, connect directly to MySQL on port `3306`. Appwrite's MySQL [connection pooler](/docs/products/databases/mysql/connection-pooling) listens on port `6033`. If your Retool queries rely on session-level state such as server-side prepared statements, user variables, or temporary tables, set the pooler to **session** mode. The mode applies to every client of the database, not only Retool.

[Compare pooler modes](/docs/products/databases/mysql/connection-pooling#modes)

### Allow Retool Cloud through the network

If you enabled an [IP allowlist](/docs/products/databases/mysql/network-security#ip-allowlist) for the database, add the Retool Cloud egress addresses for the resource's outbound region. Retool's default outbound region is `us-west-2`, and Retool also documents `eu-central-1` and `ap-southeast-1` egress addresses. Retrieve the current list from [Retool's IP address documentation](https://docs.retool.com/data-sources/reference/ip-allowlist-cloud-orgs) instead of copying static addresses into your runbook.

If you leave the database allowlist open, rely on TLS, database credentials, and Retool resource permissions to protect access.

[Configure the IP allowlist](/docs/products/databases/mysql/network-security#ip-allowlist)

### Build an admin tool

After the resource is connected, create MySQL queries in Retool and wire them to components:

- Use **SQL mode** for read queries that feed a **Table**, chart, or other display component.
- Use **GUI mode** actions such as **Insert a record**, **Update an existing record**, **Update a record, or create a new record if it doesn't exist**, **Delete a record**, **Bulk insert records**, **Bulk update via a primary key**, and **Bulk upsert via a primary key** for forms, imports, and editable tables.
- Bind table edits to the **Save changes** event and refresh the read query after writes complete.
- Show a confirmation modal before delete actions.

Reference component values with Retool's `{{ }}` embedded expressions. Retool converts MySQL queries to prepared statements by default, which separates values from SQL text and helps prevent SQL injection. Keep that protection enabled unless you have a specific, reviewed reason to disable it.

### Use a branch for staging

[Branches](/docs/products/databases/mysql/branches) are API-created, isolated copies of a MySQL database with their own connection details. Create a branch for staging or preview work, fetch its `connectionString`, and configure a second Retool MySQL resource against that branch. Delete the branch when the staging tool is no longer needed.

### Related

- [MySQL](/docs/products/databases/mysql): Create and manage a native MySQL database.
- [Connections](/docs/products/databases/mysql/connections): Retrieve credentials and rotate the primary password.
- [Network security](/docs/products/databases/mysql/network-security): TLS, IP allowlists, and other network controls.
- [Branches](/docs/products/databases/mysql/branches): Create isolated database copies for staging and preview environments.

---

## Spring Boot

URL: https://appwrite.io/docs/products/databases/mysql/integrations/spring-boot

An Appwrite native MySQL database is a standard MySQL engine, so a Spring Boot application connects to it through MySQL Connector/J with no Appwrite-specific runtime configuration. Point `spring.datasource` at the JDBC URL from your database credentials, size the built-in HikariCP pool, and use Spring Data JPA, Hibernate, Flyway, or Liquibase as you would with any managed MySQL server.

**Before you start**

You'll need a native MySQL database in a `ready` state and its credentials. You can fetch credentials with the API by calling `mysql.get()`, which returns `hostname`, `connectionUser`, `connectionPassword`, and `connectionString`. See [Connections](/docs/products/databases/mysql/connections) for the full flow.

### Add the dependencies

A Spring Data JPA application needs the JPA starter and MySQL Connector/J. HikariCP ships with `spring-boot-starter-data-jpa`, and Spring Boot picks the driver class from the JDBC URL.

```text
org.springframework.boot:spring-boot-starter-data-jpa
com.mysql:mysql-connector-j
```

If you use Flyway, add `org.springframework.boot:spring-boot-starter-flyway` and `org.flywaydb:flyway-mysql`. If you use Liquibase, add `org.springframework.boot:spring-boot-starter-liquibase`.

### Configure the datasource

Build the JDBC URL from the host returned by `mysql.get()`. Appwrite uses port `3306`, the username is `admin.<hash>`, and the database name is `default`. Read the password from the environment:

```yaml
spring:
    datasource:
        url: jdbc:mysql://db-<hash>.<region>.appwrite.center:3306/default?sslMode=REQUIRED
        username: admin.<hash>
        password: ${DB_PASSWORD}
        hikari:
            maximum-pool-size: 10
            minimum-idle: 2
            connection-timeout: 30000
            max-lifetime: 1200000
    jpa:
        hibernate:
            ddl-auto: validate
```

The equivalent `application.properties`:

```ini
spring.datasource.url=jdbc:mysql://db-<hash>.<region>.appwrite.center:3306/default?sslMode=REQUIRED
spring.datasource.username=admin.<hash>
spring.datasource.password=${DB_PASSWORD}
spring.datasource.hikari.maximum-pool-size=10
spring.datasource.hikari.minimum-idle=2
spring.datasource.hikari.connection-timeout=30000
spring.datasource.hikari.max-lifetime=1200000
spring.jpa.hibernate.ddl-auto=validate
```

`sslMode=REQUIRED` enables TLS for the MySQL connection. If your security policy requires host certificate validation, configure MySQL Connector/J with `sslMode=VERIFY_IDENTITY` and a JVM trust configuration that trusts the certificate chain. The [Network security](/docs/products/databases/mysql/network-security) page covers TLS and IP allowlists.

### Size the HikariCP pool

A Spring Boot server is long-running, so it holds its HikariCP pool open for the lifetime of the process. Keep `maximum-pool-size` modest. HikariCP guidance is that throughput usually peaks at a small pool, roughly `(CPU cores x 2) + effective spindle count` for the database, not hundreds of connections. A pool that exceeds what MySQL can serve only queues work inside the database and adds latency.

Each replica of your application opens its own pool, so multiply `maximum-pool-size` by the number of instances and keep the total under the connection budget of your native MySQL database [specification](/docs/products/databases/mysql#specifications). Set `max-lifetime` a little below your infrastructure's idle timeout so HikariCP recycles connections before they are closed.

### Map an entity

Define a JPA entity and a Spring Data repository as usual. This example uses a prefixed table name so it is easy to identify in a shared database:

```java
package com.example.demo;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;

@Entity
@Table(name = "spring_boot_users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, unique = true)
    private String email;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }
}
```

Add a repository interface for it:

```java
package com.example.demo;

import java.util.Optional;

import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository<User, Long> {
    Optional<User> findByEmail(String email);
}
```

Inject the repository wherever you need it and call `save`, `findById`, `findByEmail`, and the rest of the generated query methods. HikariCP hands each transaction a pooled connection and returns it on commit.

### Run migrations

Let a migration tool own the schema and set `ddl-auto: validate` so Hibernate checks the mapping against the live tables at startup but never alters them. Add Flyway or Liquibase to your build and Spring Boot runs pending migrations automatically on boot.

For example, a Flyway migration at `src/main/resources/db/migration/V1__init.sql` can create the table used by the entity above:

```sql
CREATE TABLE spring_boot_users (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    email VARCHAR(255) NOT NULL UNIQUE
);
```

Flyway reads versioned scripts from `src/main/resources/db/migration`. Point Flyway at the direct MySQL port `3306` so migrations run on a session connection with DDL privileges. Setting `spring.flyway.url` gives Flyway its own datasource, independent of the runtime pool:

```ini
spring.flyway.url=jdbc:mysql://db-<hash>.<region>.appwrite.center:3306/default?sslMode=REQUIRED
spring.flyway.user=admin.<hash>
spring.flyway.password=${DB_PASSWORD}
```

Liquibase is equivalent: it reads a changelog from `src/main/resources/db/changelog` and accepts its own `spring.liquibase.url`, `spring.liquibase.user`, and `spring.liquibase.password` pointing at the same MySQL port.

The `admin.<hash>` user owns the database and can run schema changes, so run migrations as `admin.<hash>`.

### Pooling and the connection pooler

HikariCP is already a connection pool, so a long-running Spring Boot server should connect to the direct MySQL port `3306` and let HikariCP manage connections. Routing a server's traffic through the [connection pooler](/docs/products/databases/mysql/connection-pooling) in `transaction` mode stacks HikariCP on top of a transaction pooler and can break session-level features such as server-side prepared statements, user variables, and temporary tables.

If you put the pooler in front of your server, use `session` mode so MySQL keeps a backend connection for the whole client session. Connect HikariCP on the pooler port `6033` and keep `maximum-pool-size` small. See the [connection pooler](/docs/products/databases/mysql/connection-pooling#modes) page for the mode trade-offs. Always run Flyway or Liquibase against port `3306` regardless of how runtime traffic connects.

### Use a branch for previews and CI

[Branches](/docs/products/databases/mysql/branches) are instant, isolated copies of a database with their own hostname and connection string. They are useful for running migrations against throwaway data in a pull-request preview or integration-test job.

1. Create a branch with the API and read its `connectionString`.
2. Convert the connection string to a JDBC URL and inject it into `spring.datasource.url`, or split it into `spring.datasource.url`, `spring.datasource.username`, and `spring.datasource.password`.
3. Boot the application so Flyway or Liquibase applies migrations, then run your test suite against the branch.
4. Delete the branch when the job finishes.

Because a branch starts from a storage snapshot, the schema and data match the source database at branch time, so migrations and `@DataJpaTest` integration tests run against representative data without touching production.

### Related

- [Connections](/docs/products/databases/mysql/connections): Retrieve credentials and rotate the primary password.
- [Connection pooler](/docs/products/databases/mysql/connection-pooling): Pool modes, ports, and read/write splitting for the connection pooler.
- [Branches](/docs/products/databases/mysql/branches): Ephemeral database copies for preview environments and CI.
- [Network security](/docs/products/databases/mysql/network-security): TLS, certificate verification, and IP allowlists.

[Back to native MySQL overview](/docs/products/databases/mysql)

---

## Maintenance

URL: https://appwrite.io/docs/products/databases/mysql/maintenance

Appwrite manages the infrastructure around your database: security patches, engine upgrades, and instance health. This page covers the controls you have over when and how that maintenance happens.

### Maintenance window

Routine maintenance that can briefly affect the database runs inside a weekly window that you choose. Set it through the API by picking a day and start hour (UTC):

```server-nodejs
import { Client, Mysql } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const mysql = new Mysql(client);

await mysql.updateMaintenance({
    databaseId: '<DATABASE_ID>',
    day: 'sun',
    hourUtc: 3,
});
```
```server-deno
import { Client, Mysql } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const mysql = new Mysql(client);

await mysql.updateMaintenance({
    databaseId: '<DATABASE_ID>',
    day: 'sun',
    hourUtc: 3,
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Mysql;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$mysql = new Mysql($client);

$mysql->updateMaintenance(
    databaseId: '<DATABASE_ID>',
    day: 'sun',
    hourUtc: 3,
);
```
```server-python
from appwrite.client import Client
from appwrite.services.mysql import Mysql

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

mysql = Mysql(client)

mysql.update_maintenance(
    database_id='<DATABASE_ID>',
    day='sun',
    hour_utc=3,
)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

mysql = Mysql.new(client)

mysql.update_maintenance(
    database_id: '<DATABASE_ID>',
    day: 'sun',
    hour_utc: 3,
)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Mysql mysql = new Mysql(client);

await mysql.UpdateMaintenance(
    databaseId: "<DATABASE_ID>",
    day: "sun",
    hourUtc: 3
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Mysql mysql = Mysql(client);

await mysql.updateMaintenance(
    databaseId: '<DATABASE_ID>',
    day: 'sun',
    hourUtc: 3,
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Mysql

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val mysql = Mysql(client)

mysql.updateMaintenance(
    databaseId = "<DATABASE_ID>",
    day = "sun",
    hourUtc = 3,
)
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let mysql = Mysql(client)

_ = try await mysql.updateMaintenance(
    databaseId: "<DATABASE_ID>",
    day: "sun",
    hourUtc: 3
)
```
```server-go
package main

import (
    "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("<YOUR_API_KEY>"),
    )

    service := appwrite.NewMysql(client)

    _, err := service.UpdateMaintenance("<DATABASE_ID>", "sun", 3)
    if err != nil {
        panic(err)
    }
}
```
```server-rust
use appwrite::client::Client;
use appwrite::services::mysql::Mysql;

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

    let mysql = Mysql::new(&client);

    mysql.update_maintenance("<DATABASE_ID>", "sun", 3).await?;

    Ok(())
}
```
```bash
curl -X PATCH \
  -H "X-Appwrite-Project: <PROJECT_ID>" \
  -H "X-Appwrite-Key: <YOUR_API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{
      "day": "sun",
      "hourUtc": 3
  }' \
  https://<REGION>.cloud.appwrite.io/v1/mysql/<DATABASE_ID>/maintenance
```

`day` accepts `sun` through `sat`, and `hourUtc` accepts `0` to `23`.

### Engine version upgrades

You can upgrade the MySQL version online, such as from 8.0 to 8.4. A second instance is provisioned on the target version, data streams over with logical replication, and traffic cuts over once replication has caught up, with no read or write outage. The target version must be newer than the database's current version:

```server-nodejs
import { Client, Mysql } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const mysql = new Mysql(client);

await mysql.createUpgrade({
    databaseId: '<DATABASE_ID>',
    targetVersion: '8.4',
});
```
```server-deno
import { Client, Mysql } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const mysql = new Mysql(client);

await mysql.createUpgrade({
    databaseId: '<DATABASE_ID>',
    targetVersion: '8.4',
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Mysql;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$mysql = new Mysql($client);

$mysql->createUpgrade(
    databaseId: '<DATABASE_ID>',
    targetVersion: '8.4',
);
```
```server-python
from appwrite.client import Client
from appwrite.services.mysql import Mysql

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

mysql = Mysql(client)

mysql.create_upgrade(
    database_id='<DATABASE_ID>',
    target_version='8.4',
)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

mysql = Mysql.new(client)

mysql.create_upgrade(
    database_id: '<DATABASE_ID>',
    target_version: '8.4',
)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Mysql mysql = new Mysql(client);

await mysql.CreateUpgrade(
    databaseId: "<DATABASE_ID>",
    targetVersion: "8.4"
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Mysql mysql = Mysql(client);

await mysql.createUpgrade(
    databaseId: '<DATABASE_ID>',
    targetVersion: '8.4',
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Mysql

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val mysql = Mysql(client)

mysql.createUpgrade(
    databaseId = "<DATABASE_ID>",
    targetVersion = "8.4",
)
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let mysql = Mysql(client)

_ = try await mysql.createUpgrade(
    databaseId: "<DATABASE_ID>",
    targetVersion: "8.4"
)
```
```server-go
package main

import (
    "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("<YOUR_API_KEY>"),
    )

    service := appwrite.NewMysql(client)

    _, err := service.CreateUpgrade("<DATABASE_ID>", "8.4")
    if err != nil {
        panic(err)
    }
}
```
```server-rust
use appwrite::client::Client;
use appwrite::services::mysql::Mysql;

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

    let mysql = Mysql::new(&client);

    mysql.create_upgrade("<DATABASE_ID>", "8.4").await?;

    Ok(())
}
```
```bash
curl -X POST \
  -H "X-Appwrite-Project: <PROJECT_ID>" \
  -H "X-Appwrite-Key: <YOUR_API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{
      "targetVersion": "8.4"
  }' \
  https://<REGION>.cloud.appwrite.io/v1/mysql/<DATABASE_ID>/upgrades
```

### Pause and resume

A paused database stops its compute but keeps its storage, configuration, and credentials. Pause a database you are not using to stop paying for compute; resume it when you need it again. From the API, update the status:

```server-nodejs
import { Client, Mysql } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const mysql = new Mysql(client);

await mysql.update({
    databaseId: '<DATABASE_ID>',
    status: 'paused',
});
```
```server-deno
import { Client, Mysql } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const mysql = new Mysql(client);

await mysql.update({
    databaseId: '<DATABASE_ID>',
    status: 'paused',
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Mysql;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$mysql = new Mysql($client);

$mysql->update(
    databaseId: '<DATABASE_ID>',
    status: 'paused',
);
```
```server-python
from appwrite.client import Client
from appwrite.services.mysql import Mysql

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

mysql = Mysql(client)

mysql.update(
    database_id='<DATABASE_ID>',
    status='paused',
)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

mysql = Mysql.new(client)

mysql.update(
    database_id: '<DATABASE_ID>',
    status: 'paused',
)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Mysql mysql = new Mysql(client);

await mysql.Update(
    databaseId: "<DATABASE_ID>",
    status: "paused"
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Mysql mysql = Mysql(client);

await mysql.update(
    databaseId: '<DATABASE_ID>',
    status: 'paused',
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Mysql

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val mysql = Mysql(client)

mysql.update(
    databaseId = "<DATABASE_ID>",
    status = "paused",
)
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let mysql = Mysql(client)

_ = try await mysql.update(
    databaseId: "<DATABASE_ID>",
    status: "paused"
)
```
```server-go
package main

import (
    "github.com/appwrite/sdk-for-go/appwrite"
    "github.com/appwrite/sdk-for-go/mysql"
)

func main() {
    client := appwrite.NewClient(
        appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"),
        appwrite.WithProject("<PROJECT_ID>"),
        appwrite.WithKey("<YOUR_API_KEY>"),
    )

    service := appwrite.NewMysql(client)

    _, err := service.Update(
        "<DATABASE_ID>",
        mysql.WithUpdateStatus("paused"),
    )
    if err != nil {
        panic(err)
    }
}
```
```server-rust
use appwrite::client::Client;
use appwrite::services::mysql::Mysql;

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

    let mysql = Mysql::new(&client);

    mysql.update("<DATABASE_ID>", None, Some("paused"), None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None).await?;

    Ok(())
}
```
```bash
curl -X PATCH \
  -H "X-Appwrite-Project: <PROJECT_ID>" \
  -H "X-Appwrite-Key: <YOUR_API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{
      "status": "paused"
  }' \
  https://<REGION>.cloud.appwrite.io/v1/mysql/<DATABASE_ID>
```

Set `status` back to `ready` to resume. Both transitions are asynchronous: the request returns immediately and the database moves through `pausing` or `resuming` before settling. A database in the `failed` state can also be recovered by setting its status to `ready`.

### Lifecycle states

| Status | Meaning |
|----------------|------------------------------------------------------------------|
| `provisioning` | Being created |
| `ready` | Online and accepting connections |
| `scaling` | A configuration or specification change is being applied |
| `pausing` | Transitioning to paused |
| `paused` | Compute stopped, storage retained |
| `resuming` | Transitioning back to ready |
| `restoring` | A backup or point-in-time restore is in progress |
| `failed` | An infrastructure error occurred; the database can be resumed |

### Deleting a database

Delete a database programmatically or from the databases list in the Console. Deletion stops billing and invalidates the credentials immediately. Deleting a database also deletes its backups, so export anything you need first.

---

## Monitoring

URL: https://appwrite.io/docs/products/databases/mysql/monitoring

Every native MySQL database ships with programmatic health checks, and the engine's own instrumentation is fully available to you. There is nothing to install in the database for these checks.

### Database health

Poll the database status for live health information after the database is ready: uptime, connection counts, replica state, and storage volumes. Use it from your own monitoring:

```server-nodejs
import { Client, Mysql } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const mysql = new Mysql(client);

const status = await mysql.getStatus({
    databaseId: '<DATABASE_ID>',
});
```
```server-deno
import { Client, Mysql } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const mysql = new Mysql(client);

const status = await mysql.getStatus({
    databaseId: '<DATABASE_ID>',
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Mysql;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$mysql = new Mysql($client);

$status = $mysql->getStatus(
    databaseId: '<DATABASE_ID>',
);
```
```server-python
from appwrite.client import Client
from appwrite.services.mysql import Mysql

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

mysql = Mysql(client)

status = mysql.get_status(
    database_id='<DATABASE_ID>',
)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

mysql = Mysql.new(client)

status = mysql.get_status(
    database_id: '<DATABASE_ID>',
)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Mysql mysql = new Mysql(client);

var status = await mysql.GetStatus(
    databaseId: "<DATABASE_ID>"
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Mysql mysql = Mysql(client);

final status = await mysql.getStatus(
    databaseId: '<DATABASE_ID>',
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Mysql

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val mysql = Mysql(client)

val status = mysql.getStatus(
    databaseId = "<DATABASE_ID>",
)
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let mysql = Mysql(client)

let status = try await mysql.getStatus(
    databaseId: "<DATABASE_ID>"
)
```
```server-go
package main

import (
    "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("<YOUR_API_KEY>"),
    )

    service := appwrite.NewMysql(client)

    result, err := service.GetStatus("<DATABASE_ID>")
    if err != nil {
        panic(err)
    }
    _ = result
}
```
```server-rust
use appwrite::client::Client;
use appwrite::services::mysql::Mysql;

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

    let mysql = Mysql::new(&client);

    let status = mysql.get_status("<DATABASE_ID>").await?;

    Ok(())
}
```
```bash
curl -X GET \
  -H "X-Appwrite-Project: <PROJECT_ID>" \
  -H "X-Appwrite-Key: <YOUR_API_KEY>" \
  https://<REGION>.cloud.appwrite.io/v1/mysql/<DATABASE_ID>/status
```

For the database lifecycle state (`ready`, `scaling`, `restoring`, and friends), read the `status` field of the database object itself. Use that field from deploy pipelines to wait for the database; see [lifecycle states](/docs/products/databases/mysql/maintenance#states).

### Inspect activity in the engine

MySQL's own instrumentation works unmodified. From the `mysql` client or any driver:

```sql
-- live connections and their current statements
SELECT id, user, host, db, command, time, state, info
FROM performance_schema.processlist;

-- kill a runaway connection
KILL <id>;

-- execution plan for a slow query
EXPLAIN ANALYZE SELECT ...;
```

A healthy OLTP database keeps its working set in memory. If throughput drops while disk reads climb, move up a [specification](/docs/products/databases/mysql/scaling) so the buffer pool fits the working set.

---

## Network security

URL: https://appwrite.io/docs/products/databases/mysql/network-security

Every native database is reachable through a unique public hostname, secured with TLS, and protected by network controls that you configure per database.

### Hostname

Each database gets a stable hostname in the form:

```
db-<hash>.<region>.appwrite.center
```

The hostname does not change for the lifetime of the database, across restarts, resizes, failovers, and version upgrades. You can copy it from the database response.

### TLS

Connections on Appwrite Cloud are encrypted with TLS, terminated at Appwrite's edge and forwarded to your database over the internal network. Use TLS when connecting to the public hostname. If your driver does not infer TLS from the connection string, configure it to require TLS.

### IP allowlist

By default, any host that has your credentials can reach the database over the public internet. To restrict access to known networks, configure an IP allowlist. Connections from addresses outside the allowlist are refused by the edge proxy before authentication, with MySQL error 1045 and a message that the address is not permitted by the allowlist.

From the API, pass CIDR blocks or single addresses:

```server-nodejs
import { Client, Mysql } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const mysql = new Mysql(client);

await mysql.update({
    databaseId: '<DATABASE_ID>',
    networkIPAllowlist: ['203.0.113.0/24', '198.51.100.7'],
});
```

```server-deno
import { Client, Mysql } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const mysql = new Mysql(client);

await mysql.update({
    databaseId: '<DATABASE_ID>',
    networkIPAllowlist: ['203.0.113.0/24', '198.51.100.7'],
});
```

```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Mysql;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$mysql = new Mysql($client);

$mysql->update(
    databaseId: '<DATABASE_ID>',
    networkIPAllowlist: ['203.0.113.0/24', '198.51.100.7'],
);
```

```server-python
from appwrite.client import Client
from appwrite.services.mysql import Mysql

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

mysql = Mysql(client)

mysql.update(
    database_id='<DATABASE_ID>',
    network_ip_allowlist=['203.0.113.0/24', '198.51.100.7'],
)
```

```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

mysql = Mysql.new(client)

mysql.update(
    database_id: '<DATABASE_ID>',
    network_ip_allowlist: ['203.0.113.0/24', '198.51.100.7'],
)
```

```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Mysql mysql = new Mysql(client);

await mysql.Update(
    databaseId: "<DATABASE_ID>",
    networkIPAllowlist: new List<string> { "203.0.113.0/24", "198.51.100.7" }
);
```

```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Mysql mysql = Mysql(client);

await mysql.update(
    databaseId: '<DATABASE_ID>',
    networkIPAllowlist: ['203.0.113.0/24', '198.51.100.7'],
);
```

```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Mysql

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val mysql = Mysql(client)

mysql.update(
    databaseId = "<DATABASE_ID>",
    networkIPAllowlist = listOf("203.0.113.0/24", "198.51.100.7"),
)
```

```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let mysql = Mysql(client)

_ = try await mysql.update(
    databaseId: "<DATABASE_ID>",
    networkIPAllowlist: ["203.0.113.0/24", "198.51.100.7"]
)
```

```server-go
package main

import (
    "github.com/appwrite/sdk-for-go/appwrite"
    "github.com/appwrite/sdk-for-go/mysql"
)

func main() {
    client := appwrite.NewClient(
        appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"),
        appwrite.WithProject("<PROJECT_ID>"),
        appwrite.WithKey("<YOUR_API_KEY>"),
    )

    service := appwrite.NewMysql(client)

    _, err := service.Update(
        "<DATABASE_ID>",
        mysql.WithUpdateNetworkIPAllowlist([]string{"203.0.113.0/24", "198.51.100.7"}),
    )
    if err != nil {
        panic(err)
    }
}
```

```server-rust
use appwrite::Client;
use appwrite::services::Mysql;

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

    let mysql = Mysql::new(&client);

    mysql.update(
        "<DATABASE_ID>",
        None,
        None,
        None,
        None,
        None,
        None,
        Some(vec!["203.0.113.0/24".to_string(), "198.51.100.7".to_string()]),
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
        None,
    ).await?;

    Ok(())
}
```

```bash
curl -X PATCH \
  -H "X-Appwrite-Project: <PROJECT_ID>" \
  -H "X-Appwrite-Key: <API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{
      "networkIPAllowlist": [
          "203.0.113.0/24",
          "198.51.100.7"
      ]
  }' \
  https://<REGION>.cloud.appwrite.io/v1/mysql/<DATABASE_ID>
```

Rules:

- Entries are IPv4 or IPv6 addresses or CIDR blocks, up to 100 entries per database.
- An empty allowlist means the database accepts connections from any address.
- The allowlist applies to the database and pooler ports. Appwrite's internal infrastructure, backups, monitoring, and replication are unaffected.
- Branch connections are not checked against the allowlist.

**Don't lock yourself out**

If you connect from networks with changing addresses (home ISPs, mobile networks, serverless platforms without static egress), an allowlist can block you. Add your serverless provider's egress ranges, or leave the allowlist empty and rely on strong credentials and rotation.

### Idle timeout

Appwrite applies `networkIdleTimeoutSeconds` as the MySQL `wait_timeout` and `interactive_timeout`, so the engine closes a session that stays idle longer than this value. The default is 900 seconds, and the accepted range is 60 to 86400. TCP keepalives do not reset the timer; only activity on the session does, so long-lived driver pools should validate or recycle connections that have been idle longer than the timeout. Through the pooler, the timeout applies to the pooler's connections to the engine, not to clients connected on port 6033. Raise the timeout if you hold connections open across long pauses; lower it to reclaim connection slots faster.

```server-nodejs
import { Client, Mysql } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const mysql = new Mysql(client);

await mysql.update({
    databaseId: '<DATABASE_ID>',
    networkIdleTimeoutSeconds: 900,
});
```

```server-deno
import { Client, Mysql } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const mysql = new Mysql(client);

await mysql.update({
    databaseId: '<DATABASE_ID>',
    networkIdleTimeoutSeconds: 900,
});
```

```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Mysql;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$mysql = new Mysql($client);

$mysql->update(
    databaseId: '<DATABASE_ID>',
    networkIdleTimeoutSeconds: 900,
);
```

```server-python
from appwrite.client import Client
from appwrite.services.mysql import Mysql

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

mysql = Mysql(client)

mysql.update(
    database_id='<DATABASE_ID>',
    network_idle_timeout_seconds=900,
)
```

```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

mysql = Mysql.new(client)

mysql.update(
    database_id: '<DATABASE_ID>',
    network_idle_timeout_seconds: 900,
)
```

```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Mysql mysql = new Mysql(client);

await mysql.Update(
    databaseId: "<DATABASE_ID>",
    networkIdleTimeoutSeconds: 900
);
```

```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Mysql mysql = Mysql(client);

await mysql.update(
    databaseId: '<DATABASE_ID>',
    networkIdleTimeoutSeconds: 900,
);
```

```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Mysql

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val mysql = Mysql(client)

mysql.update(
    databaseId = "<DATABASE_ID>",
    networkIdleTimeoutSeconds = 900,
)
```

```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let mysql = Mysql(client)

_ = try await mysql.update(
    databaseId: "<DATABASE_ID>",
    networkIdleTimeoutSeconds: 900
)
```

```server-go
package main

import (
    "github.com/appwrite/sdk-for-go/appwrite"
    "github.com/appwrite/sdk-for-go/mysql"
)

func main() {
    client := appwrite.NewClient(
        appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"),
        appwrite.WithProject("<PROJECT_ID>"),
        appwrite.WithKey("<YOUR_API_KEY>"),
    )

    service := appwrite.NewMysql(client)

    _, err := service.Update(
        "<DATABASE_ID>",
        mysql.WithUpdateNetworkIdleTimeoutSeconds(900),
    )
    if err != nil {
        panic(err)
    }
}
```

```server-rust
use appwrite::Client;
use appwrite::services::Mysql;

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

    let mysql = Mysql::new(&client);

    mysql.update("<DATABASE_ID>", None, None, None, None, None, Some(900), None, None, None, None, None, None, None, None, None, None, None, None, None, None).await?;

    Ok(())
}
```

```bash
curl -X PATCH \
  -H "X-Appwrite-Project: <PROJECT_ID>" \
  -H "X-Appwrite-Key: <API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{
      "networkIdleTimeoutSeconds": 900
  }' \
  https://<REGION>.cloud.appwrite.io/v1/mysql/<DATABASE_ID>
```

### Locking down access

For a production database:

1. Set an IP allowlist covering only your application's egress addresses.
2. Rotate the [primary password](/docs/products/databases/mysql/connections#rotate) on a schedule, and after anyone with access leaves your team.
3. Watch [connection activity](/docs/products/databases/mysql/monitoring#activity) for unexpected clients.

---

## Quick start

URL: https://appwrite.io/docs/products/databases/mysql/quick-start

You can create a MySQL database and run your first query in a few minutes.

### Create a database

1. In your project, go to **Databases**.
2. Click **Create database**.
3. Give your database a name, and optionally a custom database ID.
4. Under **Choose database type**, select **MySQL** from the **Native databases** group.

![Create database type selection](/images/docs/products/databases/mysql/create-database-type.avif)

5. Under **Specifications**, select your preferred tier.
6. Optionally configure **Read replicas** and **Point-in-time recovery (PITR)**. You can change both later.

![Database specifications](/images/docs/products/databases/mysql/create-database-specs.avif)

7. Review the database summary and click **Create database**.

Your database starts provisioning and becomes `ready` shortly after.

### Get your connection details

The connection details are returned on the database object. Fetch the database with an API key that has the `databases.read` scope:

```server-nodejs
import { Client, Mysql } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const mysql = new Mysql(client);

const database = await mysql.get({
    databaseId: '<DATABASE_ID>',
});

console.log(database.connectionString);
```

The response includes the hostname, port, username, and password, plus a ready-made connection string in the form `mysql://admin.<hash>:<password>@db-<hash>.<region>.appwrite.center:3306/default?ssl=true`. The username is `admin.<hash>` and the database name is always `default`. See [Connections](/docs/products/databases/mysql/connections) for the full response and every language.

### Connect with the mysql client

Pass the connection details to the `mysql` command-line client:

```bash
mysql -h db-<hash>.<region>.appwrite.center -P 3306 -u admin.<hash> -p -D default
```

Enter the password when prompted.

### Run your first queries

Create a table, insert a row, and read it back:

```sql
CREATE TABLE books (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    title TEXT NOT NULL,
    author TEXT NOT NULL
);

INSERT INTO books (title, author)
VALUES ('The Hitchhiker''s Guide to the Galaxy', 'Douglas Adams');

SELECT * FROM books;
```

You can run the same queries from your application with any MySQL driver. See [Connections](/docs/products/databases/mysql/connections) for driver examples.

### Next steps

- [Connections](/docs/products/databases/mysql/connections): Connect from your application with any MySQL driver or ORM.
- [Backups](/docs/products/databases/mysql/backups): Configure backup policies and point-in-time recovery.
- [High availability](/docs/products/databases/mysql/high-availability): Add read replicas with automatic failover.
- [Monitoring](/docs/products/databases/mysql/monitoring): Poll live health and status information from your pipelines.

---

## Scaling

URL: https://appwrite.io/docs/products/databases/mysql/scaling

Native databases scale in two dimensions: the compute specification (CPU, memory, and connection limit) and storage. Both can change after creation, without dump-and-restore migrations.

### List available specifications

Each database runs against a specification that defines its CPU, memory, included storage, and maximum connections. List the specifications available to your plan:

```server-nodejs
import { Client, Mysql } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const mysql = new Mysql(client);

const specifications = await mysql.listSpecifications({

});
```
```server-deno
import { Client, Mysql } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const mysql = new Mysql(client);

const specifications = await mysql.listSpecifications({

});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Mysql;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$mysql = new Mysql($client);

$specifications = $mysql->listSpecifications(

);
```
```server-python
from appwrite.client import Client
from appwrite.services.mysql import Mysql

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

mysql = Mysql(client)

specifications = mysql.list_specifications(

)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

mysql = Mysql.new(client)

specifications = mysql.list_specifications(

)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Mysql mysql = new Mysql(client);

var specifications = await mysql.ListSpecifications(

);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Mysql mysql = Mysql(client);

final specifications = await mysql.listSpecifications(

);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Mysql

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val mysql = Mysql(client)

val specifications = mysql.listSpecifications(

)
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let mysql = Mysql(client)

let specifications = try await mysql.listSpecifications(

)
```
```server-go
package main

import (
    "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("<YOUR_API_KEY>"),
    )

    service := appwrite.NewMysql(client)

    result, err := service.ListSpecifications()
    if err != nil {
        panic(err)
    }
    _ = result
}
```
```server-rust
use appwrite::client::Client;
use appwrite::services::mysql::Mysql;

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

    let mysql = Mysql::new(&client);

    let specifications = mysql.list_specifications().await?;

    Ok(())
}
```
```bash
curl -X GET \
  -H "X-Appwrite-Project: <PROJECT_ID>" \
  -H "X-Appwrite-Key: <YOUR_API_KEY>" \
  https://<REGION>.cloud.appwrite.io/v1/mysql/specifications
```

### Change the compute specification

From the API, pass the new specification ID:

```server-nodejs
import { Client, Mysql } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const mysql = new Mysql(client);

await mysql.update({
    databaseId: '<DATABASE_ID>',
    specification: '<SPECIFICATION>',
});
```
```server-deno
import { Client, Mysql } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const mysql = new Mysql(client);

await mysql.update({
    databaseId: '<DATABASE_ID>',
    specification: '<SPECIFICATION>',
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Mysql;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$mysql = new Mysql($client);

$mysql->update(
    databaseId: '<DATABASE_ID>',
    specification: '<SPECIFICATION>',
);
```
```server-python
from appwrite.client import Client
from appwrite.services.mysql import Mysql

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

mysql = Mysql(client)

mysql.update(
    database_id='<DATABASE_ID>',
    specification='<SPECIFICATION>',
)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

mysql = Mysql.new(client)

mysql.update(
    database_id: '<DATABASE_ID>',
    specification: '<SPECIFICATION>',
)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Mysql mysql = new Mysql(client);

await mysql.Update(
    databaseId: "<DATABASE_ID>",
    specification: "<SPECIFICATION>"
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Mysql mysql = Mysql(client);

await mysql.update(
    databaseId: '<DATABASE_ID>',
    specification: '<SPECIFICATION>',
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Mysql

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val mysql = Mysql(client)

mysql.update(
    databaseId = "<DATABASE_ID>",
    specification = "<SPECIFICATION>",
)
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let mysql = Mysql(client)

_ = try await mysql.update(
    databaseId: "<DATABASE_ID>",
    specification: "<SPECIFICATION>"
)
```
```server-go
package main

import (
    "github.com/appwrite/sdk-for-go/appwrite"
    "github.com/appwrite/sdk-for-go/mysql"
)

func main() {
    client := appwrite.NewClient(
        appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"),
        appwrite.WithProject("<PROJECT_ID>"),
        appwrite.WithKey("<YOUR_API_KEY>"),
    )

    service := appwrite.NewMysql(client)

    _, err := service.Update(
        "<DATABASE_ID>",
        mysql.WithUpdateSpecification("<SPECIFICATION>"),
    )
    if err != nil {
        panic(err)
    }
}
```
```server-rust
use appwrite::client::Client;
use appwrite::services::mysql::Mysql;

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

    let mysql = Mysql::new(&client);

    mysql.update("<DATABASE_ID>", None, None, Some("<SPECIFICATION>"), None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None).await?;

    Ok(())
}
```
```bash
curl -X PATCH \
  -H "X-Appwrite-Project: <PROJECT_ID>" \
  -H "X-Appwrite-Key: <YOUR_API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{
      "specification": "<SPECIFICATION>"
  }' \
  https://<REGION>.cloud.appwrite.io/v1/mysql/<DATABASE_ID>
```

Resizes apply with zero downtime through a rolling cutover: a new instance is provisioned on the target specification, data is streamed over, and traffic cuts over once it has caught up. The database status shows `scaling` while the resize is in progress.

### Storage

Each specification includes a storage allowance, and storage beyond the allowance is billed per GB. Storage only grows; you cannot shrink a database's storage after it has expanded. To reclaim a smaller footprint, restore a [backup](/docs/products/databases/mysql/backups) into a new database.

### Storage autoscaling

With storage autoscaling enabled, Appwrite grows the storage automatically when usage crosses a threshold, so the database never hits a full disk. Configure it through the API:

```server-nodejs
import { Client, Mysql } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const mysql = new Mysql(client);

await mysql.update({
    databaseId: '<DATABASE_ID>',
    storageAutoscaling: true,
    storageAutoscalingThresholdPercent: 85,
    storageAutoscalingMaxGb: 500,
});
```
```server-deno
import { Client, Mysql } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const mysql = new Mysql(client);

await mysql.update({
    databaseId: '<DATABASE_ID>',
    storageAutoscaling: true,
    storageAutoscalingThresholdPercent: 85,
    storageAutoscalingMaxGb: 500,
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Mysql;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$mysql = new Mysql($client);

$mysql->update(
    databaseId: '<DATABASE_ID>',
    storageAutoscaling: true,
    storageAutoscalingThresholdPercent: 85,
    storageAutoscalingMaxGb: 500,
);
```
```server-python
from appwrite.client import Client
from appwrite.services.mysql import Mysql

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

mysql = Mysql(client)

mysql.update(
    database_id='<DATABASE_ID>',
    storage_autoscaling=True,
    storage_autoscaling_threshold_percent=85,
    storage_autoscaling_max_gb=500,
)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

mysql = Mysql.new(client)

mysql.update(
    database_id: '<DATABASE_ID>',
    storage_autoscaling: true,
    storage_autoscaling_threshold_percent: 85,
    storage_autoscaling_max_gb: 500,
)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Mysql mysql = new Mysql(client);

await mysql.Update(
    databaseId: "<DATABASE_ID>",
    storageAutoscaling: true,
    storageAutoscalingThresholdPercent: 85,
    storageAutoscalingMaxGb: 500
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Mysql mysql = Mysql(client);

await mysql.update(
    databaseId: '<DATABASE_ID>',
    storageAutoscaling: true,
    storageAutoscalingThresholdPercent: 85,
    storageAutoscalingMaxGb: 500,
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Mysql

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val mysql = Mysql(client)

mysql.update(
    databaseId = "<DATABASE_ID>",
    storageAutoscaling = true,
    storageAutoscalingThresholdPercent = 85,
    storageAutoscalingMaxGb = 500,
)
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let mysql = Mysql(client)

_ = try await mysql.update(
    databaseId: "<DATABASE_ID>",
    storageAutoscaling: true,
    storageAutoscalingThresholdPercent: 85,
    storageAutoscalingMaxGb: 500
)
```
```server-go
package main

import (
    "github.com/appwrite/sdk-for-go/appwrite"
    "github.com/appwrite/sdk-for-go/mysql"
)

func main() {
    client := appwrite.NewClient(
        appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"),
        appwrite.WithProject("<PROJECT_ID>"),
        appwrite.WithKey("<YOUR_API_KEY>"),
    )

    service := appwrite.NewMysql(client)

    _, err := service.Update(
        "<DATABASE_ID>",
        mysql.WithUpdateStorageAutoscaling(true),
        mysql.WithUpdateStorageAutoscalingThresholdPercent(85),
        mysql.WithUpdateStorageAutoscalingMaxGb(500),
    )
    if err != nil {
        panic(err)
    }
}
```
```server-rust
use appwrite::client::Client;
use appwrite::services::mysql::Mysql;

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

    let mysql = Mysql::new(&client);

    mysql.update("<DATABASE_ID>", None, None, None, None, None, None, None, None, None, None, Some(true), Some(85), Some(500), None, None, None, None, None, None, None).await?;

    Ok(())
}
```
```bash
curl -X PATCH \
  -H "X-Appwrite-Project: <PROJECT_ID>" \
  -H "X-Appwrite-Key: <YOUR_API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{
      "storageAutoscaling": true,
      "storageAutoscalingThresholdPercent": 85,
      "storageAutoscalingMaxGb": 500
  }' \
  https://<REGION>.cloud.appwrite.io/v1/mysql/<DATABASE_ID>
```

| Parameter | Range | Description |
|---------------------------------------|--------------------|-----------------------------------------------------------|
| `storageAutoscaling` | boolean | Enable automatic storage growth |
| `storageAutoscalingThresholdPercent` | 50 - 95 | Usage percentage that triggers an expansion (default 85) |
| `storageAutoscalingMaxGb` | integer, 0 = no cap | Upper bound for automatic growth |

Set a cap if you want a hard ceiling on storage cost; without one, autoscaling grows storage as needed and the overage is billed per GB.

### Picking a specification

Guidelines for choosing a starting tier:

- **Connections**: count the maximum concurrent connections your application opens, including all replicas of your app server. If it exceeds the specification's connection cap, either move up a tier or put the [connection pooler](/docs/products/databases/mysql/connection-pooling) in front.
- **Memory**: MySQL performs best when the working set fits in memory. Track cache hit ratio with database metrics; a sustained ratio below ~99% for an OLTP workload is a sign to add memory.
- **CPU**: sustained CPU above 70-80% at normal load leaves no headroom for spikes, migrations, or backups.

Start small and resize up when the metrics say so; resizes are online, so there is no penalty for growing later.

---

## Managed PostgreSQL

URL: https://appwrite.io/docs/products/databases/postgresql

Appwrite managed PostgreSQL is a hosted PostgreSQL database provisioned for your project. You pick the compute specification, and Appwrite runs the engine in your project's region with its own storage, networking, and credentials, exposed through a per-database public hostname secured with TLS.

A native PostgreSQL database gives you the raw engine: you connect with `psql` or any PostgreSQL driver, bring your own ORM and migrations, and use the full feature set of PostgreSQL, with no Appwrite layer in between. If you want Appwrite SDKs, platform permissions, and serverless scaling for app data, use [TablesDB](/docs/products/databases/tablesdb) for relational data, [DocumentsDB](/docs/products/databases/documentsdb) for schemaless documents, or [VectorsDB](/docs/products/databases/vectorsdb) for embeddings instead.

### Supported versions

Appwrite manages the database container, storage, backups, and networking. You bring the application.

New databases run PostgreSQL 18 by default. To create a database on PostgreSQL 17 instead, pass `version` when you create it through the API. The database listens on the standard PostgreSQL port, 5432.

The version is selected on create and can be upgraded later. Upgrades run online: a second instance is provisioned on the new version, data is streamed over with logical replication, and traffic cuts over once replication is caught up. Reads continue throughout. At the cutover itself the old instance is fenced read-only and existing client connections are closed, so the application must reconnect.

A new database starts in a `provisioning` state and becomes `ready` within a few minutes. Large specifications or high-availability configurations take longer to schedule; poll the database status or watch the Console until it reports `ready`.

### Regions

Native databases are rolling out region by region, starting with Frankfurt (`fra`) and New York (`nyc`), and more regions are on the way.

A native database lives in the same region as the project that owns it. There is no per-database region selector, so a project in a region that has not been enabled yet cannot host one. Each database gets a unique hostname in the form `db-<hash>.<region>.appwrite.center`, and data does not leave the region.

### Feature overview

- [Connections](/docs/products/databases/postgresql/connections): Connect with `psql` or any driver. Credentials are rotatable from the Console and API.
- [Connection pooling](/docs/products/databases/postgresql/connection-pooling): Per-database connection pooler with automatic read/write split when high availability is enabled.
- [Extensions](/docs/products/databases/postgresql/extensions): Install PostgreSQL extensions like PostGIS, pgvector, and pg_trgm.
- [Backups](/docs/products/databases/postgresql/backups): Scheduled backups, manual backups, restores, and point-in-time recovery.
- [Branches](/docs/products/databases/postgresql/branches): Spin up an ephemeral copy of your database in seconds from a storage snapshot.
- [High availability](/docs/products/databases/postgresql/high-availability): Up to five replicas with asynchronous, synchronous, or quorum replication, and automatic failover.
- [Scaling](/docs/products/databases/postgresql/scaling): Resize compute online and grow storage automatically as your data grows.
- [Network security](/docs/products/databases/postgresql/network-security): TLS by default, optional IP allowlists, and a dedicated hostname per database.
- [Monitoring](/docs/products/databases/postgresql/monitoring): Live compute, connection, storage, and workload metrics, with an active-connections inspector.
- [Maintenance](/docs/products/databases/postgresql/maintenance): Maintenance windows, online version upgrades, and pause and resume.

### Specifications and pricing

Each database runs against one compute specification. Prices below are monthly rates, pro-rated by the hour a database is running:

| Tier | Specification | CPU | Memory | Storage | Bandwidth | Connections | Price |
|-----------------|----------------|---------|--------|---------|-----------|-------------|---------|
| Starter | `s-1vcpu-1gb` | 1 core | 1 GB | 10 GB | 50 GB | 100 | $10/mo |
| Standard | `s-2vcpu-2gb` | 2 cores | 2 GB | 25 GB | 200 GB | 200 | $20/mo |
| Standard Plus | `s-2vcpu-4gb` | 2 cores | 4 GB | 50 GB | 400 GB | 500 | $49/mo |
| Professional | `s-4vcpu-8gb` | 4 cores | 8 GB | 200 GB | 1 TB | 1,000 | $85/mo |
| Business | `s-4vcpu-16gb` | 4 cores | 16 GB | 500 GB | 2 TB | 2,000 | $160/mo |
| Business Plus | `s-4vcpu-32gb` | 4 cores | 32 GB | 1 TB | 5 TB | 4,000 | $299/mo |
| Enterprise | `s-8vcpu-32gb` | 8 cores | 32 GB | 2 TB | 7.5 TB | 5,000 | $425/mo |
| Enterprise Plus | `s-8vcpu-64gb` | 8 cores | 64 GB | 3 TB | 10 TB | 10,000 | $699/mo |
| Scale | `s-16vcpu-64gb` | 16 cores | 64 GB | 4 TB | 15 TB | 10,000 | $999/mo |
| Scale Plus | `s-16vcpu-128gb` | 16 cores | 128 GB | 5 TB | 20 TB | 10,000 | $1,699/mo |
| Scale Pro | `s-32vcpu-128gb` | 32 cores | 128 GB | 6 TB | 30 TB | 10,000 | $2,399/mo |
| Scale Max | `s-32vcpu-256gb` | 32 cores | 256 GB | 8 TB | 40 TB | 10,000 | $4,099/mo |

The Storage and Bandwidth columns are the monthly allowances included with each tier. Usage beyond them is billed as overage, and optional features are billed as add-ons on top of the tier price:

| Add-on | Price |
|----------------------------|---------------------------------|
| Storage overage | $0.125 per GB per month |
| Bandwidth overage | $0.08 per GB per month |
| High availability replica | 100% of the tier price, per replica |
| Point-in-time recovery | 20% of the tier price |

You can [resize between tiers](/docs/products/databases/postgresql/scaling) at any time with zero downtime.

### Limits

The following limits apply per database:

| Limit | Value |
|--------------------------------|-----------------------------------------------------------|
| High availability replicas | 0 - 5 |
| IP allowlist entries | 100 |
| Backup retention | 7 days on Pro, 1 - 36,500 days on Scale |
| Point-in-time recovery window | 1 - 35 days |
| Extensions installed | 50 |
| Max simultaneous connections | 10,000 platform cap, lower on smaller specifications |

### Billing and plan requirements

Native databases are available on paid plans, and a payment method must be attached to your organization. Your plan also decides which specifications you can pick: the Pro plan allows the four smallest, and the Scale plan allows all twelve. Each database is billed against its [compute specification](#specifications), pro-rated by the hour, with separate line items for overages and add-ons. See [pricing](/pricing) for details.

---

## Backups

URL: https://appwrite.io/docs/products/databases/postgresql/backups

Native databases are backed up automatically. Backups are stored off the database instance and restorable from the API. For finer recovery granularity than scheduled backups, enable point-in-time recovery.

### Automatic backups

![Backups page with policies and completed backups](/images/docs/products/databases/postgresql/backups-tab.avif)

Every database gets a default backup policy when it is provisioned, so you have scheduled backups from day one. You can adjust the default policy, or add more policies with different schedules and retention windows.

### Backup policies

![Create backup policy dialog](/images/docs/products/databases/postgresql/backups-create-policy.avif)

A policy defines a schedule and a retention period in days. In the Console, open your database's **Backups** page and click **Create policy**, then pick a preset schedule or add a custom policy with its own schedule and retention.

Your plan decides how many policies you can create, which schedules they can run on, and how long they can retain backups:

| Plan | Policies | Schedule | Retention |
|-------|-----------|-------------------|-------------|
| Pro | 1 | Daily | 7 days |
| Scale | Unlimited | Any cron schedule | 1-36500 days |

On the Pro plan the values are fixed, so the default daily policy created with the database is the only one you get. Requesting a different schedule or retention returns an error.

You can also create a policy with a Server SDK:

```server-nodejs
import { Client, Postgresql } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const postgresql = new Postgresql(client);

await postgresql.createBackupPolicy({
    databaseId: '<DATABASE_ID>',
    policyId: 'hourly',
    name: 'Hourly',
    schedule: '0 * * * *',
    retention: 30,
});
```
```server-deno
import { Client, Postgresql } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const postgresql = new Postgresql(client);

await postgresql.createBackupPolicy({
    databaseId: '<DATABASE_ID>',
    policyId: 'hourly',
    name: 'Hourly',
    schedule: '0 * * * *',
    retention: 30,
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Postgresql;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$postgresql = new Postgresql($client);

$postgresql->createBackupPolicy(
    databaseId: '<DATABASE_ID>',
    policyId: 'hourly',
    name: 'Hourly',
    schedule: '0 * * * *',
    retention: 30,
);
```
```server-python
from appwrite.client import Client
from appwrite.services.postgresql import Postgresql

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

postgresql = Postgresql(client)

postgresql.create_backup_policy(
    database_id='<DATABASE_ID>',
    policy_id='hourly',
    name='Hourly',
    schedule='0 * * * *',
    retention=30,
)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

postgresql = Postgresql.new(client)

postgresql.create_backup_policy(
    database_id: '<DATABASE_ID>',
    policy_id: 'hourly',
    name: 'Hourly',
    schedule: '0 * * * *',
    retention: 30,
)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Postgresql postgresql = new Postgresql(client);

await postgresql.CreateBackupPolicy(
    databaseId: "<DATABASE_ID>",
    policyId: "hourly",
    name: "Hourly",
    schedule: "0 * * * *",
    retention: 30
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Postgresql postgresql = Postgresql(client);

await postgresql.createBackupPolicy(
    databaseId: '<DATABASE_ID>',
    policyId: 'hourly',
    name: 'Hourly',
    schedule: '0 * * * *',
    retention: 30,
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Postgresql

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val postgresql = Postgresql(client)

postgresql.createBackupPolicy(
    databaseId = "<DATABASE_ID>",
    policyId = "hourly",
    name = "Hourly",
    schedule = "0 * * * *",
    retention = 30,
)
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let postgresql = Postgresql(client)

_ = try await postgresql.createBackupPolicy(
    databaseId: "<DATABASE_ID>",
    policyId: "hourly",
    name: "Hourly",
    schedule: "0 * * * *",
    retention: 30
)
```
```server-go
package main

import (
    "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("<YOUR_API_KEY>"),
    )

    service := appwrite.NewPostgresql(client)

    _, err := service.CreateBackupPolicy("<DATABASE_ID>", "hourly", "Hourly", "0 * * * *", 30)
    if err != nil {
        panic(err)
    }
}
```
```server-rust
use appwrite::client::Client;
use appwrite::services::postgresql::Postgresql;

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

    let postgresql = Postgresql::new(&client);

    postgresql.create_backup_policy("<DATABASE_ID>", "hourly", "Hourly", "0 * * * *", 30, None, None).await?;

    Ok(())
}
```
```bash
curl -X POST \
  -H "X-Appwrite-Project: <PROJECT_ID>" \
  -H "X-Appwrite-Key: <API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{
      "policyId": "hourly",
      "name": "Hourly",
      "schedule": "0 * * * *",
      "retention": 30
  }' \
  https://<REGION>.cloud.appwrite.io/v1/postgresql/<DATABASE_ID>/backups/policies
```

| Parameter | Value | Description |
|-------------|-------------------------------------|-----------------------------------------------|
| `policyId` | custom ID or `unique()` | Policy identifier |
| `name` | text | Display name |
| `schedule` | cron expression | When backups run, for example `0 3 * * *` |
| `retention` | 7 days on Pro, 1-36500 on Scale | How long backups from this policy are kept |

List, update, and delete policies with `listBackupPolicies`, `updateBackupPolicy`, and `deleteBackupPolicy`. The number of policies you can create depends on your plan.

### Manual backups

![Create manual backup dialog](/images/docs/products/databases/postgresql/backups-manual.avif)

Take an on-demand backup before a risky change. In the Console, click **Manual backup** on the **Backups** page. Policy backups are pruned once they pass their retention window. Manual backups are not pruned on that schedule, so remove them yourself when you no longer need them.

You can also take one with a Server SDK:

```server-nodejs
import { Client, Postgresql } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const postgresql = new Postgresql(client);

const backup = await postgresql.createBackup({
    databaseId: '<DATABASE_ID>',
});
```
```server-deno
import { Client, Postgresql } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const postgresql = new Postgresql(client);

const backup = await postgresql.createBackup({
    databaseId: '<DATABASE_ID>',
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Postgresql;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$postgresql = new Postgresql($client);

$backup = $postgresql->createBackup(
    databaseId: '<DATABASE_ID>',
);
```
```server-python
from appwrite.client import Client
from appwrite.services.postgresql import Postgresql

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

postgresql = Postgresql(client)

backup = postgresql.create_backup(
    database_id='<DATABASE_ID>',
)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

postgresql = Postgresql.new(client)

backup = postgresql.create_backup(
    database_id: '<DATABASE_ID>',
)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Postgresql postgresql = new Postgresql(client);

var backup = await postgresql.CreateBackup(
    databaseId: "<DATABASE_ID>"
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Postgresql postgresql = Postgresql(client);

final backup = await postgresql.createBackup(
    databaseId: '<DATABASE_ID>',
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Postgresql

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val postgresql = Postgresql(client)

val backup = postgresql.createBackup(
    databaseId = "<DATABASE_ID>",
)
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let postgresql = Postgresql(client)

let backup = try await postgresql.createBackup(
    databaseId: "<DATABASE_ID>"
)
```
```server-go
package main

import (
    "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("<YOUR_API_KEY>"),
    )

    service := appwrite.NewPostgresql(client)

    result, err := service.CreateBackup("<DATABASE_ID>")
    if err != nil {
        panic(err)
    }
    _ = result
}
```
```server-rust
use appwrite::client::Client;
use appwrite::services::postgresql::Postgresql;

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

    let postgresql = Postgresql::new(&client);

    let backup = postgresql.create_backup("<DATABASE_ID>", None).await?;

    Ok(())
}
```
```bash
curl -X POST \
  -H "X-Appwrite-Project: <PROJECT_ID>" \
  -H "X-Appwrite-Key: <API_KEY>" \
  https://<REGION>.cloud.appwrite.io/v1/postgresql/<DATABASE_ID>/backups
```

The backup runs asynchronously with status `pending` until it completes. List backups and check their status with `listBackups`, or fetch one with `getBackup`.

### Restore from a backup

![Restore backup confirmation dialog](/images/docs/products/databases/postgresql/backups-restore.avif)

Restoring replaces the database's current data with the backup's contents. The database status moves to `restoring` and returns to `ready` when the restore completes; connections are unavailable during the restore.

In the Console, open the **Actions** menu on a backup row, click **Restore**, then acknowledge that the current data will be replaced. You can also restore with a Server SDK:

```server-nodejs
import { Client, Postgresql } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const postgresql = new Postgresql(client);

await postgresql.createRestoration({
    databaseId: '<DATABASE_ID>',
    type: 'backup',
    backupId: '<BACKUP_ID>',
});
```
```server-deno
import { Client, Postgresql } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const postgresql = new Postgresql(client);

await postgresql.createRestoration({
    databaseId: '<DATABASE_ID>',
    type: 'backup',
    backupId: '<BACKUP_ID>',
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Postgresql;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$postgresql = new Postgresql($client);

$postgresql->createRestoration(
    databaseId: '<DATABASE_ID>',
    type: 'backup',
    backupId: '<BACKUP_ID>',
);
```
```server-python
from appwrite.client import Client
from appwrite.services.postgresql import Postgresql

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

postgresql = Postgresql(client)

postgresql.create_restoration(
    database_id='<DATABASE_ID>',
    type='backup',
    backup_id='<BACKUP_ID>',
)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

postgresql = Postgresql.new(client)

postgresql.create_restoration(
    database_id: '<DATABASE_ID>',
    type: 'backup',
    backup_id: '<BACKUP_ID>',
)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Postgresql postgresql = new Postgresql(client);

await postgresql.CreateRestoration(
    databaseId: "<DATABASE_ID>",
    type: "backup",
    backupId: "<BACKUP_ID>"
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Postgresql postgresql = Postgresql(client);

await postgresql.createRestoration(
    databaseId: '<DATABASE_ID>',
    type: 'backup',
    backupId: '<BACKUP_ID>',
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Postgresql

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val postgresql = Postgresql(client)

postgresql.createRestoration(
    databaseId = "<DATABASE_ID>",
    type = "backup",
    backupId = "<BACKUP_ID>",
)
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let postgresql = Postgresql(client)

_ = try await postgresql.createRestoration(
    databaseId: "<DATABASE_ID>",
    type: "backup",
    backupId: "<BACKUP_ID>"
)
```
```server-go
package main

import (
    "github.com/appwrite/sdk-for-go/appwrite"
    "github.com/appwrite/sdk-for-go/postgresql"
)

func main() {
    client := appwrite.NewClient(
        appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"),
        appwrite.WithProject("<PROJECT_ID>"),
        appwrite.WithKey("<YOUR_API_KEY>"),
    )

    service := appwrite.NewPostgresql(client)

    _, err := service.CreateRestoration(
        "<DATABASE_ID>",
        postgresql.WithCreateRestorationType("backup"),
        postgresql.WithCreateRestorationBackupId("<BACKUP_ID>"),
    )
    if err != nil {
        panic(err)
    }
}
```
```server-rust
use appwrite::client::Client;
use appwrite::services::postgresql::Postgresql;

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

    let postgresql = Postgresql::new(&client);

    postgresql.create_restoration("<DATABASE_ID>", Some("backup"), Some("<BACKUP_ID>"), None).await?;

    Ok(())
}
```
```bash
curl -X POST \
  -H "X-Appwrite-Project: <PROJECT_ID>" \
  -H "X-Appwrite-Key: <API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{
      "type": "backup",
      "backupId": "<BACKUP_ID>"
  }' \
  https://<REGION>.cloud.appwrite.io/v1/postgresql/<DATABASE_ID>/restorations
```

Track progress with `getRestoration` or the database status.

**Restores overwrite current data**

Everything written after the backup was taken is lost when you restore it. If you need the current state too, take a manual backup first, or use a [branch](/docs/products/databases/postgresql/branches) to inspect data without touching the live database.

### Point-in-time recovery

![Point-in-time recovery settings](/images/docs/products/databases/postgresql/settings-pitr.avif)

Scheduled backups recover to fixed snapshots. Point-in-time recovery (PITR) continuously archives the write-ahead log, so you can restore to any moment inside the retention window, for example the second before a bad migration ran.

Enable PITR when creating the database, from **Settings** > **PITR** in the Console, or through the API:

```server-nodejs
import { Client, Postgresql } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const postgresql = new Postgresql(client);

await postgresql.update({
    databaseId: '<DATABASE_ID>',
    pitr: true,
    pitrRetentionDays: 7,
});
```
```server-deno
import { Client, Postgresql } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const postgresql = new Postgresql(client);

await postgresql.update({
    databaseId: '<DATABASE_ID>',
    pitr: true,
    pitrRetentionDays: 7,
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Postgresql;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$postgresql = new Postgresql($client);

$postgresql->update(
    databaseId: '<DATABASE_ID>',
    pitr: true,
    pitrRetentionDays: 7,
);
```
```server-python
from appwrite.client import Client
from appwrite.services.postgresql import Postgresql

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

postgresql = Postgresql(client)

postgresql.update(
    database_id='<DATABASE_ID>',
    pitr=True,
    pitr_retention_days=7,
)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

postgresql = Postgresql.new(client)

postgresql.update(
    database_id: '<DATABASE_ID>',
    pitr: true,
    pitr_retention_days: 7,
)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Postgresql postgresql = new Postgresql(client);

await postgresql.Update(
    databaseId: "<DATABASE_ID>",
    pitr: true,
    pitrRetentionDays: 7
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Postgresql postgresql = Postgresql(client);

await postgresql.update(
    databaseId: '<DATABASE_ID>',
    pitr: true,
    pitrRetentionDays: 7,
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Postgresql

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val postgresql = Postgresql(client)

postgresql.update(
    databaseId = "<DATABASE_ID>",
    pitr = true,
    pitrRetentionDays = 7,
)
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let postgresql = Postgresql(client)

_ = try await postgresql.update(
    databaseId: "<DATABASE_ID>",
    pitr: true,
    pitrRetentionDays: 7
)
```
```server-go
package main

import (
    "github.com/appwrite/sdk-for-go/appwrite"
    "github.com/appwrite/sdk-for-go/postgresql"
)

func main() {
    client := appwrite.NewClient(
        appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"),
        appwrite.WithProject("<PROJECT_ID>"),
        appwrite.WithKey("<YOUR_API_KEY>"),
    )

    service := appwrite.NewPostgresql(client)

    _, err := service.Update(
        "<DATABASE_ID>",
        postgresql.WithUpdatePitr(true),
        postgresql.WithUpdatePitrRetentionDays(7),
    )
    if err != nil {
        panic(err)
    }
}
```
```server-rust
use appwrite::client::Client;
use appwrite::services::postgresql::Postgresql;

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

    let postgresql = Postgresql::new(&client);

    postgresql.update("<DATABASE_ID>", None, None, None, None, None, None, None, None, Some(true), Some(7), None, None, None, None, None, None, None, None, None, None).await?;

    Ok(())
}
```
```bash
curl -X PATCH \
  -H "X-Appwrite-Project: <PROJECT_ID>" \
  -H "X-Appwrite-Key: <API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{
      "pitr": true,
      "pitrRetentionDays": 7
  }' \
  https://<REGION>.cloud.appwrite.io/v1/postgresql/<DATABASE_ID>
```

`pitrRetentionDays` accepts 1 to 35 days. PITR is billed as an add-on on top of your specification; see [pricing](/pricing).

#### Check the recovery window

You can retrieve the time range you can restore to, as an earliest and latest pair. Right after enabling PITR the call returns an error rather than an empty window, because continuous archiving has not captured its first segment yet.

```server-nodejs
import { Client, Postgresql } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const postgresql = new Postgresql(client);

const windows = await postgresql.getPitr({
    databaseId: '<DATABASE_ID>',
});
```
```server-deno
import { Client, Postgresql } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const postgresql = new Postgresql(client);

const windows = await postgresql.getPitr({
    databaseId: '<DATABASE_ID>',
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Postgresql;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$postgresql = new Postgresql($client);

$windows = $postgresql->getPitr(
    databaseId: '<DATABASE_ID>',
);
```
```server-python
from appwrite.client import Client
from appwrite.services.postgresql import Postgresql

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

postgresql = Postgresql(client)

windows = postgresql.get_pitr(
    database_id='<DATABASE_ID>',
)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

postgresql = Postgresql.new(client)

windows = postgresql.get_pitr(
    database_id: '<DATABASE_ID>',
)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Postgresql postgresql = new Postgresql(client);

var windows = await postgresql.GetPitr(
    databaseId: "<DATABASE_ID>"
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Postgresql postgresql = Postgresql(client);

final windows = await postgresql.getPitr(
    databaseId: '<DATABASE_ID>',
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Postgresql

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val postgresql = Postgresql(client)

val windows = postgresql.getPitr(
    databaseId = "<DATABASE_ID>",
)
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let postgresql = Postgresql(client)

let windows = try await postgresql.getPitr(
    databaseId: "<DATABASE_ID>"
)
```
```server-go
package main

import (
    "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("<YOUR_API_KEY>"),
    )

    service := appwrite.NewPostgresql(client)

    result, err := service.GetPitr("<DATABASE_ID>")
    if err != nil {
        panic(err)
    }
    _ = result
}
```
```server-rust
use appwrite::client::Client;
use appwrite::services::postgresql::Postgresql;

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

    let postgresql = Postgresql::new(&client);

    let windows = postgresql.get_pitr("<DATABASE_ID>").await?;

    Ok(())
}
```
```bash
curl -X GET \
  -H "X-Appwrite-Project: <PROJECT_ID>" \
  -H "X-Appwrite-Key: <API_KEY>" \
  https://<REGION>.cloud.appwrite.io/v1/postgresql/<DATABASE_ID>/pitr
```

#### Restore to a point in time

Pass an ISO 8601 datetime inside the recovery window:

```server-nodejs
import { Client, Postgresql } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const postgresql = new Postgresql(client);

await postgresql.createRestoration({
    databaseId: '<DATABASE_ID>',
    type: 'pitr',
    targetTime: '2026-01-01T00:00:00.000+00:00',
});
```
```server-deno
import { Client, Postgresql } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const postgresql = new Postgresql(client);

await postgresql.createRestoration({
    databaseId: '<DATABASE_ID>',
    type: 'pitr',
    targetTime: '2026-01-01T00:00:00.000+00:00',
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Postgresql;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$postgresql = new Postgresql($client);

$postgresql->createRestoration(
    databaseId: '<DATABASE_ID>',
    type: 'pitr',
    targetTime: '2026-01-01T00:00:00.000+00:00',
);
```
```server-python
from appwrite.client import Client
from appwrite.services.postgresql import Postgresql

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

postgresql = Postgresql(client)

postgresql.create_restoration(
    database_id='<DATABASE_ID>',
    type='pitr',
    target_time='2026-01-01T00:00:00.000+00:00',
)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

postgresql = Postgresql.new(client)

postgresql.create_restoration(
    database_id: '<DATABASE_ID>',
    type: 'pitr',
    target_time: '2026-01-01T00:00:00.000+00:00',
)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Postgresql postgresql = new Postgresql(client);

await postgresql.CreateRestoration(
    databaseId: "<DATABASE_ID>",
    type: "pitr",
    targetTime: "2026-01-01T00:00:00.000+00:00"
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Postgresql postgresql = Postgresql(client);

await postgresql.createRestoration(
    databaseId: '<DATABASE_ID>',
    type: 'pitr',
    targetTime: '2026-01-01T00:00:00.000+00:00',
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Postgresql

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val postgresql = Postgresql(client)

postgresql.createRestoration(
    databaseId = "<DATABASE_ID>",
    type = "pitr",
    targetTime = "2026-01-01T00:00:00.000+00:00",
)
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let postgresql = Postgresql(client)

_ = try await postgresql.createRestoration(
    databaseId: "<DATABASE_ID>",
    type: "pitr",
    targetTime: "2026-01-01T00:00:00.000+00:00"
)
```
```server-go
package main

import (
    "github.com/appwrite/sdk-for-go/appwrite"
    "github.com/appwrite/sdk-for-go/postgresql"
)

func main() {
    client := appwrite.NewClient(
        appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"),
        appwrite.WithProject("<PROJECT_ID>"),
        appwrite.WithKey("<YOUR_API_KEY>"),
    )

    service := appwrite.NewPostgresql(client)

    _, err := service.CreateRestoration(
        "<DATABASE_ID>",
        postgresql.WithCreateRestorationType("pitr"),
        postgresql.WithCreateRestorationTargetTime("2026-01-01T00:00:00.000+00:00"),
    )
    if err != nil {
        panic(err)
    }
}
```
```server-rust
use appwrite::client::Client;
use appwrite::services::postgresql::Postgresql;

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

    let postgresql = Postgresql::new(&client);

    postgresql.create_restoration("<DATABASE_ID>", Some("pitr"), None, Some("2026-01-01T00:00:00.000+00:00")).await?;

    Ok(())
}
```
```bash
curl -X POST \
  -H "X-Appwrite-Project: <PROJECT_ID>" \
  -H "X-Appwrite-Key: <API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{
      "type": "pitr",
      "targetTime": "2026-01-01T00:00:00.000+00:00"
  }' \
  https://<REGION>.cloud.appwrite.io/v1/postgresql/<DATABASE_ID>/restorations
```

Like a backup restore, a PITR restore is in-place: the database is unavailable while restoring and everything after the target time is discarded.

### Limits

| Limit | Value |
|------------------|-----------------|
| Backup retention | 7 days on Pro, 1-36500 days on Scale |
| PITR retention | 1 - 35 days |
| Backup policies | 1 on Pro, unlimited on Scale |

---

## Branches

URL: https://appwrite.io/docs/products/databases/postgresql/branches

A branch is a short-lived, isolated copy of your database. It has its own endpoint and reuses the parent's credentials, because it is a snapshot copy of the parent's storage volume taken at a point in time. Branches are not replicas: once created, they diverge from the parent and never sync back.

**Branches do not merge back**

There is no branch merge operation. Use a branch to validate a migration, data repair, or application change, then intentionally cut application traffic over to the validated database or copy the data you want back with engine-native tools. Appwrite does not reconcile two diverged database histories for you.

Use cases:

- **Preview environments**: one branch per pull request, destroyed when the PR closes
- **Test migrations**: apply a destructive `ALTER` against the branch first, observe the behavior, then run it against the source
- **Reproduce a bug**: branch the database, attach a debugger, throw the branch away when done
- **Heavy analytical queries**: `EXPLAIN ANALYZE` experiments against a branch cannot slow down the primary

### How it works

Creating a branch snapshots the parent's storage volume and provisions a branch instance from that snapshot, on the same engine version, with its own isolated storage. PostgreSQL snapshots are crash-consistent, so the parent is never frozen and takes no write pause. Branch compute is fixed and lightweight, enough to validate a change rather than carry production load, and is not configurable. Provisioning is asynchronous and usually takes a few minutes, because the storage provider finalizes the snapshot before the branch instance starts.

### Create a branch

```server-nodejs
import { Client, Postgresql } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const postgresql = new Postgresql(client);

await postgresql.createBranch({
    databaseId: '<DATABASE_ID>',
    branchId: 'preview',
    ttl: 86400,
});
```
```server-deno
import { Client, Postgresql } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const postgresql = new Postgresql(client);

await postgresql.createBranch({
    databaseId: '<DATABASE_ID>',
    branchId: 'preview',
    ttl: 86400,
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Postgresql;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$postgresql = new Postgresql($client);

$postgresql->createBranch(
    databaseId: '<DATABASE_ID>',
    branchId: 'preview',
    ttl: 86400,
);
```
```server-python
from appwrite.client import Client
from appwrite.services.postgresql import Postgresql

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

postgresql = Postgresql(client)

postgresql.create_branch(
    database_id='<DATABASE_ID>',
    branch_id='preview',
    ttl=86400,
)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

postgresql = Postgresql.new(client)

postgresql.create_branch(
    database_id: '<DATABASE_ID>',
    branch_id: 'preview',
    ttl: 86400,
)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Postgresql postgresql = new Postgresql(client);

await postgresql.CreateBranch(
    databaseId: "<DATABASE_ID>",
    branchId: "preview",
    ttl: 86400
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Postgresql postgresql = Postgresql(client);

await postgresql.createBranch(
    databaseId: '<DATABASE_ID>',
    branchId: 'preview',
    ttl: 86400,
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Postgresql

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val postgresql = Postgresql(client)

postgresql.createBranch(
    databaseId = "<DATABASE_ID>",
    branchId = "preview",
    ttl = 86400,
)
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let postgresql = Postgresql(client)

_ = try await postgresql.createBranch(
    databaseId: "<DATABASE_ID>",
    branchId: "preview",
    ttl: 86400
)
```
```server-go
package main

import (
    "github.com/appwrite/sdk-for-go/appwrite"
    "github.com/appwrite/sdk-for-go/postgresql"
)

func main() {
    client := appwrite.NewClient(
        appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"),
        appwrite.WithProject("<PROJECT_ID>"),
        appwrite.WithKey("<YOUR_API_KEY>"),
    )

    service := appwrite.NewPostgresql(client)

    _, err := service.CreateBranch(
        "<DATABASE_ID>",
        postgresql.WithCreateBranchBranchId("preview"),
        postgresql.WithCreateBranchTtl(86400),
    )
    if err != nil {
        panic(err)
    }
}
```
```server-rust
use appwrite::client::Client;
use appwrite::services::postgresql::Postgresql;

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

    let postgresql = Postgresql::new(&client);

    postgresql.create_branch("<DATABASE_ID>", Some("preview"), Some(86400)).await?;

    Ok(())
}
```
```bash
curl -X POST \
  -H "X-Appwrite-Project: <PROJECT_ID>" \
  -H "X-Appwrite-Key: <API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{
      "branchId": "preview",
      "ttl": 86400
  }' \
  https://<REGION>.cloud.appwrite.io/v1/postgresql/<DATABASE_ID>/branches
```

Both fields are optional:

| Field | Default | Purpose |
|------------|--------------------|-----------------------------------------------------------------------|
| `branchId` | auto-generated | Custom ID (`a-z`, `A-Z`, `0-9`, `.`, `-`, `_`, max 36 chars) |
| `ttl` | `86400` (24 hours) | Lifetime in seconds before the branch expires (min 300, max 604800) |

The call is asynchronous and returns immediately while the branch provisions in the background. Every branch has a TTL. Leaving `ttl` out gives you 24 hours, and the longest branch you can create is 7 days, so a branch is never a permanent resource.

The value you send as `branchId` is stored as the branch name. The list response returns a separate generated `branchId`, so look up a branch by its `name` rather than by the value you passed.

### List branches and connect

Each entry carries its metadata and connection details, so there is no separate credentials call:

```server-nodejs
import { Client, Postgresql } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const postgresql = new Postgresql(client);

const branches = await postgresql.listBranches({
    databaseId: '<DATABASE_ID>',
});
```
```server-deno
import { Client, Postgresql } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const postgresql = new Postgresql(client);

const branches = await postgresql.listBranches({
    databaseId: '<DATABASE_ID>',
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Postgresql;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$postgresql = new Postgresql($client);

$branches = $postgresql->listBranches(
    databaseId: '<DATABASE_ID>',
);
```
```server-python
from appwrite.client import Client
from appwrite.services.postgresql import Postgresql

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

postgresql = Postgresql(client)

branches = postgresql.list_branches(
    database_id='<DATABASE_ID>',
)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

postgresql = Postgresql.new(client)

branches = postgresql.list_branches(
    database_id: '<DATABASE_ID>',
)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Postgresql postgresql = new Postgresql(client);

var branches = await postgresql.ListBranches(
    databaseId: "<DATABASE_ID>"
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Postgresql postgresql = Postgresql(client);

final branches = await postgresql.listBranches(
    databaseId: '<DATABASE_ID>',
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Postgresql

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val postgresql = Postgresql(client)

val branches = postgresql.listBranches(
    databaseId = "<DATABASE_ID>",
)
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let postgresql = Postgresql(client)

let branches = try await postgresql.listBranches(
    databaseId: "<DATABASE_ID>"
)
```
```server-go
package main

import (
    "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("<YOUR_API_KEY>"),
    )

    service := appwrite.NewPostgresql(client)

    result, err := service.ListBranches("<DATABASE_ID>")
    if err != nil {
        panic(err)
    }
    _ = result
}
```
```server-rust
use appwrite::client::Client;
use appwrite::services::postgresql::Postgresql;

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

    let postgresql = Postgresql::new(&client);

    let branches = postgresql.list_branches("<DATABASE_ID>").await?;

    Ok(())
}
```
```bash
curl -X GET \
  -H "X-Appwrite-Project: <PROJECT_ID>" \
  -H "X-Appwrite-Key: <API_KEY>" \
  https://<REGION>.cloud.appwrite.io/v1/postgresql/<DATABASE_ID>/branches
```

A branch gets its own hostname, and reuses the parent's username and password because it is a snapshot copy of the parent's storage. The port is the standard `5432`; branches have no connection pooler. Connect with the branch's `connectionString` straight from the response:

```bash
psql "<branch connectionString>"
```

### Delete a branch

Deleting a branch removes the branch's instance, its storage volume, and the underlying snapshot. There is no soft delete: once the branch is gone, the data is gone.

```server-nodejs
import { Client, Postgresql } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const postgresql = new Postgresql(client);

await postgresql.deleteBranch({
    databaseId: '<DATABASE_ID>',
    branchId: 'preview',
});
```
```server-deno
import { Client, Postgresql } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const postgresql = new Postgresql(client);

await postgresql.deleteBranch({
    databaseId: '<DATABASE_ID>',
    branchId: 'preview',
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Postgresql;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$postgresql = new Postgresql($client);

$postgresql->deleteBranch(
    databaseId: '<DATABASE_ID>',
    branchId: 'preview',
);
```
```server-python
from appwrite.client import Client
from appwrite.services.postgresql import Postgresql

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

postgresql = Postgresql(client)

postgresql.delete_branch(
    database_id='<DATABASE_ID>',
    branch_id='preview',
)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

postgresql = Postgresql.new(client)

postgresql.delete_branch(
    database_id: '<DATABASE_ID>',
    branch_id: 'preview',
)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Postgresql postgresql = new Postgresql(client);

await postgresql.DeleteBranch(
    databaseId: "<DATABASE_ID>",
    branchId: "preview"
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Postgresql postgresql = Postgresql(client);

await postgresql.deleteBranch(
    databaseId: '<DATABASE_ID>',
    branchId: 'preview',
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Postgresql

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val postgresql = Postgresql(client)

postgresql.deleteBranch(
    databaseId = "<DATABASE_ID>",
    branchId = "preview",
)
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let postgresql = Postgresql(client)

_ = try await postgresql.deleteBranch(
    databaseId: "<DATABASE_ID>",
    branchId: "preview"
)
```
```server-go
package main

import (
    "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("<YOUR_API_KEY>"),
    )

    service := appwrite.NewPostgresql(client)

    _, err := service.DeleteBranch("<DATABASE_ID>", "preview")
    if err != nil {
        panic(err)
    }
}
```
```server-rust
use appwrite::client::Client;
use appwrite::services::postgresql::Postgresql;

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

    let postgresql = Postgresql::new(&client);

    postgresql.delete_branch("<DATABASE_ID>", "preview").await?;

    Ok(())
}
```
```bash
curl -X DELETE \
  -H "X-Appwrite-Project: <PROJECT_ID>" \
  -H "X-Appwrite-Key: <API_KEY>" \
  https://<REGION>.cloud.appwrite.io/v1/postgresql/<DATABASE_ID>/branches/preview
```

### Billing

A branch is billed for the parent's full provisioned storage from the moment it is created, pro-rated for how long the branch exists. Divergence is not measured, so a branch you never write to costs the same as one you rewrite completely. Short-lived branches are therefore much cheaper than long-lived ones. There is no separate branch line item, branches roll into your regular database storage totals.

### Use case: a development copy of production

Branches also separate daily development from production without maintaining seed scripts. Create a branch from the production database and point local and staging environments at the branch's hostname. Developers query production-shaped data, and every write stays on the branch, so production is never at risk from a bad migration or a careless `DELETE`.

Because a branch expires after 7 days at most, treat this as a recurring refresh rather than a standing environment. Delete the branch and create a new one with the same name on a schedule that suits your team. Each new branch starts from the parent's current state, which also keeps the development data from drifting far from production.

### Use case: per-PR preview database

A CI pipeline that branches on every pull request and tears down on close:

```yaml
name: preview-database

on:
  pull_request:
    types: [opened, reopened, closed]

jobs:
  branch:
    if: github.event.action != 'closed'
    runs-on: ubuntu-latest
    steps:
      - name: Create branch
        run: |
          curl -X POST \
            -H "X-Appwrite-Project: ${{ vars.APPWRITE_PROJECT_ID }}" \
            -H "X-Appwrite-Key: ${{ secrets.APPWRITE_API_KEY }}" \
            -H "Content-Type: application/json" \
            -d '{"branchId": "pr-${{ github.event.number }}", "ttl": 604800}' \
            https://<REGION>.cloud.appwrite.io/v1/postgresql/${{ vars.DATABASE_ID }}/branches

  teardown:
    if: github.event.action == 'closed'
    runs-on: ubuntu-latest
    steps:
      - name: Delete branch
        run: |
          curl -X DELETE \
            -H "X-Appwrite-Project: ${{ vars.APPWRITE_PROJECT_ID }}" \
            -H "X-Appwrite-Key: ${{ secrets.APPWRITE_API_KEY }}" \
            https://<REGION>.cloud.appwrite.io/v1/postgresql/${{ vars.DATABASE_ID }}/branches/pr-${{ github.event.number }}
```

Branch creation does not deduplicate by name. Calling it twice with the same `branchId` provisions a second branch, so reopening a pull request leaves you with two. Delete the existing branch before recreating it, or list branches first and skip creation when one already matches.

---

## Security and access control

URL: https://appwrite.io/docs/products/databases/postgresql/concepts/access-control

PostgreSQL controls access in three layers:

- A **role** is an identity that can connect.
- **Privileges** decide what a role may do to each table.
- **Row-level security** narrows that further, to which rows.

Your database's primary `admin` role can create additional roles, so you can give every service and teammate exactly the access it needs instead of sharing one all-powerful login.

### Setup

The examples below protect a documents table whose rows belong to different owners. Create and seed it as `admin`, the role your database comes with:

```sql
CREATE TABLE documents (
    id    bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    owner text NOT NULL DEFAULT current_user,
    title text NOT NULL
);

INSERT INTO documents (owner, title)
VALUES ('app_ada', 'Ada plan'), ('app_grace', 'Grace notes');
```

### Create roles

A role with `LOGIN` and a password is what other databases call a user:

```sql
CREATE ROLE reporting LOGIN PASSWORD 'a-strong-password';
CREATE ROLE app_ada   LOGIN PASSWORD 'pw-ada';
CREATE ROLE app_grace LOGIN PASSWORD 'pw-grace';
```

New roles can connect but hold no privileges on your tables until granted some. You can also create and manage roles from the **Roles** tab of your database in the Appwrite Console; see [Database roles](/docs/products/databases/postgresql/connections#roles).

### Grant privileges

Privileges are per table (or view, sequence, schema) and per action:

```sql
GRANT SELECT ON documents TO reporting;
GRANT SELECT, INSERT, UPDATE, DELETE ON documents TO app_ada, app_grace;
```

| Privilege | Allows |
| --- | --- |
| `SELECT` | Read rows |
| `INSERT` / `UPDATE` / `DELETE` | Write rows |
| `USAGE` (on schema) | Access objects inside a schema |
| `CREATE` (on schema) | Create tables in a schema |
| `ALL` | Everything applicable to the object |

`REVOKE` reverses any grant: `REVOKE INSERT ON documents FROM app_ada;`. To cover tables that don't exist yet, set default privileges once: `ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO reporting;`.

Connecting as `reporting` now allows reads but rejects writes:

```sql
INSERT INTO documents (title) VALUES ('nope');
```

```text
ERROR:  permission denied for table documents
```

This is the pattern for a dashboard, BI tool, or analyst account: a dedicated role that physically cannot modify data.

### Row-level security

Privileges gate whole tables. Row-level security (RLS) filters individual rows through policies, enforced by the database no matter how the query is written. Enable it and define who sees what:

```sql
ALTER TABLE documents ENABLE ROW LEVEL SECURITY;

CREATE POLICY tenant_isolation ON documents
    USING (owner = current_user)          -- which rows are visible
    WITH CHECK (owner = current_user);    -- which rows may be written
```

`USING` filters reads, updates, and deletes; `WITH CHECK` validates new or modified rows. With the policy in place, connect as `app_ada` and the table simply appears to contain only Ada's rows:

```sql
SELECT owner, title FROM documents;
```

```text
  owner  |  title
---------+----------
 app_ada | Ada plan
```

The `DEFAULT current_user` on the `owner` column stamps new rows automatically, and the policy blocks writing rows for anyone else:

```sql
INSERT INTO documents (title) VALUES ('Ada draft');           -- works, owner = app_ada
INSERT INTO documents (owner, title) VALUES ('app_grace', 'forged');
```

```text
ERROR:  new row violates row-level security policy for table "documents"
```

A cross-tenant `UPDATE ... WHERE owner = 'app_grace'` doesn't error; it matches zero visible rows and does nothing. The application can't leak what it can't see.

**Table owners bypass RLS**

RLS does not apply to a table's owner, so `admin` sees every row in the tables it owns, which is usually what you want for migrations and support tooling. `admin` is not exempt in general: it is created without `BYPASSRLS`, so policies do apply to it on tables owned by another role. To subject the owner to policies too, run `ALTER TABLE documents FORCE ROW LEVEL SECURITY;`.

RLS also composes with privileges rather than replacing them: `reporting` can still `SELECT`, but sees zero rows because no row matches `owner = 'reporting'`. Add a second, permissive policy for roles that legitimately need everything:

```sql
CREATE POLICY reporting_reads_all ON documents
    FOR SELECT TO reporting
    USING (true);
```

### Multi-tenant patterns

One role per end user only scales so far. The common production pattern uses one application role plus a session variable the app sets per request. Permissive policies on a table combine with `OR`, so replace the per-role policy instead of stacking the two:

```sql
DROP POLICY tenant_isolation ON documents;

CREATE POLICY tenant_by_setting ON documents
    USING (owner = current_setting('app.current_tenant', true))
    WITH CHECK (owner = current_setting('app.current_tenant', true));
```

Set the variable and run the query inside one explicit transaction, with `set_config`'s third argument set to `true` so the setting is transaction-local:

```sql
BEGIN;
SELECT set_config('app.current_tenant', 'app_ada', true);
SELECT * FROM documents;
COMMIT;
```

This matters because connections reach the database through a pooler running in transaction mode by default. A session-level `SET` is not guaranteed to still apply on the next statement, since that statement can land on a different backend connection. Scoping the setting to a transaction keeps it with the queries it protects. If you need session-level settings to persist across statements, switch the pooler to session mode or connect on the direct port.

Whichever variant you choose, the isolation lives in the database: a forgotten `WHERE` clause in application code returns no one else's data instead of everyone's.

### Use Appwrite Auth as the tenant identity

Native PostgreSQL has no built-in link to Appwrite's permission system. [Permissions](/docs/advanced/security/permissions) apply to Appwrite databases, not to the raw engine. You can still key row-level security on Appwrite users: your backend verifies the caller's Appwrite session and sets `app.current_tenant` to the verified user ID before running queries.

The client sends its session as a [JWT](/docs/products/auth/jwt). The backend verifies the JWT against Appwrite, then scopes the connection to the verified user:

```server-nodejs
import { Client, Account } from 'node-appwrite';
import pg from 'pg';

const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL });

export async function listDocuments(jwt) {
    // Verify the Appwrite session. Throws if the JWT is invalid or expired.
    const client = new Client()
        .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
        .setProject('<PROJECT_ID>')
        .setJWT(jwt);
    const user = await new Account(client).get();

    const conn = await pool.connect();
    try {
        // Scope the setting to the transaction so it stays with the query.
        await conn.query('BEGIN');
        await conn.query("SELECT set_config('app.current_tenant', $1, true)", [user.$id]);
        const result = await conn.query('SELECT * FROM documents');
        await conn.query('COMMIT');
        return result.rows;
    } catch (err) {
        await conn.query('ROLLBACK');
        throw err;
    } finally {
        conn.release();
    }
}
```

With `owner` columns storing Appwrite user IDs, the `tenant_by_setting` policy above isolates rows per Appwrite user. The backend only translates a verified Appwrite identity into a session variable; the database enforces the isolation.

### What the primary role can do

The `admin` role you receive with the database owns the database and the `public` schema, and can create roles (`CREATEROLE`) and databases (`CREATEDB`). It is not a superuser: instance-level settings, replication, and other managed responsibilities stay with the platform. Network-level restrictions such as IP allowlists are configured separately; see [Network security](/docs/products/databases/postgresql/network-security).

---

## Data modeling and normalization

URL: https://appwrite.io/docs/products/databases/postgresql/concepts/data-modeling

Data modeling decides where each fact lives. Normalization is the discipline of storing every fact exactly once, so it can't contradict itself. This page walks a flat spreadsheet-style table through the normal forms, then covers when to deliberately break the rules, and how views let you reshape data without duplicating it.

### The problem with one big table

Start with an orders table designed the way a spreadsheet would be:

```sql
CREATE TABLE orders_flat (
    order_id       bigint,
    customer_name  text,
    customer_email text,
    product_names  text,      -- 'Keyboard, Mouse'
    product_prices text       -- '79.00, 29.50'
);

INSERT INTO orders_flat VALUES
    (1, 'Ada', 'ada@example.com', 'Keyboard, Mouse', '79.00, 29.50'),
    (2, 'Ada', 'ada@example.com', 'Monitor', '349.99'),
    (3, 'Grace', 'grace@example.com', 'Mouse', '29.50');
```

Every design flaw here causes a concrete failure:

- Ada's email is stored twice. Update one row and not the other, and the database now disagrees with itself. This is an **update anomaly**.
- Products only exist inside order rows. Delete order 2 and the Monitor, its price, everything, vanishes. A **delete anomaly**.
- A product has no row of its own, so recording one before anyone orders it means a row with empty order fields. An **insert anomaly**.
- "Which orders contain a Mouse?" requires string parsing instead of a `WHERE` clause.

### First normal form: one value per cell

First normal form (1NF) requires each column to hold a single atomic value, no comma-separated lists. Split the line items into rows:

```sql
CREATE TABLE order_lines_1nf (
    order_id       bigint,
    customer_name  text,
    customer_email text,
    product_name   text,
    product_price  numeric(10, 2)
);

INSERT INTO order_lines_1nf VALUES
    (1, 'Ada', 'ada@example.com', 'Keyboard', 79.00),
    (1, 'Ada', 'ada@example.com', 'Mouse', 29.50),
    (2, 'Ada', 'ada@example.com', 'Monitor', 349.99),
    (3, 'Grace', 'grace@example.com', 'Mouse', 29.50);
```

Now `WHERE product_name = 'Mouse'` works. But the duplication got worse: Ada's email appears three times, and the Mouse's price twice.

### Second normal form: columns depend on the whole key

Second normal form (2NF) applies to tables whose key spans more than one column. Every non-key column must depend on the whole key, not on part of it. The key here is implicitly `(order_id, product_name)`, and `product_price` depends only on the product, not on which order it is in. The fix is to move the price into a products table, where the product alone is the key.

### Third normal form: no column depends on another non-key column

Third normal form (3NF) says no column may depend on a non-key column. `customer_email` depends on the customer, not the order, so it belongs in a customers table keyed by customer.

Applying both produces the standard shape, where every fact has exactly one home:

```sql
CREATE TABLE customers (
    id    bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    name  text NOT NULL,
    email text NOT NULL UNIQUE
);

CREATE TABLE products (
    id    bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    name  text NOT NULL,
    price numeric(10, 2) NOT NULL
);

CREATE TABLE orders (
    id          bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    customer_id bigint NOT NULL REFERENCES customers (id),
    created_at  timestamptz NOT NULL DEFAULT now()
);

CREATE TABLE order_items (
    order_id   bigint NOT NULL REFERENCES orders (id),
    product_id bigint NOT NULL REFERENCES products (id),
    quantity   integer NOT NULL DEFAULT 1,
    unit_price numeric(10, 2) NOT NULL,   -- price at time of order, see below
    PRIMARY KEY (order_id, product_id)
);

INSERT INTO customers (name, email) VALUES
    ('Ada', 'ada@example.com'), ('Grace', 'grace@example.com');
INSERT INTO products (name, price) VALUES
    ('Keyboard', 79.00), ('Mouse', 29.50), ('Monitor', 349.99);
INSERT INTO orders (customer_id) VALUES (1), (1), (2);
INSERT INTO order_items (order_id, product_id, quantity, unit_price) VALUES
    (1, 1, 1, 79.00), (1, 2, 1, 29.50), (2, 3, 1, 349.99), (3, 2, 1, 29.50);
```

Changing Ada's email is now a one-row `UPDATE` that cannot leave a stale copy anywhere. The anomalies are gone because duplication is gone.

Notice `unit_price` in `order_items` looks like duplication but isn't: the price *at the time of the order* is a different fact from the product's *current* price, and both deserve a home. Normalization is about one home per fact, not zero copies of anything that looks similar.

#### Boyce-Codd normal form

Boyce-Codd normal form (BCNF) is a stricter 3NF: whenever one column determines another, the determining column must be a candidate key. The difference from 3NF only shows up in tables with overlapping composite candidate keys, for example a bookings table `(room, time_slot, teacher)` where each teacher always teaches in one room. That table is in 3NF, yet the teacher-to-room fact still duplicates per booking; BCNF moves it into its own table. Schemas like this are rare, and a schema in 3NF almost always satisfies BCNF too.

#### The normal forms at a glance

| Form | Rule | Duplication it removes |
| --- | --- | --- |
| 1NF | One value per cell, no repeating groups | Lists packed into a single cell |
| 2NF | Every column depends on the whole key | Facts about part of a composite key, copied into every row |
| 3NF | No column depends on a non-key column | Facts about another column, copied into every row |
| BCNF | Every determining column is a candidate key | Dependencies between overlapping composite keys that 3NF misses |

Higher forms (4NF, 5NF) deal with multi-valued and join dependencies and rarely change a practical schema; the forms above cover day-to-day design.

### Reading a normalized schema

Normalized data comes back together with joins:

```sql
SELECT o.id AS order_id, c.name, sum(oi.quantity * oi.unit_price) AS total
FROM orders o
JOIN customers c ON c.id = o.customer_id
JOIN order_items oi ON oi.order_id = o.id
GROUP BY o.id, c.name
ORDER BY o.id;
```

```text
 order_id | name  | total
----------+-------+--------
        1 | Ada   | 108.50
        2 | Ada   | 349.99
        3 | Grace |  29.50
```

If the joins feel verbose, that's what views are for, not denormalization.

### Views: stored queries, not stored data

A view names a query so readers get the convenient shape without the data being duplicated:

```sql
CREATE VIEW order_summaries AS
SELECT o.id AS order_id, c.name AS customer, o.created_at,
       sum(oi.quantity * oi.unit_price) AS total
FROM orders o
JOIN customers c ON c.id = o.customer_id
JOIN order_items oi ON oi.order_id = o.id
GROUP BY o.id, c.name, o.created_at;

SELECT customer, total FROM order_summaries WHERE order_id = 1;
```

The view runs its query on every read, so it is always current. A **materialized view** stores the result physically and serves reads from the stored copy, trading freshness for speed on expensive aggregations:

```sql
CREATE MATERIALIZED VIEW customer_totals AS
SELECT c.name, sum(oi.quantity * oi.unit_price) AS lifetime_value
FROM customers c
JOIN orders o ON o.customer_id = c.id
JOIN order_items oi ON oi.order_id = o.id
GROUP BY c.name;

SELECT * FROM customer_totals ORDER BY lifetime_value DESC;

-- data changed? refresh on your schedule:
REFRESH MATERIALIZED VIEW customer_totals;
```

### When to denormalize

Denormalization deliberately duplicates a fact to make reads cheaper, accepting that your code must now keep the copies in sync. It's a real tool with a real cost, so it should be a measured response to a demonstrated problem, not a default:

- **A hot aggregate**, such as showing order totals on every page: store `orders.total` and update it when items change, rather than summing on every read.
- **An access-pattern mismatch**, such as a search page filtering on a joined column at scale.
- **Snapshot semantics**, like `unit_price` above, which isn't really denormalization: the historical value is its own fact.

Before duplicating data, check whether an index, a view, or a materialized view solves the read problem: indexes and views can never disagree with the source data, and a materialized view only lags until its next refresh, without needing any synchronization code of your own. When you do denormalize, keep the copies consistent inside one transaction, or with triggers, so a crash between the write and the sync can't leave them disagreeing. See [Transactions](/docs/products/databases/postgresql/concepts/transactions).

### JSON columns in a relational model

A `jsonb` column is controlled denormalization for attributes that vary per row, such as per-event metadata. The rule of thumb: anything you filter on, join on, or aggregate regularly deserves a real column with a real type and real constraints; the long tail can live in `jsonb`. See [Tables and data types](/docs/products/databases/postgresql/concepts/tables#json).

---

## Indexes

URL: https://appwrite.io/docs/products/databases/postgresql/concepts/indexes

An index is a lookup structure the database maintains next to a table so it can find rows without scanning everything; the default B-tree kind keeps its entries sorted. Reads get faster; writes pay a small tax to keep each index current. Knowing when an index helps, and how to confirm it's being used, is the highest-leverage performance skill in SQL.

### Setup

Index behavior only shows up with enough data on the table, so the setup seeds 100,000 users to give the query planner a real choice to make:

```sql
CREATE TABLE users (
    id         bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    email      text NOT NULL,
    country    text NOT NULL,
    created_at timestamptz NOT NULL
);

INSERT INTO users (email, country, created_at)
SELECT 'user' || n || '@example.com',
       (ARRAY['DE','US','IN','BR'])[1 + n % 4],
       now() - (n || ' minutes')::interval
FROM generate_series(1, 100000) AS n;

ANALYZE users;
```

`ANALYZE` refreshes the statistics the planner uses to estimate row counts. PostgreSQL runs it automatically in the background; running it manually after a bulk load just avoids waiting.

### How an index works

Table rows are stored in no order useful for searching an arbitrary column, so answering `WHERE email = '...'` without an index means reading every row and checking each one. The work grows with the table: ten times the rows, ten times the reads.

A **B-tree** (balanced tree) is the data structure indexes use to fix this. It keeps every value of the indexed column in **sorted order**, stored as a tree of small pages: the top page holds a few boundary values that direct the search into one of its child pages, each child narrows the range further, and the bottom layer, the leaves, holds the actual values with pointers back to their rows. Sorted order is what makes searching cheap:

- The database starts in the middle of the index, checks whether the target value sorts before or after that point, and discards the half that cannot contain it. Repeating this takes a handful of steps even on millions of rows.
- Equal values sit next to each other, so one descent lands on every matching entry at once.
- The same sorted order answers range conditions (`<`, `BETWEEN`) and returns rows already sorted for `ORDER BY`.

Indexes have a price: each one is a second copy of the column that must be kept correct, so every insert, update, and delete on the table also rewrites part of every index. They trade write work and disk space for read speed, which is why you add them for the queries you actually run rather than on every column.

### Reading EXPLAIN ANALYZE

![EXPLAIN ANALYZE output in the SQL editor](/images/docs/products/databases/postgresql/sql-explain.avif)

Before adding indexes, you need to see what the database is currently doing. Prefix any query with `EXPLAIN ANALYZE` and instead of returning its result, the database runs it and reports the plan it used:

```sql
EXPLAIN ANALYZE SELECT * FROM users WHERE email = 'user4242@example.com';
```

```text
 Seq Scan on users  (cost=0.00..2175.00 rows=1 width=40) (actual time=0.141..2.565 rows=1.00 loops=1)
   Filter: (email = 'user4242@example.com'::text)
   Rows Removed by Filter: 99999
```

Reading it line by line:

- **Seq Scan on users** is the chosen strategy: a sequential scan, reading the table start to finish.
- **Filter** is the condition checked against every row as it's read.
- **Rows Removed by Filter: 99999** is the waste: 100,000 rows read, 99,999 discarded, 1 returned.
- **actual time=0.141..2.565** is when the first and last row were produced, in milliseconds. The `cost` numbers are the planner's internal estimates for comparing candidate plans, not milliseconds.

Exact numbers will differ on your database; the plan shape is what matters, and this shape, an entire table read to return one row, is the signal that an index would help. Plain `EXPLAIN` without `ANALYZE` shows the plan without executing the query, which is safer for slow queries and writes.

### Create an index

`CREATE INDEX` builds the structure from the table's existing rows and keeps it maintained on every future write. Rerunning the earlier query shows the planner picking it up immediately:

```sql
CREATE INDEX idx_users_email ON users (email);

EXPLAIN ANALYZE SELECT * FROM users WHERE email = 'user4242@example.com';
```

```text
 Index Scan using idx_users_email on users  (cost=0.42..8.44 rows=1 width=40) (actual time=0.019..0.019 rows=1.00 loops=1)
   Index Cond: (email = 'user4242@example.com'::text)
```

The plan flipped to an `Index Scan`, and execution time dropped from milliseconds to microseconds.

One B-tree index serves several kinds of conditions on its column:

- equality: `=` and `IN`
- ranges: `<`, `>`, `BETWEEN`
- sorting: `ORDER BY`
- prefix patterns like `LIKE 'user42%'`, with a caveat: a normal index sorts text by human-language rules, which don't match how `LIKE` compares characters, so prefix searches ignore it. Adding the `text_pattern_ops` option when creating the index makes them work.

Primary keys and `UNIQUE` constraints create B-tree indexes automatically, so don't add duplicates for those columns.

### Composite indexes and column order

An index on multiple columns is sorted by the first column, then the second within it, like a phone book sorted by last name, then first name. Column order decides which queries it serves:

```sql
CREATE INDEX idx_users_email_created ON users (email, created_at);
```

This index answers `WHERE email = ...`, and `WHERE email = ... AND created_at > ...`, but a filter on `created_at` alone can't use the sorted order because entries for every email are interleaved:

```sql
EXPLAIN SELECT * FROM users WHERE created_at > now() - interval '1 day';
```

```text
 Seq Scan on users  (cost=0.00..2675.00 rows=1428 width=40)
   Filter: (created_at > (now() - '1 day'::interval))
```

Rule of thumb: put equality-filtered columns first and range-filtered columns last. If you also query `created_at` on its own, that's a separate index.

**Skip scan in PostgreSQL 18**

When the leading column has very few distinct values, PostgreSQL 18 can "skip scan" a composite index even if the query doesn't filter on the leading column. It helps in that narrow case, but designing column order for your queries remains the right approach.

### Covering indexes

A normal index lookup is two steps: find the matching entries in the index, then fetch each matching row from the table to read the other columns. That second step is the expensive part, and it's unnecessary when the query only asks for columns the index already stores.

This query selects only `email`, and the index contains `email`, so the answer comes straight out of the index:

```sql
EXPLAIN ANALYZE SELECT email FROM users WHERE email = 'user4242@example.com';
```

```text
 Index Only Scan using idx_users_email_created on users  (cost=0.42..4.44 rows=1 width=21) (actual time=0.041..0.041 rows=1.00 loops=1)
   Index Cond: (email = 'user4242@example.com'::text)
   Heap Fetches: 0
```

- **Index Only Scan** is the plan node for answering from the index alone.
- **Heap Fetches: 0** confirms the table was read zero times.

An index that satisfies a query this way is called a covering index. If a query also reads one extra column, you can add that column to the index with the `INCLUDE` option, which stores it alongside the entries without making it searchable.

### Partial indexes

A partial index covers only rows matching a condition. It's smaller and cheaper to maintain than a full index, ideal when queries always target the same slice:

```sql
CREATE INDEX idx_users_recent_de ON users (created_at) WHERE country = 'DE';
```

The planner considers this index only for queries whose `WHERE` clause implies the index's condition, so include the `country = 'DE'` filter in queries that should use it. Common uses: unshipped orders, active sessions, rows where a nullable column is set.

### Expression indexes

Indexing an expression makes queries on that expression fast, such as case-insensitive lookups:

```sql
CREATE INDEX idx_users_email_lower ON users (lower(email));

EXPLAIN SELECT * FROM users WHERE lower(email) = 'user4242@example.com';
```

```text
 Bitmap Heap Scan on users  (cost=16.29..828.37 rows=500 width=40)
   Recheck Cond: (lower(email) = 'user4242@example.com'::text)
   ->  Bitmap Index Scan on idx_users_email_lower  (cost=0.00..16.17 rows=500 width=0)
```

The query's expression must match the indexed expression exactly. A `Bitmap Index Scan` is another index-driven strategy: PostgreSQL collects matching row locations from the index first, then fetches them from the table in physical order.

### Beyond B-tree

PostgreSQL ships six index access methods. B-tree is the default and right for most columns; the others serve specific data shapes:

| Type | Use for |
| --- | --- |
| `btree` | Equality and ranges on scalar values (default) |
| `gin` | `jsonb` containment, arrays, full-text search |
| `gist` | Geometric data, ranges, exclusion constraints |
| `brin` | Huge append-only tables with naturally ordered data |
| `hash` | Equality only; rarely better than B-tree |
| `spgist` | Space-partitioned data such as prefixes |

### The cost of indexes

Every index consumes disk and slows every `INSERT`, `UPDATE`, and `DELETE` on the table, because each write updates each index. They add up quickly:

```sql
SELECT pg_size_pretty(pg_relation_size('users')) AS table_size,
       pg_size_pretty(pg_relation_size('idx_users_email')) AS index_size;
```

```text
 table_size | index_size
------------+------------
 7400 kB    | 3992 kB
```

One index here costs more than half the table's size again. Index the queries you actually run, verify each index earns its keep with `EXPLAIN`, and drop the ones that don't. To find slow queries worth indexing in the first place, see [Monitoring](/docs/products/databases/postgresql/monitoring).

---

## Joins and relationships

URL: https://appwrite.io/docs/products/databases/postgresql/concepts/joins

Relational databases keep each entity in its own table and connect them through **keys**. A join combines rows from two tables by matching values, usually a **foreign key** on one side against a **primary key** on the other. This page covers how to model the two relationship shapes you'll meet constantly, and the join types you'll use in practice.

### Setup

The examples below use two tables, customers and orders, where every order records which customer placed it. Create and seed them first:

```sql
CREATE TABLE customers (
    id   bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    name text NOT NULL
);

CREATE TABLE orders (
    id          bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    customer_id bigint NOT NULL REFERENCES customers (id),
    total       numeric(10, 2) NOT NULL
);

INSERT INTO customers (name) VALUES ('Ada'), ('Grace'), ('Linus');
INSERT INTO orders (customer_id, total) VALUES
    (1, 120.00), (1, 80.00), (2, 45.50);
```

Ada has two orders, Grace has one, and Linus has none.

### What a join actually does

A join takes two tables and produces a new, temporary table built from them in three steps:

1. Pair rows from one side with rows from the other.
2. Evaluate the **join condition** on each pair, here `o.customer_id = c.id`.
3. Keep the pairs where it's true; each one becomes a result row carrying the columns of both sides.

```text
customers               orders                        result of the join
id | name               id | customer_id | total     name  | total
 1 | Ada        ←──┬──   1 |           1 | 120.00    Ada   | 120.00
                   └──   2 |           1 |  80.00    Ada   |  80.00
 2 | Grace      ←─────   3 |           2 |  45.50    Grace |  45.50
 3 | Linus      (no order matches)
```

Two things fall out of this model:

- A row that matches several rows on the other side appears several times in the result: Ada shows up twice because two orders point at her.
- A row that matches nothing, like Linus, produces no pair at all, and what happens to such unmatched rows is exactly what distinguishes the join types below.

The database doesn't literally build every combination; indexes let it jump straight to matching rows, but the result is always as if it had.

### One-to-many relationships

One customer has many orders, so `orders` carries a `customer_id` column referencing `customers`. The `REFERENCES` clause creates a foreign key: the database rejects any `customer_id` that doesn't exist in `customers`:

```sql
INSERT INTO orders (customer_id, total) VALUES (99, 10.00);
```

```text
ERROR:  insert or update on table "orders" violates foreign key constraint "orders_customer_id_fkey"
DETAIL:  Key (customer_id)=(99) is not present in table "customers".
```

A foreign key also decides what happens when the parent row is deleted. Declare it with the behavior you want:

- `ON DELETE NO ACTION` (the default) blocks deleting a customer who still has orders.
- `ON DELETE CASCADE` deletes the customer's orders along with the customer.
- `ON DELETE SET NULL` keeps the orders but clears their `customer_id` (the column must be nullable).

### Inner joins

An inner join returns only rows that match on both sides:

```sql
SELECT c.name, o.total
FROM customers c
JOIN orders o ON o.customer_id = c.id
ORDER BY c.name, o.total;
```

```text
 name  | total
-------+--------
 Ada   |  80.00
 Ada   | 120.00
 Grace |  45.50
```

Linus disappears from the result because he has no matching order. `JOIN` and `INNER JOIN` are the same thing.

### Left joins

A left join keeps every row from the left table and fills the right side with `NULL` where nothing matches:

```sql
SELECT c.name, o.total
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id
ORDER BY c.name, o.total;
```

```text
 name  | total
-------+--------
 Ada   |  80.00
 Ada   | 120.00
 Grace |  45.50
 Linus |
```

This is the join for "all X, with their Y if any". Combined with aggregation it answers questions like order counts per customer, including zero:

```sql
SELECT c.name, count(o.id) AS orders
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id
GROUP BY c.name
ORDER BY orders DESC;
```

Note `count(o.id)`, not `count(*)`: counting a column from the right table skips the `NULL` produced for Linus, yielding 0 instead of 1.

`RIGHT JOIN` mirrors `LEFT JOIN` in the other direction. In practice, most people reorder the tables and use `LEFT JOIN` for consistency.

### Full joins

A full join keeps unmatched rows from both sides. It's useful for reconciliation, such as comparing two datasets that should agree:

```sql
CREATE TABLE imported_customers (id bigint, name text);
INSERT INTO imported_customers VALUES (1, 'Ada'), (4, 'Margaret');

SELECT c.name AS in_database, i.name AS in_import
FROM customers c
FULL JOIN imported_customers i ON i.id = c.id
ORDER BY c.name;
```

```text
 in_database | in_import
-------------+-----------
 Ada         | Ada
 Grace       |
 Linus       |
             | Margaret
```

### Cross joins

A cross join produces every combination of rows from both tables, with no matching condition. Use it to generate combinations, such as every product in every size:

```sql
SELECT c.name, s.size
FROM customers c
CROSS JOIN (VALUES ('S'), ('M'), ('L')) AS s (size)
ORDER BY c.name, s.size;
```

Three customers times three sizes returns nine rows. Be careful with large tables: the result size is the product of both row counts.

### Anti-joins: rows without a match

"Customers with no orders" is an anti-join. Two equivalent spellings:

```sql
SELECT name FROM customers c
WHERE NOT EXISTS (
    SELECT 1 FROM orders o WHERE o.customer_id = c.id
);

SELECT c.name
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id
WHERE o.id IS NULL;
```

Both return only Linus. Prefer `NOT EXISTS` over `NOT IN (SELECT ...)`: if the subquery ever returns a `NULL`, `NOT IN` returns no rows at all, and nothing reports an error.

### Many-to-many relationships

When both sides can have many of the other, such as orders and products, neither table can hold the foreign key. A junction table holds one row per connection:

```sql
CREATE TABLE products (
    id   bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    name text NOT NULL
);

CREATE TABLE order_items (
    order_id   bigint NOT NULL REFERENCES orders (id),
    product_id bigint NOT NULL REFERENCES products (id),
    quantity   integer NOT NULL DEFAULT 1,
    PRIMARY KEY (order_id, product_id)
);

INSERT INTO products (name) VALUES ('Keyboard'), ('Mouse');
INSERT INTO order_items (order_id, product_id, quantity) VALUES
    (1, 1, 1), (1, 2, 2), (2, 2, 1);
```

The composite primary key `(order_id, product_id)` prevents duplicate connections, and the junction table is the natural home for relationship attributes like `quantity`. Traversing the relationship is two joins:

```sql
SELECT o.id AS order_id, p.name, oi.quantity
FROM orders o
JOIN order_items oi ON oi.order_id = o.id
JOIN products p ON p.id = oi.product_id
ORDER BY o.id, p.name;
```

```text
 order_id |   name   | quantity
----------+----------+----------
        1 | Keyboard |        1
        1 | Mouse    |        2
        2 | Mouse    |        1
```

Each result row is one line item: the first join finds the connections belonging to each order, and the second swaps each `product_id` for the product's actual name. The Mouse appears under both orders because two junction rows point at it, one connection per row.

### Join or separate queries?

Fetching a list and then querying once per row (the N+1 pattern) multiplies round trips and usually loses badly to a single join, even a large one. Let the database combine the data; that is what it is optimized for. If a join's result set explodes because one row matches thousands, paginate or aggregate on the many side instead of splitting into per-row queries.

---

## Querying rows

URL: https://appwrite.io/docs/products/databases/postgresql/concepts/queries

Four statements do almost all the work in a relational database:

- **SELECT** reads rows
- **INSERT** adds new rows
- **UPDATE** changes existing rows
- **DELETE** removes rows

SQL is declarative: a query describes the result you want, and the database's planner decides how to produce it, choosing between indexes, scans, and join strategies on its own. That's why the same query keeps working as data grows and indexes change.

This page walks through each statement, plus the querying patterns you reach for daily: filtering, aggregation, pagination, and upserts.

### Setup

The examples below work against a small product catalog. Create and seed it first:

```sql
CREATE TABLE products (
    id    bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    name  text NOT NULL,
    price numeric(10, 2) NOT NULL,
    stock integer NOT NULL DEFAULT 0
);

INSERT INTO products (name, price, stock) VALUES
    ('Keyboard', 79.00, 120),
    ('Mouse', 29.50, 200),
    ('Monitor', 349.99, 14),
    ('Webcam', 59.00, 0),
    ('Desk mat', 19.90, 45);
```

### Read rows

A `SELECT` statement has three jobs, each handled by its own clause: choose which columns to return, decide which rows qualify, and put them in order:

```sql
SELECT name, price
FROM products
WHERE stock > 0
ORDER BY price DESC;
```

```text
   name   | price
----------+--------
 Monitor  | 349.99
 Keyboard |  79.00
 Mouse    |  29.50
 Desk mat |  19.90
```

Four of the five products come back: the Webcam is filtered out because its stock is 0, and the rest arrive sorted by price, highest first. The other columns still exist on those rows; the query just didn't ask for them. Without an `ORDER BY`, row order is arbitrary and can change between runs, so always order results the user will see.

`WHERE` accepts any boolean expression, built from a handful of operators:

- **Comparisons** (`=`, `<`, `>`, `<=`, `>=`, `<>`) work on numbers, text, and dates alike.
- **BETWEEN** matches a range, bounds included.
- **ILIKE** matches string patterns case-insensitively, where `%` stands for any sequence of characters (`LIKE` is the case-sensitive variant).
- **IN** matches any value in a list.
- **AND** and **OR** combine conditions, with parentheses to group them.

```sql
SELECT name FROM products WHERE price BETWEEN 20 AND 100;   -- range
SELECT name FROM products WHERE name ILIKE '%mo%';          -- case-insensitive match
SELECT name FROM products WHERE stock IN (0, 14);           -- membership
SELECT name FROM products WHERE stock = 0 OR price < 25;    -- combined conditions
```

Avoid `SELECT *` in application code. Naming columns keeps results stable when the table gains columns later and avoids transferring data you don't use.

### Insert rows

`INSERT` names the columns it provides and gives a value for each; omitted columns fall back to their defaults, like the auto-generated `id` here:

```sql
INSERT INTO products (name, price, stock)
VALUES ('USB hub', 24.00, 80)
RETURNING id, name;
```

`RETURNING` gives back any columns of the rows just written, so you get the generated `id` without a second query. It works on `UPDATE` and `DELETE` too.

### Update rows

`UPDATE` changes the columns you name on every row matching the `WHERE` clause:

```sql
UPDATE products
SET price = price * 0.90
WHERE stock > 100
RETURNING name, price;
```

The `SET` expression can reference the row's current values, as the discount above does. Without a `WHERE` clause, `UPDATE` rewrites the entire table, so check the target set first with a `SELECT` using the same `WHERE` when running one by hand.

### Delete rows

`DELETE` removes every row matching the `WHERE` clause:

```sql
DELETE FROM products
WHERE stock = 0
RETURNING name;
```

Without a `WHERE` clause, `DELETE FROM products` removes every row in the table. For hand-run maintenance, wrap the statement in a transaction so you can inspect the result and roll it back. See [Transactions](/docs/products/databases/postgresql/concepts/transactions).

### Aggregate

An aggregate function computes a single value over a set of rows, such as a *count of orders* or a *total spend per customer*:

- **count(*)** counts rows
- **sum** and **avg** total and average a numeric column
- **min** and **max** find the extremes

The demonstration needs a table where several rows belong to each customer:

```sql
CREATE TABLE orders (
    id       bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    customer text NOT NULL,
    total    numeric(10, 2) NOT NULL
);

INSERT INTO orders (customer, total) VALUES
    ('ada', 120.00), ('ada', 80.00), ('grace', 45.50),
    ('grace', 30.00), ('grace', 25.00), ('linus', 999.99);
```

By default an aggregate covers every row in the table: `SELECT sum(total) FROM orders` returns one number, the total across all six orders. `GROUP BY customer` changes that. The database first gathers rows with the same customer value together, Ada's two orders, Grace's three, Linus's one, then runs the aggregate functions once for each of those groups. The result has one row per customer instead of one row for the whole table:

```sql
SELECT customer,
       count(*)   AS orders,
       sum(total) AS lifetime_value
FROM orders
GROUP BY customer
HAVING sum(total) > 100
ORDER BY lifetime_value DESC;
```

```text
 customer | orders | lifetime_value
----------+--------+----------------
 linus    |      1 |         999.99
 ada      |      2 |         200.00
 grace    |      3 |         100.50
```

Six order rows became three result rows, one per customer:

- Ada's two orders were summed into 200.00
- Grace's three orders were summed into 100.50
- Linus's single order stands alone at 999.99

In a grouped query, every selected column must be either grouped on (like `customer`) or aggregated (like `sum(total)`), because each result row now represents many source rows.

`WHERE` and `HAVING` both filter, but at different stages:

- **WHERE** filters individual rows, before grouping. It cannot use aggregate results, because they haven't been computed yet.
- **HAVING** filters the groups, after the aggregates are computed.

Here `HAVING sum(total) > 100` keeps only customers whose orders total more than 100. Grace passes at 100.50; a threshold of 150 would exclude her.

### Paginate

Pagination splits a large result into pages. There are two ways to do it:

- **Offset pagination** asks for "skip the first 20 rows, return the next 10". Simple, but the database still reads and discards every skipped row, so deep pages get slower and slower.
- **Keyset pagination** asks for "return the 10 rows after the last one I saw". The database jumps straight to that position, so every page costs the same as the first.

`LIMIT` and `OFFSET` are the offset approach:

```sql
SELECT id, name FROM products ORDER BY id LIMIT 2 OFFSET 2;
```

Keyset pagination filters on the last value seen instead:

```sql
SELECT id, name
FROM products
WHERE id > 2          -- last id from the previous page
ORDER BY id
LIMIT 2;
```

Keyset pagination stays fast at any depth because the index seeks straight to the boundary, provided the ordering columns are indexed. It requires a deterministic, unique ordering (a single column like `id`, or a composite) and can't jump to an arbitrary page number.

### Upsert

An upsert writes a row without knowing whether it already exists: insert it if it's new, update it if it's not. Stock counts, settings, and sync jobs all need this.

The obvious approach, `SELECT` to check and then `INSERT` or `UPDATE` accordingly, has a race condition: another connection can insert the same key between your check and your write, and your insert fails.

`INSERT ... ON CONFLICT` performs the check and the write as one atomic statement, so the race cannot happen:

```sql
CREATE TABLE inventory (
    sku      text PRIMARY KEY,
    quantity integer NOT NULL
);

INSERT INTO inventory (sku, quantity) VALUES ('KB-01', 10);

-- Second write for the same key updates instead of failing
INSERT INTO inventory (sku, quantity) VALUES ('KB-01', 5)
ON CONFLICT (sku) DO UPDATE
SET quantity = inventory.quantity + EXCLUDED.quantity
RETURNING sku, quantity;
```

```text
  sku  | quantity
-------+----------
 KB-01 |       15
```

What happened:

- The first insert creates KB-01 with quantity 10.
- The second insert collides with it on the primary key, so instead of failing, it runs the `DO UPDATE` clause: existing 10 plus incoming 5 gives 15.
- `EXCLUDED` refers to the row that would have been inserted, which is how the update reaches the incoming values.

Use `ON CONFLICT DO NOTHING` when duplicates should be silently ignored instead.

### Subqueries and CTEs

A common table expression (CTE) names an intermediate result so a complex query reads top to bottom:

```sql
WITH big_spenders AS (
    SELECT customer
    FROM orders
    GROUP BY customer
    HAVING sum(total) > 150
)
SELECT customer FROM big_spenders ORDER BY customer;
```

CTEs and subqueries are interchangeable in most positions. Prefer whichever keeps the query readable; PostgreSQL inlines CTEs into the surrounding query plan where possible, so there is normally no performance penalty.

---

## Tables and data types

URL: https://appwrite.io/docs/products/databases/postgresql/concepts/tables

A table is the unit of storage in a relational database, a named grid where:

- each **row** is one record: one customer, one order
- each **column** is one attribute every record shares: name, price, creation time
- each column has a **type** that determines what values it accepts and how they compare, sort, and calculate

Unlike a spreadsheet, the set of columns is declared up front and enforced; every row has exactly those columns. This fixed shape is what lets the database enforce correctness: precise types and constraints reject bad data before it ever reaches your application.

The examples on this page build up pieces of a small store schema: customers, products, and orders.

**Standard PostgreSQL types**

Column types are standard PostgreSQL types, not Appwrite-specific ones. This page covers the types most schemas need; the full list is in the [PostgreSQL data types documentation](https://www.postgresql.org/docs/current/datatype.html).

### Create a table

`CREATE TABLE` names the table and declares each column with its type and the rules it must obey:

```sql
CREATE TABLE customers (
    id         bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    name       text NOT NULL,
    email      text NOT NULL UNIQUE,
    created_at timestamptz NOT NULL DEFAULT now()
);
```

This declaration packs in the most common column features:

- `GENERATED ALWAYS AS IDENTITY` makes the database assign an auto-incrementing value. Prefer it over the older `serial` shorthand, which creates a separate sequence with looser ownership semantics. The Console table editor creates `bigserial` keys, so tables you make there use the older form.
- `PRIMARY KEY` uniquely identifies each row and creates an index automatically.
- `NOT NULL` rejects missing values, and `UNIQUE` rejects duplicates.
- `DEFAULT now()` fills the column when an insert doesn't provide a value.

### Numeric types

| Type | Range | Use for |
| --- | --- | --- |
| `smallint` | −32,768 to 32,767 | Small counters, enum-like codes |
| `integer` | about ±2.1 billion | General whole numbers |
| `bigint` | about ±9.2 quintillion | IDs, anything that may grow |
| `numeric(p, s)` | Exact decimal, up to `p` digits | Money, quantities that must not round |
| `real` / `double precision` | Approximate floating point | Measurements, scientific data |

The critical distinction is exact versus approximate:

- **numeric** stores decimal values exactly: 0.1 + 0.2 is 0.3.
- **double precision** stores binary approximations: the same sum comes back as 0.30000000000000004.

Always use `numeric` for money. A price column like `numeric(10, 2)` holds up to 8 digits before the decimal point and exactly 2 after. Plain `numeric` with no qualifiers accepts values of any precision, though the Console requires a precision when you add a numeric column, so you get `numeric(10, 0)` unless you change it.

### Text types

PostgreSQL has exactly three character types, and they all share the same storage underneath:

| Type | Holds | Use for |
| --- | --- | --- |
| `text` | Strings of any length, up to about 1 GB | All strings (the default choice) |
| `varchar(n)` | Up to n characters | Same as `text`, plus an enforced length limit |
| `char(n)` | Exactly n characters, space-padded | Fixed-length codes only |

There is no tiered family like other databases' TINYTEXT or LONGTEXT; one `text` type covers everything. Use `text` for all strings. PostgreSQL does not treat `varchar` differently from `text` in storage or performance, so a length limit is purely a business rule:

```sql
CREATE TABLE products (
    id    bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    name  text NOT NULL,
    sku   varchar(20) NOT NULL UNIQUE,  -- length limit as a business rule
    price numeric(10, 2) NOT NULL CHECK (price >= 0)
);
```

### Date and time types

| Type | Stores | Use for |
| --- | --- | --- |
| `timestamptz` | Point in time, UTC internally | Created/updated timestamps, events |
| `timestamp` | Wall-clock time, no zone | Rarely the right choice |
| `date` | Calendar date | Birthdays, due dates |
| `time` | Time of day | Opening hours |
| `interval` | Duration | Timeouts, subscription lengths |

Default to `timestamptz`. It converts input to UTC on write and renders it in the session's time zone on read, so two clients in different time zones always agree on the moment an event happened:

```sql
CREATE TABLE deployments (
    id          bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    version     text NOT NULL,
    deployed_at timestamptz NOT NULL DEFAULT now()
);

INSERT INTO deployments (version, deployed_at) VALUES
    ('1.4.0', '2026-07-15 09:00:00+02'),   -- written from Berlin
    ('1.4.1', '2026-07-15 07:30:00+00');   -- written from London

SELECT version, deployed_at FROM deployments ORDER BY deployed_at;
```

Both rows are stored in UTC, so the ordering is correct regardless of the time zone each writer used: 07:00 UTC (1.4.0) sorts before 07:30 UTC (1.4.1).

### Boolean, UUID, and binary

Three more types cover flags, identifiers, and raw bytes:

- **boolean** accepts `true`/`false` and the literals `'yes'`, `'no'`, `'1'`, `'0'`.
- **uuid** stores 128-bit identifiers natively in 16 bytes, and `gen_random_uuid()` generates them without any extension.
- **bytea** holds raw bytes, such as hashes or encrypted blobs.

```sql
CREATE TABLE api_tokens (
    token_id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
    active   boolean NOT NULL DEFAULT true,
    secret   bytea NOT NULL
);
```

### JSON columns

Use `jsonb` for schemaless data attached to structured rows. It stores a parsed binary representation that supports indexing and containment queries; plain `json` keeps the original text and is only useful when you must preserve key order or duplicates:

```sql
CREATE TABLE events (
    id      bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    kind    text NOT NULL,
    payload jsonb NOT NULL DEFAULT '{}'
);

INSERT INTO events (kind, payload)
VALUES ('signup', '{"plan": "pro", "referrer": "newsletter"}');

SELECT payload->>'plan' AS plan
FROM events
WHERE payload @> '{"referrer": "newsletter"}';
```

`->>` extracts a field as text, and `@>` tests containment. Reach for `jsonb` when attributes vary per row; keep anything you filter or join on regularly as a real column.

### Array types

Any type can be stored as an array by appending `[]`:

```sql
CREATE TABLE articles (
    id   bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    tags text[] NOT NULL DEFAULT '{}'
);

INSERT INTO articles (tags) VALUES (ARRAY['sql', 'tutorial']);

SELECT * FROM articles WHERE 'sql' = ANY (tags);
```

Arrays suit small, ordered value lists owned by one row. When the values need their own attributes or are shared across rows, model them as a separate table instead. See [Data modeling](/docs/products/databases/postgresql/concepts/data-modeling).

### Constraints

Constraints are rules the database enforces on every write, no matter which application or migration performs it:

| Constraint | Enforces |
| --- | --- |
| `PRIMARY KEY` | Unique, non-null row identifier |
| `UNIQUE` | No duplicate values in a column or column group (multiple `NULL`s are allowed unless declared `NULLS NOT DISTINCT`) |
| `NOT NULL` | Value must be present |
| `CHECK` | Arbitrary boolean expression per row |
| `REFERENCES` (foreign key) | Value must exist in the referenced table |

```sql
CREATE TABLE orders (
    id          bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    customer_id bigint NOT NULL REFERENCES customers (id),
    status      text NOT NULL DEFAULT 'pending'
                CHECK (status IN ('pending', 'paid', 'shipped', 'cancelled')),
    total       numeric(10, 2) NOT NULL CHECK (total >= 0)
);
```

A foreign key also controls what happens when the referenced row disappears. `ON DELETE CASCADE` removes dependent rows, `ON DELETE SET NULL` orphans them explicitly, and the default `NO ACTION` blocks the delete. Foreign keys are covered in depth in [Joins and relationships](/docs/products/databases/postgresql/concepts/joins).

### Generated columns

A generated column is one the database computes for you from the row's other columns, instead of accepting a value from the application. You declare the formula once in the schema; every write runs it automatically. This removes a whole class of bugs where a derived value, like a line total, is computed in application code and drifts out of sync with the columns it came from.

```sql
CREATE TABLE line_items (
    quantity   integer NOT NULL,
    unit_price numeric(10, 2) NOT NULL,
    line_total numeric(12, 2) GENERATED ALWAYS AS (quantity * unit_price) STORED
);

INSERT INTO line_items (quantity, unit_price) VALUES (3, 19.99);

SELECT line_total FROM line_items;
```

```text
 line_total
------------
      59.97
```

The insert supplies only `quantity` and `unit_price`; the database fills in `line_total` as 3 × 19.99. Writing to the column directly is an error, and if a later `UPDATE` changes the quantity, the total is recomputed in the same statement, so it can never disagree with its inputs. `STORED` means the result is computed on write and saved on disk like a normal column, making reads free.

### Change a table

`ALTER TABLE` evolves a schema in place:

```sql
ALTER TABLE customers ADD COLUMN phone text;
ALTER TABLE customers ALTER COLUMN phone SET NOT NULL;
ALTER TABLE customers RENAME COLUMN phone TO phone_number;
ALTER TABLE customers DROP COLUMN phone_number;
```

Adding a nullable column or one with a constant default doesn't rewrite the table, so it's fast at any size, though it still takes a brief exclusive lock and waits for in-flight queries. Adding `NOT NULL` to an existing column scans the table to validate existing rows, so on large tables do it during a quiet period.

**Full type catalog**

PostgreSQL ships more types beyond these. Network addresses (`inet`, `cidr`, `macaddr`) and bit strings are available in the Console column picker alongside the types above. Geometric types (`point`, `polygon`, `circle`), ranges, and full-text search types (`tsvector`, `tsquery`) are available in SQL. Run `\dTS` in `psql` or query `pg_type` to list what your database supports, and see the [extensions page](/docs/products/databases/postgresql/extensions) for types added by extensions such as `vector`.

---

## Transactions

URL: https://appwrite.io/docs/products/databases/postgresql/concepts/transactions

A transaction groups statements into a single all-or-nothing unit. Either every statement takes effect, or none do, and no other connection ever sees a half-finished state.

### How a transaction works

Think of a transaction as a private draft of the database that only your connection can see:

- `BEGIN` opens the draft. From here, your changes apply to the draft, not the shared database.
- Reads inside the transaction see the draft, so your own changes look real to you. Everyone else still sees the database as it was.
- `COMMIT` publishes the whole draft at once. Other connections go from seeing none of your changes to seeing all of them; there is no in-between state.
- `ROLLBACK` throws the draft away, and the database is as if the transaction never started.
- If your connection drops mid-transaction, the database rolls the draft back automatically.

Everything else on this page, isolation levels, locks, deadlocks, is about what happens when several connections work on their drafts at the same time.

### What goes wrong without transactions

Two distinct things go wrong without them:

- **Partial failure**: a crash or dropped connection after the debit but before the credit leaves data in a state that was never supposed to exist.
- **Interleaving**: two concurrent processes both read a balance of 100, both compute a new value, both write, and one update silently vanishes.

Transactions address both: statements apply atomically, and the database isolates transactions from each other's unfinished work.

### Setup

The examples below move money between two accounts, an operation where a half-applied change must be impossible. Create and seed the table first:

```sql
CREATE TABLE accounts (
    id      bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    owner   text NOT NULL,
    balance numeric(10, 2) NOT NULL CHECK (balance >= 0)
);

INSERT INTO accounts (owner, balance) VALUES ('ada', 100.00), ('grace', 50.00);
```

### Commit and roll back

Outside an explicit transaction, every statement commits on its own. `BEGIN` opens a transaction so several statements commit together:

```sql
BEGIN;
UPDATE accounts SET balance = balance - 30 WHERE owner = 'ada';
UPDATE accounts SET balance = balance + 30 WHERE owner = 'grace';
COMMIT;
```

If the connection drops between the two updates, or anything fails, the whole transaction rolls back and the money never left Ada's account. You can also abandon a transaction deliberately:

```sql
BEGIN;
DELETE FROM accounts;   -- oops
SELECT count(*) FROM accounts;   -- returns 0 inside this transaction
ROLLBACK;
SELECT count(*) FROM accounts;   -- returns 2, nothing happened
```

Inside the transaction the delete looks real, but only to that connection. `ROLLBACK` discards it. This makes `BEGIN` an effective safety net for hand-run maintenance SQL.

The ACID properties describe what transactions guarantee: **atomicity** (all or nothing), **consistency** (constraints hold before and after), **isolation** (concurrent transactions don't see each other's partial work), and **durability** (committed data survives a crash).

### Errors abort the transaction

If a statement fails inside a transaction, PostgreSQL aborts the whole transaction. Further statements are rejected until you `ROLLBACK`:

```sql
BEGIN;
UPDATE accounts SET balance = balance - 200 WHERE owner = 'ada';
```

```text
ERROR:  new row for relation "accounts" violates check constraint "accounts_balance_check"
```

The transaction is now in an aborted state. Even a statement that has nothing wrong with it is refused:

```sql
SELECT 1;
```

```text
ERROR:  current transaction is aborted, commands ignored until end of transaction block
```

The only way out is to end the transaction:

```sql
ROLLBACK;
```

The failed update tripped the balance check, and from that point PostgreSQL refuses everything, even a harmless `SELECT 1`, until the transaction ends. `ROLLBACK` clears the aborted state and discards the transaction's work; the connection is then ready for a fresh transaction. To recover from a failure without losing the statements that already succeeded, use a savepoint.

### Savepoints

A savepoint marks a spot you can roll back to without abandoning the whole transaction:

```sql
BEGIN;
UPDATE accounts SET balance = balance - 10 WHERE owner = 'ada';

SAVEPOINT before_bonus;
UPDATE accounts SET balance = balance - 200 WHERE owner = 'ada';  -- fails
ROLLBACK TO SAVEPOINT before_bonus;

UPDATE accounts SET balance = balance + 10 WHERE owner = 'grace';
COMMIT;
```

The failed update is undone, but the first update and everything after the rollback still commit. Drivers and ORMs use savepoints to implement nested transactions.

### Isolation levels

Isolation levels trade strictness for concurrency. They answer one question: what may this transaction see of other transactions' concurrent work?

| Level | Prevents | Behavior in PostgreSQL |
| --- | --- | --- |
| `READ COMMITTED` | Dirty reads | Default. Each statement sees data committed before that statement began |
| `REPEATABLE READ` | + non-repeatable reads, phantom reads | The whole transaction sees one snapshot, taken at its first query |
| `SERIALIZABLE` | + serialization anomalies | Transactions behave as if run one at a time; conflicts abort with an error |

PostgreSQL accepts `READ UNCOMMITTED` syntax and reports it back, but behaves as `READ COMMITTED`; dirty reads are never possible. Set the level per transaction:

```sql
BEGIN;
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
SELECT sum(balance) FROM accounts;
-- every query in this transaction sees the same snapshot
COMMIT;
```

Under the default `READ COMMITTED`, two reads inside one transaction can return different results if another transaction commits in between. That's usually fine for OLTP work. Use `REPEATABLE READ` for multi-query reports that must be internally consistent, and `SERIALIZABLE` when correctness depends on invariants across rows. Serializable transactions can abort with serialization failures (error code `40001`), so the application must be prepared to retry them.

### Row locking

Writers automatically lock the rows they modify until commit; a second transaction updating the same row waits. When you read a value in order to update it, that implicit protection isn't enough, because two transactions can read the same balance concurrently and both write results based on stale data. `SELECT ... FOR UPDATE` locks rows on read:

```sql
BEGIN;
SELECT balance FROM accounts WHERE owner = 'ada' FOR UPDATE;
-- other transactions now wait to read-for-update or modify this row
UPDATE accounts SET balance = balance - 30 WHERE owner = 'ada';
COMMIT;
```

For queue-like workloads where workers grab rows, add `SKIP LOCKED` so each worker takes the next unclaimed row instead of waiting. The claim only holds while the transaction is open, so the grab and the work's final update must share one transaction:

```sql
BEGIN;
SELECT id FROM accounts ORDER BY id LIMIT 1 FOR UPDATE SKIP LOCKED;
-- process the claimed row, then mark it done in the same transaction
COMMIT;
```

A concurrent worker running the same statement while the first transaction is open skips the claimed row and returns the next one.

### Deadlocks

A deadlock occurs when two transactions each hold a lock the other needs. Session A locks Ada's row and wants Grace's; session B holds Grace's and wants Ada's. Neither can proceed, so PostgreSQL detects the cycle after a second and kills one:

```text
ERROR:  deadlock detected
DETAIL:  Process 2412 waits for ShareLock on transaction 870; blocked by process 2421.
Process 2421 waits for ShareLock on transaction 869; blocked by process 2412.
HINT:  See server log for query details.
CONTEXT:  while updating tuple (0,6) in relation "accounts"
```

The aborted transaction rolls back; the survivor continues. Two habits prevent most deadlocks:

- Lock rows in a consistent order across your codebase, for example always by ascending `id`.
- Keep transactions short. Don't hold one open across network calls or user input.

The application should treat a deadlock like a serialization failure: retry the aborted transaction.

### Transactions and pooling

Transaction state lives on the connection, which matters when connecting through a pooler in transaction mode: session-level settings such as `SET default_transaction_isolation` won't stick between statements, and every multi-statement unit must be wrapped in an explicit `BEGIN`/`COMMIT` so it lands on one server connection. See [Connection pooling](/docs/products/databases/postgresql/connection-pooling) for how this applies to your Appwrite database.

---

## Connection pooling

URL: https://appwrite.io/docs/products/databases/postgresql/connection-pooling

PostgreSQL creates one backend process per connection, which makes each connection relatively expensive. Serverless functions, edge runtimes, and horizontally scaled application servers can easily exhaust the connection limit of your specification. The connection pooler sits in front of your database and multiplexes many client connections onto a small pool of server connections.

The pooler runs next to your database and is reachable on port `6432` on the same hostname. Your application connects to the pooler exactly like it would connect to PostgreSQL directly, same credentials, same TLS. The pooler runs on every PostgreSQL specification.

### Pool modes

| Mode | Behavior | Use for |
|---------------|-----------------------------------------------------------------------------|------------------------------------------------|
| `transaction` | A server connection is assigned for the duration of a transaction, then returned to the pool | Serverless and most applications (default) |
| `session` | A server connection is held for the entire client session | Session-level features: prepared statements, advisory locks, `LISTEN/NOTIFY`, temporary tables |

Transaction mode gives the highest connection multiplexing but does not support session-level state. If your framework prepares statements at the session level, either switch the driver to unnamed prepared statements or use session mode.

### Configure the pooler

Read the current pooler configuration with `getPooler`, and tune the pool mode and sizes with `updatePooler`. All parameters are optional; omitted values keep their current setting.

```server-nodejs
import { Client, Postgresql } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const postgresql = new Postgresql(client);

const pooler = await postgresql.updatePooler({
    databaseId: '<DATABASE_ID>',
    mode: 'transaction',
    defaultPoolSize: 25,
});
```
```server-deno
import { Client, Postgresql } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const postgresql = new Postgresql(client);

const pooler = await postgresql.updatePooler({
    databaseId: '<DATABASE_ID>',
    mode: 'transaction',
    defaultPoolSize: 25,
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Postgresql;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$postgresql = new Postgresql($client);

$pooler = $postgresql->updatePooler(
    databaseId: '<DATABASE_ID>',
    mode: 'transaction',
    defaultPoolSize: 25,
);
```
```server-python
from appwrite.client import Client
from appwrite.services.postgresql import Postgresql

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

postgresql = Postgresql(client)

pooler = postgresql.update_pooler(
    database_id='<DATABASE_ID>',
    mode='transaction',
    default_pool_size=25,
)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

postgresql = Postgresql.new(client)

pooler = postgresql.update_pooler(
    database_id: '<DATABASE_ID>',
    mode: 'transaction',
    default_pool_size: 25,
)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Postgresql postgresql = new Postgresql(client);

var pooler = await postgresql.UpdatePooler(
    databaseId: "<DATABASE_ID>",
    mode: "transaction",
    defaultPoolSize: 25
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Postgresql postgresql = Postgresql(client);

final pooler = await postgresql.updatePooler(
    databaseId: '<DATABASE_ID>',
    mode: 'transaction',
    defaultPoolSize: 25,
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Postgresql

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val postgresql = Postgresql(client)

val pooler = postgresql.updatePooler(
    databaseId = "<DATABASE_ID>",
    mode = "transaction",
    defaultPoolSize = 25,
)
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let postgresql = Postgresql(client)

let pooler = try await postgresql.updatePooler(
    databaseId: "<DATABASE_ID>",
    mode: "transaction",
    defaultPoolSize: 25
)
```
```server-go
package main

import (
    "github.com/appwrite/sdk-for-go/appwrite"
    "github.com/appwrite/sdk-for-go/postgresql"
)

func main() {
    client := appwrite.NewClient(
        appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"),
        appwrite.WithProject("<PROJECT_ID>"),
        appwrite.WithKey("<YOUR_API_KEY>"),
    )

    service := appwrite.NewPostgresql(client)

    result, err := service.UpdatePooler(
        "<DATABASE_ID>",
        postgresql.WithUpdatePoolerMode("transaction"),
        postgresql.WithUpdatePoolerDefaultPoolSize(25),
    )
    if err != nil {
        panic(err)
    }
    _ = result
}
```
```server-rust
use appwrite::client::Client;
use appwrite::services::postgresql::Postgresql;

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

    let postgresql = Postgresql::new(&client);

    let pooler = postgresql.update_pooler("<DATABASE_ID>", Some("transaction"), Some(25), None, None, None, None, None).await?;

    Ok(())
}
```
```bash
curl -X PATCH \
  -H "X-Appwrite-Project: <PROJECT_ID>" \
  -H "X-Appwrite-Key: <API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{
      "mode": "transaction",
      "defaultPoolSize": 25
  }' \
  https://<REGION>.cloud.appwrite.io/v1/postgresql/<DATABASE_ID>/pooler
```

| Parameter | Range | Description |
|----------------------|----------------|--------------------------------------------------------------------------------|
| `mode` | `transaction`, `session` | How long a server connection stays assigned to a client |
| `maxConnections` | read-only | Reports the database's advertised connection limit. The PostgreSQL pooler does not cap client connections, so set `networkMaxConnections` on the database instead |
| `defaultPoolSize` | 1 - 1,000 | Server connections per user in the pool |
| `readWriteSplitting` | boolean | Route `SELECT`s to replicas, writes and locked reads to the primary. Defaults to on when high availability is enabled |

Pooler configuration is API-only. The Console shows the pooled host and port in the **Credentials** dialog but has no page for changing these settings.

Switching to session mode is refused while the database has high availability replicas. Set `replicas` to 0 first, or stay on transaction mode.

### Connect through the pooler

Take your normal connection string and change the port to `6432`:

```bash
postgresql://admin:<password>@db-<hash>.<region>.appwrite.center:6432/db-<hash>?sslmode=require
```

Point your application's runtime traffic at the pooler port. Keep migrations and long-lived administrative sessions on the direct port `5432`, schema changes and tools like `pg_dump` expect session semantics and can misbehave in transaction mode.

### Read/write splitting

When [high availability](/docs/products/databases/postgresql/high-availability) is enabled, the pooler can route read-only statements to replicas and everything else to the primary. `SELECT ... FOR UPDATE` and statements inside explicit transactions go to the primary. Replicas replicate asynchronously by default, so a read that immediately follows a write can be stale. Synchronous replication does not fix this. It waits for the replica to flush the write to disk, not to replay it, so a replica can still answer a read from before the write. When a read must observe the write that preceded it, send it to the primary on the direct port.

### Sizing guidance

Appwrite already sizes the pool for you when the database is provisioned, from your specification's CPU count and connection limit. If you tune it by hand, `4 x CPU cores` is a useful starting point. Watch the connection metrics in the [Monitor tab](/docs/products/databases/postgresql/monitoring) and increase the pool only when clients queue for a connection.

### When not to use the pooler

The pooler adds a network hop, and transaction mode trades session-level features for connection multiplexing. Connect to the direct port `5432` instead when any of the following applies:

- **A small, fixed fleet.** A few long-lived application servers that each maintain a driver-level pool, with a combined connection count that fits in your specification's limit, gain nothing from an extra hop.
- **Session state.** Workloads that rely on advisory locks, `LISTEN/NOTIFY`, session-level prepared statements, or temporary tables break in transaction mode. Use session mode or the direct port.
- **Migrations and administration.** Schema changes and tools like `pg_dump` expect one session for the whole run. Always run them against the direct port.
- **Single latency-sensitive queries.** A workload of few, fast queries on an idle database pays the extra hop on every round trip without ever hitting the connection limit the pooler protects against.

---

## Connections

URL: https://appwrite.io/docs/products/databases/postgresql/connections

A native PostgreSQL database exposes a PostgreSQL endpoint over TLS. You connect to it the same way you would connect to any PostgreSQL server: with `psql`, any driver in any language, or any ORM.

### Get connection details in the Console

![Database credentials dialog](/images/docs/products/databases/postgresql/credentials.avif)

The fastest way to connect is through the Appwrite Console:

1. In your project, go to **Databases** and select your PostgreSQL database.
2. Click **Credentials** to open the credentials dialog.
3. Copy the individual values from the **Details** tab, or switch to the **DSN**, **.env**, **Prisma**, **Drizzle**, or **psql** tab for a ready-made snippet.
4. Paste it into `psql`, your ORM, or your database client.

Use the API flow below when you need to fetch connection details from automation or inject them into your deployment pipeline.

### Get connection details with the API

The connection details are returned on the database object itself. Fetch the database with an API key that has the `databases.read` scope:

```server-nodejs
import { Client, Postgresql } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const postgresql = new Postgresql(client);

const database = await postgresql.get({
    databaseId: '<DATABASE_ID>',
});

console.log(database.connectionString);
```
```server-deno
import { Client, Postgresql } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const postgresql = new Postgresql(client);

const database = await postgresql.get({
    databaseId: '<DATABASE_ID>',
});

console.log(database.connectionString);
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Postgresql;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$postgresql = new Postgresql($client);

$database = $postgresql->get(databaseId: '<DATABASE_ID>');

echo $database['connectionString'];
```
```server-python
from appwrite.client import Client
from appwrite.services.postgresql import Postgresql

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

postgresql = Postgresql(client)

database = postgresql.get(database_id='<DATABASE_ID>')

print(database.connection_string)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

postgresql = Postgresql.new(client)

database = postgresql.get(database_id: '<DATABASE_ID>')

puts database.connection_string
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Postgresql postgresql = new Postgresql(client);

var database = await postgresql.Get(databaseId: "<DATABASE_ID>");

Console.WriteLine(database.ConnectionString);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Postgresql postgresql = Postgresql(client);

final database = await postgresql.get(
    databaseId: '<DATABASE_ID>',
);

print(database.connectionString);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Postgresql

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val postgresql = Postgresql(client)

val database = postgresql.get(
    databaseId = "<DATABASE_ID>",
)

println(database.connectionString)
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let postgresql = Postgresql(client)

let database = try await postgresql.get(
    databaseId: "<DATABASE_ID>"
)

print(database.connectionString)
```
```server-go
package main

import (
    "fmt"

    "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("<YOUR_API_KEY>"),
    )

    postgresql := appwrite.NewPostgresql(client)

    database, err := postgresql.Get("<DATABASE_ID>")
    if err != nil {
        panic(err)
    }
    fmt.Println(database.ConnectionString)
}
```
```server-rust
use appwrite::client::Client;
use appwrite::services::postgresql::Postgresql;

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

    let postgresql = Postgresql::new(&client);

    let database = postgresql.get("<DATABASE_ID>").await?;

    println!("{}", database.connection_string);

    Ok(())
}
```
```bash
curl -X GET \
  -H "X-Appwrite-Project: <PROJECT_ID>" \
  -H "X-Appwrite-Key: <API_KEY>" \
  https://<REGION>.cloud.appwrite.io/v1/postgresql/<DATABASE_ID>
```

The response includes the connection fields alongside the database configuration:

```json
{
  "$id": "<DATABASE_ID>",
  "name": "main",
  "engine": "postgresql",
  "version": "18",
  "status": "ready",
  "hostname": "db-<hash>.<region>.appwrite.center",
  "connectionPort": 5432,
  "connectionUser": "admin",
  "connectionPassword": "<password>",
  "connectionString": "postgresql://admin:<password>@db-<hash>.<region>.appwrite.center:5432/db-<hash>?sslmode=require",
  "ssl": true,
  "credentialGeneration": 1
}
```

The primary user is `admin`. The database name on the wire is the first label of the hostname, so a database at `db-abc123def456.fra.appwrite.center` is reached as `db-abc123def456`.

### Connect with psql

Pass the connection string, or the individual values, to `psql`. The Console credentials dialog also has a **psql** tab with the command ready to copy.

```bash
psql "postgresql://admin:<password>@db-<hash>.<region>.appwrite.center:5432/db-<hash>?sslmode=require"
```

Or with individual flags:

```bash
PGSSLMODE=require psql -h db-<hash>.<region>.appwrite.center -p 5432 -U admin -d db-<hash>
```

### Rotate the primary password

If your password is compromised, or your security policy requires regular rotation, you can issue a new password for the primary user. You can do this from the Console, in the **Credentials** dialog, or through the API.

Rotation is asynchronous. The request returns an operation rather than the updated database. Poll `listOperations` until that operation reaches a terminal status, then fetch the database again to read the new connection details. Existing sessions stay alive until they disconnect, then have to authenticate with the new password. The API key needs the `databases.write` scope.

```server-nodejs
import { Client, Postgresql } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const postgresql = new Postgresql(client);

const operation = await postgresql.updateCredentials({
    databaseId: '<DATABASE_ID>',
});
```
```server-deno
import { Client, Postgresql } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const postgresql = new Postgresql(client);

const operation = await postgresql.updateCredentials({
    databaseId: '<DATABASE_ID>',
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Postgresql;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$postgresql = new Postgresql($client);

$operation = $postgresql->updateCredentials(databaseId: '<DATABASE_ID>');
```
```server-python
from appwrite.client import Client
from appwrite.services.postgresql import Postgresql

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

postgresql = Postgresql(client)

operation = postgresql.update_credentials(database_id='<DATABASE_ID>')
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

postgresql = Postgresql.new(client)

operation = postgresql.update_credentials(database_id: '<DATABASE_ID>')
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Postgresql postgresql = new Postgresql(client);

var operation = await postgresql.UpdateCredentials(databaseId: "<DATABASE_ID>");
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Postgresql postgresql = Postgresql(client);

final operation = await postgresql.updateCredentials(
    databaseId: '<DATABASE_ID>',
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Postgresql

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val postgresql = Postgresql(client)

val operation = postgresql.updateCredentials(
    databaseId = "<DATABASE_ID>",
)
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let postgresql = Postgresql(client)

let operation = try await postgresql.updateCredentials(
    databaseId: "<DATABASE_ID>"
)
```
```server-go
package main

import (
    "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("<YOUR_API_KEY>"),
    )

    postgresql := appwrite.NewPostgresql(client)

    _, err := postgresql.UpdateCredentials("<DATABASE_ID>")
    if err != nil {
        panic(err)
    }
}
```
```server-rust
use appwrite::client::Client;
use appwrite::services::postgresql::Postgresql;

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

    let postgresql = Postgresql::new(&client);

    let operation = postgresql.update_credentials("<DATABASE_ID>").await?;

    Ok(())
}
```
```bash
curl -X PATCH \
  -H "X-Appwrite-Project: <PROJECT_ID>" \
  -H "X-Appwrite-Key: <API_KEY>" \
  https://<REGION>.cloud.appwrite.io/v1/postgresql/<DATABASE_ID>/credentials
```

### Database roles

![Database roles tab](/images/docs/products/databases/postgresql/roles-tab.avif)

The primary `admin` role owns the default database. For applications that need narrower access, such as a read-only reporting user or a write-only ingestion user, create additional PostgreSQL roles from the **Roles** tab of your database in the Console. The list shows each role's login, role-creation, and database-creation privileges, its connection limit, and its role memberships.

Roles are standard PostgreSQL roles, so `GRANT` and `REVOKE` statements in the [SQL editor](/docs/products/databases/postgresql/quick-start#first-queries) or `psql` work on them like on any PostgreSQL server. Through the SQL API, `GRANT` and `REVOKE` are rejected unless you add them to the database's `sqlApiAllowedStatements`. The `postgres` superuser is managed by Appwrite and cannot be modified.

### TLS

Connections on Appwrite Cloud are encrypted with TLS, terminated at the edge and forwarded to your database over the internal network. The connection string from the credentials dialog carries the right SSL settings for your environment, so drivers need no extra configuration.

For IP allowlists and other network controls, see [network security](/docs/products/databases/postgresql/network-security).

### Connecting from an application

There is nothing Appwrite-specific about the driver setup. A few example snippets:

```server-nodejs
import { Client } from 'pg';

const client = new Client({
    connectionString: process.env.DATABASE_URL,
});

await client.connect();
const { rows } = await client.query('SELECT now()');
console.log(rows);
```
```server-python
import os
import psycopg

with psycopg.connect(os.environ['DATABASE_URL']) as conn:
    with conn.cursor() as cur:
        cur.execute('SELECT now()')
        print(cur.fetchone())
```
```server-php
<?php

$pdo = new PDO(getenv('DATABASE_DSN'));

$rows = $pdo->query('SELECT now()')->fetchAll();
print_r($rows);
```
```server-go
package main

import (
    "context"
    "fmt"
    "os"

    "github.com/jackc/pgx/v5"
)

func main() {
    conn, err := pgx.Connect(context.Background(), os.Getenv("DATABASE_URL"))
    if err != nil {
        panic(err)
    }
    defer conn.Close(context.Background())

    var now string
    if err := conn.QueryRow(context.Background(), "SELECT now()").Scan(&now); err != nil {
        panic(err)
    }
    fmt.Println(now)
}
```
```server-rust
use postgres_native_tls::MakeTlsConnector;
use native_tls::TlsConnector;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let url = std::env::var("DATABASE_URL").expect("DATABASE_URL");
    let tls = MakeTlsConnector::new(TlsConnector::new()?);
    let (client, connection) = tokio_postgres::connect(&url, tls).await?;

    tokio::spawn(async move {
        if let Err(e) = connection.await {
            eprintln!("connection error: {e}");
        }
    });

    let row = client.query_one("SELECT now()::text", &[]).await?;
    let now: &str = row.get(0);
    println!("{now}");

    Ok(())
}
```

Set `DATABASE_URL` to the connection string from the credentials dialog. Once you can run a query, you can use any tool that talks the PostgreSQL wire protocol: pgAdmin, DataGrip, your ORM of choice, your migration tool of choice. Appwrite gets out of the way.

---

## Extensions

URL: https://appwrite.io/docs/products/databases/postgresql/extensions

PostgreSQL exposes a rich extension ecosystem: PostGIS for geospatial data, pgvector for embeddings, pg_trgm for fuzzy search, and more. Native PostgreSQL databases on Appwrite support extensions at no extra cost.

Extensions change the database itself, so only trusted callers can manage them. You cannot manage extensions from client SDKs: the API needs a [Server SDK](/docs/sdks#server) and an [API key](/docs/partners/project/api-keys). Installing and removing an extension needs the `databases.write` scope, and listing extensions needs `databases.read`.

### Install an extension

![Extensions catalog in database settings](/images/docs/products/databases/postgresql/settings-extensions.avif)

In the Console, open your database's **Settings** > **Extensions**, find the extension in the catalog, click **Install**, and confirm. The database must be in the `ready` state.

You can also install an extension with a Server SDK. Pass the extension name as it appears in the [extension catalog](#list). The install runs asynchronously: the request returns immediately and a worker runs `CREATE EXTENSION` inside the database in the background.

```server-nodejs
import { Client, Postgresql } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const postgresql = new Postgresql(client);

await postgresql.createExtension({
    databaseId: '<DATABASE_ID>',
    name: 'vector',
});
```
```server-deno
import { Client, Postgresql } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const postgresql = new Postgresql(client);

await postgresql.createExtension({
    databaseId: '<DATABASE_ID>',
    name: 'vector',
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Postgresql;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$postgresql = new Postgresql($client);

$postgresql->createExtension(
    databaseId: '<DATABASE_ID>',
    name: 'vector',
);
```
```server-python
from appwrite.client import Client
from appwrite.services.postgresql import Postgresql

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

postgresql = Postgresql(client)

postgresql.create_extension(
    database_id='<DATABASE_ID>',
    name='vector',
)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

postgresql = Postgresql.new(client)

postgresql.create_extension(
    database_id: '<DATABASE_ID>',
    name: 'vector',
)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Postgresql postgresql = new Postgresql(client);

await postgresql.CreateExtension(
    databaseId: "<DATABASE_ID>",
    name: "vector"
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Postgresql postgresql = Postgresql(client);

await postgresql.createExtension(
    databaseId: '<DATABASE_ID>',
    name: 'vector',
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Postgresql

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val postgresql = Postgresql(client)

postgresql.createExtension(
    databaseId = "<DATABASE_ID>",
    name = "vector",
)
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let postgresql = Postgresql(client)

_ = try await postgresql.createExtension(
    databaseId: "<DATABASE_ID>",
    name: "vector"
)
```
```server-go
package main

import (
    "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("<YOUR_API_KEY>"),
    )

    service := appwrite.NewPostgresql(client)

    _, err := service.CreateExtension("<DATABASE_ID>", "vector")
    if err != nil {
        panic(err)
    }
}
```
```server-rust
use appwrite::client::Client;
use appwrite::services::postgresql::Postgresql;

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

    let postgresql = Postgresql::new(&client);

    postgresql.create_extension("<DATABASE_ID>", "vector").await?;

    Ok(())
}
```
```bash
curl -X POST \
  -H "X-Appwrite-Project: <PROJECT_ID>" \
  -H "X-Appwrite-Key: <API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{
      "name": "vector"
  }' \
  https://<REGION>.cloud.appwrite.io/v1/postgresql/<DATABASE_ID>/extensions
```

Requesting an extension that is not available fails with an error naming the extension you asked for. Call [list extensions](#list) to see what this database can install.

### List extensions

![Extensions catalog filtered to installed extensions](/images/docs/products/databases/postgresql/settings-extensions-installed.avif)

In the Console, the extensions catalog filters by **All**, **Installed**, and **In progress**. The API returns both sets at once, the extensions installed on the database and the extensions available to install:

```server-nodejs
import { Client, Postgresql } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const postgresql = new Postgresql(client);

const extensions = await postgresql.listExtensions({
    databaseId: '<DATABASE_ID>',
});
```
```server-deno
import { Client, Postgresql } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const postgresql = new Postgresql(client);

const extensions = await postgresql.listExtensions({
    databaseId: '<DATABASE_ID>',
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Postgresql;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$postgresql = new Postgresql($client);

$extensions = $postgresql->listExtensions(
    databaseId: '<DATABASE_ID>',
);
```
```server-python
from appwrite.client import Client
from appwrite.services.postgresql import Postgresql

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

postgresql = Postgresql(client)

extensions = postgresql.list_extensions(
    database_id='<DATABASE_ID>',
)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

postgresql = Postgresql.new(client)

extensions = postgresql.list_extensions(
    database_id: '<DATABASE_ID>',
)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Postgresql postgresql = new Postgresql(client);

var extensions = await postgresql.ListExtensions(
    databaseId: "<DATABASE_ID>"
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Postgresql postgresql = Postgresql(client);

final extensions = await postgresql.listExtensions(
    databaseId: '<DATABASE_ID>',
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Postgresql

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val postgresql = Postgresql(client)

val extensions = postgresql.listExtensions(
    databaseId = "<DATABASE_ID>",
)
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let postgresql = Postgresql(client)

let extensions = try await postgresql.listExtensions(
    databaseId: "<DATABASE_ID>"
)
```
```server-go
package main

import (
    "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("<YOUR_API_KEY>"),
    )

    service := appwrite.NewPostgresql(client)

    result, err := service.ListExtensions("<DATABASE_ID>")
    if err != nil {
        panic(err)
    }
    _ = result
}
```
```server-rust
use appwrite::client::Client;
use appwrite::services::postgresql::Postgresql;

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

    let postgresql = Postgresql::new(&client);

    let extensions = postgresql.list_extensions("<DATABASE_ID>").await?;

    Ok(())
}
```
```bash
curl -X GET \
  -H "X-Appwrite-Project: <PROJECT_ID>" \
  -H "X-Appwrite-Key: <API_KEY>" \
  https://<REGION>.cloud.appwrite.io/v1/postgresql/<DATABASE_ID>/extensions
```

The response contains two arrays of extension names, plus a `metadata` array describing each one:

```json
{
  "installed": ["plpgsql", "vector"],
  "available": ["postgis", "pg_trgm", "pgcrypto", "hstore", "citext", "..."],
  "metadata": [
    {
      "key": "vector",
      "name": "pgvector",
      "description": "Vector data type and similarity search for embeddings",
      "category": "Search"
    }
  ]
}
```

The available list starts from what the engine reports for your PostgreSQL version, minus extensions Appwrite does not offer, so it reflects what you can actually install on this database.

### Remove an extension

![Uninstall extension confirmation](/images/docs/products/databases/postgresql/settings-extensions-uninstall.avif)

In the Console, click **Uninstall** on an installed extension and confirm. You can also remove it with a Server SDK:

```server-nodejs
import { Client, Postgresql } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const postgresql = new Postgresql(client);

await postgresql.deleteExtension({
    databaseId: '<DATABASE_ID>',
    extensionName: 'vector',
});
```
```server-deno
import { Client, Postgresql } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const postgresql = new Postgresql(client);

await postgresql.deleteExtension({
    databaseId: '<DATABASE_ID>',
    extensionName: 'vector',
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Postgresql;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$postgresql = new Postgresql($client);

$postgresql->deleteExtension(
    databaseId: '<DATABASE_ID>',
    extensionName: 'vector',
);
```
```server-python
from appwrite.client import Client
from appwrite.services.postgresql import Postgresql

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

postgresql = Postgresql(client)

postgresql.delete_extension(
    database_id='<DATABASE_ID>',
    extension_name='vector',
)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

postgresql = Postgresql.new(client)

postgresql.delete_extension(
    database_id: '<DATABASE_ID>',
    extension_name: 'vector',
)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Postgresql postgresql = new Postgresql(client);

await postgresql.DeleteExtension(
    databaseId: "<DATABASE_ID>",
    extensionName: "vector"
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Postgresql postgresql = Postgresql(client);

await postgresql.deleteExtension(
    databaseId: '<DATABASE_ID>',
    extensionName: 'vector',
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Postgresql

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val postgresql = Postgresql(client)

postgresql.deleteExtension(
    databaseId = "<DATABASE_ID>",
    extensionName = "vector",
)
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let postgresql = Postgresql(client)

_ = try await postgresql.deleteExtension(
    databaseId: "<DATABASE_ID>",
    extensionName: "vector"
)
```
```server-go
package main

import (
    "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("<YOUR_API_KEY>"),
    )

    service := appwrite.NewPostgresql(client)

    _, err := service.DeleteExtension("<DATABASE_ID>", "vector")
    if err != nil {
        panic(err)
    }
}
```
```server-rust
use appwrite::client::Client;
use appwrite::services::postgresql::Postgresql;

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

    let postgresql = Postgresql::new(&client);

    postgresql.delete_extension("<DATABASE_ID>", "vector").await?;

    Ok(())
}
```
```bash
curl -X DELETE \
  -H "X-Appwrite-Project: <PROJECT_ID>" \
  -H "X-Appwrite-Key: <API_KEY>" \
  https://<REGION>.cloud.appwrite.io/v1/postgresql/<DATABASE_ID>/extensions/vector
```

Uninstalling always cascades. Any object that depends on the extension is dropped with it, including tables, columns, and indexes that use its types or functions. Check what depends on an extension before you remove it, and take a [backup](/docs/products/databases/postgresql/backups) first if the data matters.

### Common extensions

A few extensions worth knowing about:

| Name | Use case |
|-------------|-----------------------------------------------------------------------|
| `vector` | Vector data type and similarity search for embeddings (pgvector) |
| `postgis` | Spatial data types, indexes, and functions for geographic objects |
| `pg_trgm` | Trigram matching for fuzzy string search and similarity |
| `pgcrypto` | Cryptographic functions for hashing and encryption |
| `uuid-ossp` | Generate universally unique identifiers (UUIDs) |
| `hstore` | Store sets of key/value pairs within a single column |
| `citext` | Case-insensitive character string data type |
| `ltree` | Represent and query hierarchical tree-like data |

List the extensions on your database for the full catalog available to it.

### Limits

| Limit | Value |
|---------------------------------|------------|
| Maximum extensions per database | 50 (enforced in the Console) |
| Extension install and uninstall | Free |

Extensions are installed at their default version for your database's PostgreSQL major version.

---

## High availability

URL: https://appwrite.io/docs/products/databases/postgresql/high-availability

A single database instance is a single point of failure. High availability (HA) adds streaming replicas next to your primary: they replicate continuously, serve read traffic through the [connection pooler](/docs/products/databases/postgresql/connection-pooling), and take over automatically when the primary becomes unhealthy.

High availability works on every PostgreSQL specification. How many replicas you can add depends on your plan: the Pro and Scale plans allow up to five, and the free plan allows none.

The connection pooler must be in transaction mode. Adding replicas is refused while the pooler is in session mode.

### How it works

Replicas receive changes from the primary through PostgreSQL streaming replication (WAL shipping). Each replica is a full copy of the database on its own compute. When the primary fails, the most caught-up replica is promoted to primary and the hostname is repointed, your application keeps connecting to the same host and port.

### Replication modes

| Mode | Behavior | Trade-off |
|----------|----------------------------------------------------------------------------------|--------------------------------------------------|
| `async` | The primary commits without waiting for replicas | Fastest writes; a failover can lose the last moments of writes |
| `sync` | The primary waits for one replica to confirm each commit | No data loss on single failure; slightly higher write latency |
| `quorum` | The primary waits for a majority of the replica set, counting the primary itself | Scales the number of confirmations with the replica count |

`async` is the default. For production workloads that cannot lose acknowledged writes, use `sync` or `quorum` with at least one replica.

`quorum` needs `(replicas + 1) / 2` confirmations, rounded down. At one and two replicas that is a single confirmation, the same as `sync`, so the two modes behave identically until you run three or more replicas.

Both `sync` and `quorum` wait for the replica to write the change to disk, not to apply it. A replica can still answer a read from before a write that the primary has already acknowledged.

If replicas fall behind or disappear, the primary lowers the number of confirmations it waits for rather than blocking writes, and raises it again once the replicas recover. The replication status reports the mode in effect alongside the one you configured.

### Enable high availability

![High availability settings](/images/docs/products/databases/postgresql/settings-high-availability.avif)

Set the replica count and replication mode on the database, from **Settings** > **Replication** in the Console or through the API:

```server-nodejs
import { Client, Postgresql } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const postgresql = new Postgresql(client);

await postgresql.update({
    databaseId: '<DATABASE_ID>',
    replicas: 2,
    syncMode: 'sync',
});
```
```server-deno
import { Client, Postgresql } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const postgresql = new Postgresql(client);

await postgresql.update({
    databaseId: '<DATABASE_ID>',
    replicas: 2,
    syncMode: 'sync',
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Postgresql;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$postgresql = new Postgresql($client);

$postgresql->update(
    databaseId: '<DATABASE_ID>',
    replicas: 2,
    syncMode: 'sync',
);
```
```server-python
from appwrite.client import Client
from appwrite.services.postgresql import Postgresql

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

postgresql = Postgresql(client)

postgresql.update(
    database_id='<DATABASE_ID>',
    replicas=2,
    sync_mode='sync',
)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

postgresql = Postgresql.new(client)

postgresql.update(
    database_id: '<DATABASE_ID>',
    replicas: 2,
    sync_mode: 'sync',
)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Postgresql postgresql = new Postgresql(client);

await postgresql.Update(
    databaseId: "<DATABASE_ID>",
    replicas: 2,
    syncMode: "sync"
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Postgresql postgresql = Postgresql(client);

await postgresql.update(
    databaseId: '<DATABASE_ID>',
    replicas: 2,
    syncMode: 'sync',
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Postgresql

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val postgresql = Postgresql(client)

postgresql.update(
    databaseId = "<DATABASE_ID>",
    replicas = 2,
    syncMode = "sync",
)
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let postgresql = Postgresql(client)

_ = try await postgresql.update(
    databaseId: "<DATABASE_ID>",
    replicas: 2,
    syncMode: "sync"
)
```
```server-go
package main

import (
    "github.com/appwrite/sdk-for-go/appwrite"
    "github.com/appwrite/sdk-for-go/postgresql"
)

func main() {
    client := appwrite.NewClient(
        appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"),
        appwrite.WithProject("<PROJECT_ID>"),
        appwrite.WithKey("<YOUR_API_KEY>"),
    )

    service := appwrite.NewPostgresql(client)

    _, err := service.Update(
        "<DATABASE_ID>",
        postgresql.WithUpdateReplicas(2),
        postgresql.WithUpdateSyncMode("sync"),
    )
    if err != nil {
        panic(err)
    }
}
```
```server-rust
use appwrite::client::Client;
use appwrite::services::postgresql::Postgresql;

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

    let postgresql = Postgresql::new(&client);

    postgresql.update("<DATABASE_ID>", None, None, None, Some(2), Some("sync"), None, None, None, None, None, None, None, None, None, None, None, None, None, None, None).await?;

    Ok(())
}
```
```bash
curl -X PATCH \
  -H "X-Appwrite-Project: <PROJECT_ID>" \
  -H "X-Appwrite-Key: <API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{
      "replicas": 2,
      "syncMode": "sync"
  }' \
  https://<REGION>.cloud.appwrite.io/v1/postgresql/<DATABASE_ID>
```

Adding replicas provisions them online; the primary keeps serving traffic while each replica seeds from a snapshot and catches up. Setting `replicas` back to `0` disables HA and resets `syncMode` to `async`.

### Check replication status

You can check each replica's role, health, and replication lag:

```server-nodejs
import { Client, Postgresql } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const postgresql = new Postgresql(client);

const replicas = await postgresql.getReplicas({
    databaseId: '<DATABASE_ID>',
});
```
```server-deno
import { Client, Postgresql } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const postgresql = new Postgresql(client);

const replicas = await postgresql.getReplicas({
    databaseId: '<DATABASE_ID>',
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Postgresql;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$postgresql = new Postgresql($client);

$replicas = $postgresql->getReplicas(
    databaseId: '<DATABASE_ID>',
);
```
```server-python
from appwrite.client import Client
from appwrite.services.postgresql import Postgresql

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

postgresql = Postgresql(client)

replicas = postgresql.get_replicas(
    database_id='<DATABASE_ID>',
)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

postgresql = Postgresql.new(client)

replicas = postgresql.get_replicas(
    database_id: '<DATABASE_ID>',
)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Postgresql postgresql = new Postgresql(client);

var replicas = await postgresql.GetReplicas(
    databaseId: "<DATABASE_ID>"
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Postgresql postgresql = Postgresql(client);

final replicas = await postgresql.getReplicas(
    databaseId: '<DATABASE_ID>',
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Postgresql

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val postgresql = Postgresql(client)

val replicas = postgresql.getReplicas(
    databaseId = "<DATABASE_ID>",
)
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let postgresql = Postgresql(client)

let replicas = try await postgresql.getReplicas(
    databaseId: "<DATABASE_ID>"
)
```
```server-go
package main

import (
    "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("<YOUR_API_KEY>"),
    )

    service := appwrite.NewPostgresql(client)

    result, err := service.GetReplicas("<DATABASE_ID>")
    if err != nil {
        panic(err)
    }
    _ = result
}
```
```server-rust
use appwrite::client::Client;
use appwrite::services::postgresql::Postgresql;

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

    let postgresql = Postgresql::new(&client);

    let replicas = postgresql.get_replicas("<DATABASE_ID>").await?;

    Ok(())
}
```
```bash
curl -X GET \
  -H "X-Appwrite-Project: <PROJECT_ID>" \
  -H "X-Appwrite-Key: <API_KEY>" \
  https://<REGION>.cloud.appwrite.io/v1/postgresql/<DATABASE_ID>/replicas
```

### Automatic failover

Appwrite continuously health-checks the primary. When it becomes unresponsive, the platform promotes the replica with the least replication lag, repoints the database hostname, and rebuilds the old primary as a replica of the new one. Your application reconnects to the same hostname; a well-configured driver pool retries and recovers without intervention.

With `async` replication, writes that had not yet reached the promoted replica are lost in a failover. Use `sync` or `quorum` if that is unacceptable.

### Manual failover

Trigger a failover yourself, for example to test your application's recovery behavior. Optionally pass `targetReplicaId` to promote a specific replica.

```server-nodejs
import { Client, Postgresql } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const postgresql = new Postgresql(client);

await postgresql.createFailover({
    databaseId: '<DATABASE_ID>',
});
```
```server-deno
import { Client, Postgresql } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const postgresql = new Postgresql(client);

await postgresql.createFailover({
    databaseId: '<DATABASE_ID>',
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Postgresql;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$postgresql = new Postgresql($client);

$postgresql->createFailover(
    databaseId: '<DATABASE_ID>',
);
```
```server-python
from appwrite.client import Client
from appwrite.services.postgresql import Postgresql

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

postgresql = Postgresql(client)

postgresql.create_failover(
    database_id='<DATABASE_ID>',
)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

postgresql = Postgresql.new(client)

postgresql.create_failover(
    database_id: '<DATABASE_ID>',
)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Postgresql postgresql = new Postgresql(client);

await postgresql.CreateFailover(
    databaseId: "<DATABASE_ID>"
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Postgresql postgresql = Postgresql(client);

await postgresql.createFailover(
    databaseId: '<DATABASE_ID>',
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Postgresql

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val postgresql = Postgresql(client)

postgresql.createFailover(
    databaseId = "<DATABASE_ID>",
)
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let postgresql = Postgresql(client)

_ = try await postgresql.createFailover(
    databaseId: "<DATABASE_ID>"
)
```
```server-go
package main

import (
    "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("<YOUR_API_KEY>"),
    )

    service := appwrite.NewPostgresql(client)

    _, err := service.CreateFailover("<DATABASE_ID>")
    if err != nil {
        panic(err)
    }
}
```
```server-rust
use appwrite::client::Client;
use appwrite::services::postgresql::Postgresql;

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

    let postgresql = Postgresql::new(&client);

    postgresql.create_failover("<DATABASE_ID>", None).await?;

    Ok(())
}
```
```bash
curl -X POST \
  -H "X-Appwrite-Project: <PROJECT_ID>" \
  -H "X-Appwrite-Key: <API_KEY>" \
  https://<REGION>.cloud.appwrite.io/v1/postgresql/<DATABASE_ID>/failovers
```

### Reading from replicas

Replicas serve read traffic when [read/write splitting](/docs/products/databases/postgresql/connection-pooling#read-write-splitting) is enabled on the connection pooler. A read that immediately follows a write can return stale data, and synchronous replication does not change that, because it waits for the replica to store the write rather than apply it. Route reads that must see the latest write to the primary.

### Limits and billing

- Up to 5 replicas per database; the maximum depends on your plan.
- Each replica runs on the same specification as the primary and is billed as an add-on. See [pricing](/pricing).
- Replicas live in the same region as the primary.

---

## Auth.js

URL: https://appwrite.io/docs/products/databases/postgresql/integrations/auth-js

[Auth.js](https://authjs.dev/) (formerly NextAuth.js) persists users, accounts, sessions, and verification tokens through a database adapter. When you configure an adapter, those records live in your own database instead of only in a cookie, which is what makes database sessions, account linking, and email sign-in possible. An Appwrite [native PostgreSQL database](/docs/products/databases/postgresql) is a standard PostgreSQL engine, so any Auth.js adapter built on a PostgreSQL ORM works against it with no Appwrite-specific configuration.

**Before you start**

You'll need a native PostgreSQL database in a `ready` state and its credentials. See [native PostgreSQL databases](/docs/products/databases/postgresql) to create one, then open the database in the Console and click **Credentials**. The primary user is `admin`, and each database has its own generated database name.

### Choose an adapter

Auth.js doesn't talk to the database directly, it goes through an official adapter. For a native PostgreSQL database, use whichever ORM you already run:

- **Prisma** through [`@auth/prisma-adapter`](https://authjs.dev/getting-started/adapters/prisma). See the [Prisma guide](/docs/products/databases/postgresql/integrations/prisma) for the full datasource, pooling, and migration setup.
- **Drizzle** through [`@auth/drizzle-adapter`](https://authjs.dev/getting-started/adapters/drizzle). See the [Drizzle guide](/docs/products/databases/postgresql/integrations/drizzle) for the driver connection and `drizzle-kit` migration setup.

The connection details, ports, and pooling behaviour are the same for both. The rest of this page shows the Prisma path and notes the Drizzle equivalents.

### Set the connection string

Auth.js adapters read the database URL from the environment, exactly like any other ORM workload. Copy the connection string from the Console **Credentials** dialog or from `postgresql.get()` in the [API](/docs/products/databases/postgresql/connections#credentials), and give the adapter two URLs: a **pooled** one for the running app and a **direct** one for migrations.

```env
### Runtime: pooled, transaction mode (prepared statements off)
DATABASE_URL="postgresql://admin:<password>@db-<hash>.<region>.appwrite.center:6432/<database>?sslmode=require&pgbouncer=true"

### Migrations: direct connection to the engine
DIRECT_URL="postgresql://admin:<password>@db-<hash>.<region>.appwrite.center:5432/<database>?sslmode=require"

### Development migrations: separate branch or database used only by Prisma Migrate
SHADOW_DATABASE_URL="postgresql://admin:<password>@db-<shadow-hash>.<region>.appwrite.center:5432/<database>?sslmode=require"
```

The app runtime connects on the [pooler](/docs/products/databases/postgresql/connection-pooling) port (`6432`), and schema migrations run over the direct engine port (`5432`). The TLS parameter (`sslmode=require`) is already part of the string Appwrite returns. The edge proxy terminates TLS, so no certificate setup is needed. For full certificate verification (`verify-full`) or mTLS, see the [Network](/docs/products/databases/postgresql/network-security) page.

`SHADOW_DATABASE_URL` must point at a separate PostgreSQL database or branch. Prisma Migrate uses it during `migrate dev` to detect schema drift. It can reset the shadow target, so never set it to the same value as `DIRECT_URL`. `migrate deploy` does not use the shadow database.

### Create the adapter schema

Auth.js ships a [canonical schema](https://authjs.dev/getting-started/database#models) of four core models, `User`, `Account`, `Session`, and `VerificationToken`. With Prisma, add them to `prisma/schema.prisma` and keep connection URLs in `prisma.config.ts` and your Prisma Client setup:

```prisma
datasource db {
  provider = "postgresql"
}
```

The generator and models are engine-agnostic:

```prisma
generator client {
  provider = "prisma-client-js"
}

model User {
  id            String    @id @default(cuid())
  name          String?
  email         String?   @unique
  emailVerified DateTime?
  image         String?
  accounts      Account[]
  sessions      Session[]
}

model Account {
  id                String  @id @default(cuid())
  userId            String
  type              String
  provider          String
  providerAccountId String
  refresh_token     String? @db.Text
  access_token      String? @db.Text
  expires_at        Int?
  token_type        String?
  scope             String?
  id_token          String? @db.Text
  session_state     String?
  user              User    @relation(fields: [userId], references: [id], onDelete: Cascade)

  @@unique([provider, providerAccountId])
}

model Session {
  id           String   @id @default(cuid())
  sessionToken String   @unique
  userId       String
  expires      DateTime
  user         User     @relation(fields: [userId], references: [id], onDelete: Cascade)
}

model VerificationToken {
  identifier String
  token      String
  expires    DateTime

  @@unique([identifier, token])
}
```

Configure Prisma CLI commands in `prisma.config.ts`. The CLI uses `DIRECT_URL` because migrations need a direct session connection, while Prisma Client uses the pooled `DATABASE_URL` at runtime.

```ts
import 'dotenv/config';
import { defineConfig, env } from 'prisma/config';

export default defineConfig({
  schema: 'prisma/schema.prisma',
  migrations: {
    path: 'prisma/migrations',
  },
  datasource: {
    url: env('DIRECT_URL'),
    shadowDatabaseUrl: process.env.SHADOW_DATABASE_URL,
  },
});
```

With Drizzle, define the equivalent PostgreSQL tables in your schema file and generate a `drizzle-kit` migration instead. The table shapes are the same, and the official adapter docs include a ready-made PostgreSQL schema.

### Run the migration over the direct connection

Create the tables before the app serves any traffic. Prisma Migrate connects with `DIRECT_URL` (the engine port), so it gets a real session connection with full DDL privileges:

```bash
npx prisma migrate dev --name authjs-init
```

In CI or production, apply already-generated migrations without prompting:

```bash
npx prisma migrate deploy
```

If your build does not run `migrate dev`, generate Prisma Client explicitly:

```bash
npx prisma generate
```

With Drizzle, run `drizzle-kit push` (or `migrate()`) against the same direct URL. The primary `admin` user owns the default database and can run schema changes. Narrower [connection users](/docs/products/databases/postgresql/connections#roles) (`readonly` / `readwrite`) intentionally cannot run DDL, so always migrate as `admin`.

### Wire the adapter into Auth.js

Create one Prisma Client with the PostgreSQL driver adapter, pointed at the pooled `DATABASE_URL`:

```ts
import { PrismaPg } from '@prisma/adapter-pg';
import { PrismaClient } from '@prisma/client';

const connectionString = process.env.DATABASE_URL;
if (!connectionString) throw new Error('DATABASE_URL is required');

const adapter = new PrismaPg({ connectionString });

export const prisma = new PrismaClient({ adapter });
```

Pass the adapter to your Auth.js config through the `adapter` key. The adapter uses Prisma Client for every runtime read and write:

```ts
import NextAuth from 'next-auth';
import { PrismaAdapter } from '@auth/prisma-adapter';
import { prisma } from '@/prisma';

export const { handlers, auth, signIn, signOut } = NextAuth({
  adapter: PrismaAdapter(prisma),
  providers: [
    // your providers, e.g. GitHub, Google, Resend
  ],
});
```

The Drizzle equivalent is identical apart from the import, `adapter: DrizzleAdapter(db)` from `@auth/drizzle-adapter`, where `db` is your Drizzle instance.

### Database vs JWT sessions

Auth.js has two session strategies, and the adapter changes the default:

- **`database`**: the default *once an adapter is configured*. A session row is written to the `Session` table and only an opaque session ID is stored in an `HttpOnly` cookie. Each request looks the session up in the native PostgreSQL database. Sessions can be revoked server-side.
- **`jwt`**: the default when no adapter is set. Session state lives entirely in a signed cookie, and the database is not read on the session path.

You can set it explicitly:

```ts
export const { handlers, auth } = NextAuth({
  adapter: PrismaAdapter(prisma),
  session: { strategy: 'database' },
  providers: [],
});
```

Even with `strategy: 'jwt'`, the adapter still persists users and linked accounts, so account linking and the admin view of users keep working; only the per-request session read moves off the database.

### Pooling note

On serverless and edge platforms (Vercel, Netlify, Cloudflare) every invocation is a fresh instance, so connecting straight to the engine fans out into more backend connections than it allows. Routing through the pooler in **transaction mode** (the default) absorbs that churn. Auth.js tables are write-light and read-heavy, exactly the access pattern transaction-mode pooling handles best, so the pooled `DATABASE_URL` above is the right default for the app runtime.

Transaction mode does not hold a backend connection across statements, so server-side prepared statements are unavailable. The pooled Prisma URL carries `pgbouncer=true`, and Drizzle's `postgres.js` client should be created with `prepare: false`. Migrations always use the direct connection, so they keep a full session connection. If you need session-bound features, switch the pooler to **session mode**, see the [pooler modes](/docs/products/databases/postgresql/connection-pooling#modes) page for the trade-offs.

### Use a branch for previews

[Branches](/docs/products/databases/postgresql/branches) are instant, isolated copies of a database with their own hostname and connection string, ideal for a pull-request preview or an integration-test job that signs users in and out against throwaway data:

1. Create a branch from the API and read its `connectionString`.
2. Export the direct variant as `DIRECT_URL` and the pooled variant as `DATABASE_URL`.
3. Run `prisma migrate deploy` (or `drizzle-kit push`) and your auth flow against the branch.
4. Delete the branch when the job finishes.

Because a branch starts from a storage snapshot, the Auth.js tables already exist with realistic data, so preview sign-ins behave like production without touching it.

### Related

- [Prisma](/docs/products/databases/postgresql/integrations/prisma): Datasource, pooled and direct URLs, and migrations for the Prisma adapter.
- [Drizzle](/docs/products/databases/postgresql/integrations/drizzle): Driver connection, pooler settings, and drizzle-kit migrations for PostgreSQL.
- [Connection pooler](/docs/products/databases/postgresql/connection-pooling): Transaction vs session mode, ports, and serverless connection handling.
- [Better Auth](/docs/products/databases/postgresql/integrations/better-auth): The same pattern for Better Auth on a native PostgreSQL database.

---

## Better Auth

URL: https://appwrite.io/docs/products/databases/postgresql/integrations/better-auth

[Better Auth](https://www.better-auth.com/) is a framework-agnostic authentication library for TypeScript that keeps its state, including users, sessions, accounts, and verification tokens, in a database you own. A native PostgreSQL database gives Better Auth a standard PostgreSQL engine, so you can use the connection string from the [Connections](/docs/products/databases/postgresql/connections) page and run the Better Auth CLI to create the schema.

**Before you start**

You'll need a native PostgreSQL database in a `ready` state and its credentials. See [PostgreSQL databases](/docs/products/databases/postgresql) to create one, then open the database and click **Credentials** to copy values from the **Details**, **DSN**, **.env**, **Prisma**, **Drizzle**, or **psql** tab. The primary user is `admin`, and each database has its own generated database name.

### Set the connection strings

Better Auth needs two connection strings for the same database: a **pooled** URL for your application runtime and a **direct** URL for schema work. Copy the values from the Console **Credentials** dialog, or fetch them with [`postgresql.get()`](/docs/products/databases/postgresql/connections#credentials), and put both in your environment. Never commit them.

```env
### Runtime: pooler port (transaction mode), absorbs serverless connection churn
DATABASE_URL="postgresql://admin:<password>@db-<hash>.<region>.appwrite.center:6432/<database>?sslmode=require"

### Schema generation & migrations: direct connection to the engine
DIRECT_URL="postgresql://admin:<password>@db-<hash>.<region>.appwrite.center:5432/<database>?sslmode=require"
```

Use `sslmode=require` for Appwrite Cloud connections. The edge proxy terminates TLS for every native PostgreSQL database, so drivers need no extra certificate configuration. See the [Network security](/docs/products/databases/postgresql/network-security) page for TLS and network controls.

The PostgreSQL pooler runs on port `6432`, and the engine runs on port `5432`. See [Connections](/docs/products/databases/postgresql/connections) for connection details and the [connection pooler](/docs/products/databases/postgresql/connection-pooling#modes) page for the pool modes.

### Configure Better Auth

Better Auth accepts either a **database instance** (a driver connection pool, which it drives through its built-in Kysely adapter) or an **ORM adapter** (Prisma, Drizzle, Kysely). Both work against a native PostgreSQL database.

#### With a driver pool

Pass a `pg` `Pool` pointed at the **pooled** URL so runtime traffic is multiplexed. Set `ssl: { rejectUnauthorized: true }` so the driver verifies the proxy's certificate:

```ts
import { betterAuth } from 'better-auth';
import { Pool } from 'pg';

export const auth = betterAuth({
    database: new Pool({
        connectionString: process.env.DATABASE_URL,
        ssl: { rejectUnauthorized: true }
    }),
    emailAndPassword: { enabled: true }
});
```

This is the simplest path: Better Auth manages the schema for you and can both generate and apply migrations through the CLI below.

#### With an ORM adapter

If you already use an ORM, hand Better Auth an adapter instead of a raw `Pool`. Configure the ORM's own client against the native PostgreSQL database (pooled URL for the runtime client, direct URL for migrations), then wrap it:

```ts
import { betterAuth } from 'better-auth';
import { drizzleAdapter } from 'better-auth/adapters/drizzle';
import { db } from './db';

export const auth = betterAuth({
    database: drizzleAdapter(db, { provider: 'pg' }),
    emailAndPassword: { enabled: true }
});
```

Set the ORM up against the native PostgreSQL database first, then return here for the schema steps. The [Prisma](/docs/products/databases/postgresql/integrations/prisma) and [Drizzle](/docs/products/databases/postgresql/integrations/drizzle) guides cover the `datasource`/client config, the pooled-vs-direct URL split, and how transaction-mode pooling affects prepared statements.

### Generate and migrate the schema

The [Better Auth CLI](https://www.better-auth.com/docs/concepts/cli) reads your `auth` config and creates the tables it needs. Run it with the **direct** URL so it gets a session connection with DDL privileges. Transaction-mode pooling is designed for short application transactions, not schema changes.

Generate the schema for your setup. With a driver pool (built-in Kysely adapter) this produces a SQL file; with an ORM adapter it produces that ORM's schema (a Prisma schema, a Drizzle `schema.ts`):

```bash
DATABASE_URL="$DIRECT_URL" npx @better-auth/cli@latest generate
```

If you're using the built-in adapter (a driver pool), apply the generated schema directly:

```bash
DATABASE_URL="$DIRECT_URL" npx @better-auth/cli@latest migrate
```

`migrate` is only available for the built-in Kysely adapter. With a **Prisma** or **Drizzle** adapter, run `generate` to produce the schema, then apply it with that ORM's own migration tool, `prisma migrate deploy` or `drizzle-kit migrate`, again over `DIRECT_URL`. See the [Prisma](/docs/products/databases/postgresql/integrations/prisma#migrate) and [Drizzle](/docs/products/databases/postgresql/integrations/drizzle) guides for the exact commands.

Both commands connect over the engine port, so they get full DDL privileges. The primary `admin` user owns the database and can run schema changes; narrower [database roles](/docs/products/databases/postgresql/connections#roles) (`readonly` / `readwrite`) intentionally cannot run DDL.

### A minimal example

After the schema exists, your runtime connects on the pooler URL and Better Auth handles the rest. Mount the handler for your framework and start creating users:

```ts
import { auth } from './auth';

const result = await auth.api.signUpEmail({
    body: {
        email: 'ada@example.com',
        password: 'a-strong-password',
        name: 'Ada Lovelace'
    }
});

console.log(result.user.id);
```

On a long-running server, instantiate `auth` once and reuse it. On serverless, keep a single instance per module scope so warm invocations reuse it, and rely on the pooler to absorb cold-start connection churn.

### Pooling and prepared statements

Auth workloads, including sign-up, login, and session lookups, are short transactions, which is exactly what the pooler's default **transaction mode** is built for. Pointing your runtime pool at port `6432` lets a large number of serverless instances share a small backend pool.

The one constraint of transaction mode is that it does not hold a backend connection across statements, so **server-side prepared statements are unavailable**. On PostgreSQL, the node-postgres `Pool` is fine with this out of the box. If you drive Better Auth through an ORM, disable prepared statements on the runtime client: with `postgres.js`, set `prepare: false`; with Prisma, add `pgbouncer=true` to the pooled URL. If your app needs prepared statements, advisory locks, or `LISTEN`/`NOTIFY`, switch the pooler to **session mode**. See the [pooler](/docs/products/databases/postgresql/connection-pooling#modes) page for the trade-offs.

### Use a branch for previews and CI

[Branches](/docs/products/databases/postgresql/branches) are isolated copies of a database with their own hostname and connection string, ideal for running auth migrations against throwaway data in a pull-request preview or an integration-test job:

1. Create a branch from the API and read its `connectionString`.
2. Export it as `DIRECT_URL` (and the pooled variant as `DATABASE_URL`).
3. Run the Better Auth CLI (or your ORM migration) and your test suite against the branch.
4. Delete the branch when the job finishes.

Because a branch starts from a storage snapshot, the schema and data match the source database at branch time, so auth flows run against realistic data without touching production.

### Related

- [Connect](/docs/products/databases/postgresql/connections): Retrieve credentials, rotate the password, and create scoped connection users.
- [Connection pooler](/docs/products/databases/postgresql/connection-pooling): Pool modes, ports, and read/write splitting for serverless workloads.
- [Prisma](/docs/products/databases/postgresql/integrations/prisma): Drive the Better Auth Prisma adapter with pooled and direct URLs.
- [Drizzle](/docs/products/databases/postgresql/integrations/drizzle): Drive the Better Auth Drizzle adapter against a native PostgreSQL database.
- [Branches](/docs/products/databases/postgresql/branches): Ephemeral database copies for preview environments and CI.
- [Auth.js](/docs/products/databases/postgresql/integrations/auth-js): Use a native PostgreSQL database as the Auth.js (NextAuth) database.

---

## dbt

URL: https://appwrite.io/docs/products/databases/postgresql/integrations/dbt

An Appwrite native PostgreSQL database is a standard PostgreSQL server, so [dbt](https://docs.getdbt.com/) works against it through the standard [`dbt-postgres`](https://docs.getdbt.com/docs/local/connect-data-platform/postgres-setup) adapter with no Appwrite-specific configuration. Point a `profiles.yml` target at the connection details from the [Connections](/docs/products/databases/postgresql/connections) page and use `dbt debug`, `dbt run`, and `dbt build` the same way you would against any self-hosted PostgreSQL warehouse.

dbt compiles your models into `CREATE TABLE` / `CREATE VIEW` statements and runs them in dependency order, materializing a transformed analytics layer inside a schema you control.

**Before you start**

You'll need a native PostgreSQL database in a `ready` state and its credentials. See [PostgreSQL](/docs/products/databases/postgresql) to create one and [Connections](/docs/products/databases/postgresql/connections) to retrieve them. The primary user is `admin`, the database name is generated for each database, and the engine listens on port `5432`.

### Create a build schema

dbt issues DDL (`CREATE`, `DROP`, `ALTER`) to build your models, so connect with the primary `admin` user and build into a schema reserved for dbt, for example `analytics`. The `admin` user owns the generated database and can create the schema, tables, and views dbt needs.

Keep transformed tables separate from source data by setting the `schema` field in `profiles.yml`. You can use different schema names per environment, such as `analytics_dev`, `analytics_ci`, and `analytics_prod`.

### Configure profiles.yml

dbt reads connection details from `~/.dbt/profiles.yml` or the project directory. Configure a `postgres` target against the database host on port `5432`, and set `sslmode: require` so the connection is encrypted:

```yaml
analytics:
  target: dev
  outputs:
    dev:
      type: postgres
      host: db-<hash>.<region>.appwrite.center
      port: 5432
      user: admin
      password: "{{ env_var('APPWRITE_DB_PASSWORD') }}"
      dbname: <database>
      schema: analytics
      sslmode: require
      threads: 4
```

The dbt-postgres adapter uses `password` (not `pass`) and `dbname` (you may also write `database`). Read the password from an environment variable with `env_var` rather than committing it. The `schema` key is where dbt materializes models, set it to the build schema your user owns.

The edge proxy terminates TLS for every native PostgreSQL database, so `sslmode: require` needs no extra certificate files. For full certificate verification, set `sslmode: verify-full` with `sslrootcert` pointing at a trusted root store, `system` on libpq 16+, or your OS bundle such as `/etc/ssl/certs/ca-certificates.crt`. The proxy's certificate is signed by a public CA, so there is no Appwrite-specific CA to download.

### Test the connection

`dbt debug` validates your project files and opens a connection to confirm the credentials and host are correct:

```bash
dbt debug
```

A successful run reports `Connection test: OK connection ok`. If it fails, recheck the host, port, `sslmode`, and password.

### Run transformations

Build your models into the analytics schema:

```bash
dbt run
```

`dbt run` executes models only, materializing each as a table or view. Use `dbt build` to run models, tests, seeds, and snapshots together in DAG order, a failing test on an upstream model then skips its dependents:

```bash
dbt build
```

### Choose the right connection

dbt opens one database connection per thread and uses each to run DDL and rely on session state (search paths, temporary objects, transactions spanning multiple statements). Point dbt at a connection that preserves that session:

- **Direct engine port (`5432`)** is the simplest and recommended target. Each thread gets a backend session with full DDL privileges. This is what the `profiles.yml` above uses.
- **Session-mode pooler** also works, because it holds a backend connection for the whole client session. Connect on the pooler port (`6432`) and switch the pool to `session` mode.

Do **not** point dbt at the **transaction-mode** pooler, which is the pooler default. Transaction mode returns the backend connection to the pool after every statement, so the session state and multi-statement DDL that dbt depends on can break. See the [connection pooling](/docs/products/databases/postgresql/connection-pooling#modes) page for the mode trade-offs.

### Size threads to your connection budget

The `threads` setting controls how many models dbt builds in parallel, and dbt opens one connection per thread. A `threads: 8` run can hold up to eight backend connections at once. dbt also respects model dependencies, so it never runs more models concurrently than your DAG allows, regardless of the thread count.

Pick a `threads` value that fits the connection budget for your database spec, and leave headroom for any application traffic sharing the same database. If you connect through a session-mode pooler, the same per-thread connections apply at the backend, so size against the pool, not the client side. Start at the adapter default of `4` and raise it only while connections stay within budget.

### Test transformations against a branch

[Branches](/docs/products/databases/postgresql/branches) are isolated copies of a database with their own hostname and connection string. Because a branch starts from a storage snapshot, its schema and data match the source at branch time, so dbt models run against production-like data without touching production. That makes branches ideal for validating transformations in CI:

1. Create a branch from the API and read its `connectionString`.
2. Set the branch host and credentials as the `profiles.yml` target (or export them as `env_var` values).
3. Run `dbt build` against the branch so models and tests execute on realistic data.
4. Delete the branch when the job finishes.

This gives every pull request a clean, production-shaped warehouse to build against without touching live analytics tables.

### Related

- [Connections](/docs/products/databases/postgresql/connections): Retrieve credentials and rotate the primary password.
- [Connection pooler](/docs/products/databases/postgresql/connection-pooling): Pool modes and ports. Use session mode for dbt, never transaction mode.
- [Branches](/docs/products/databases/postgresql/branches): Ephemeral database copies for CI and preview environments.
- [Network](/docs/products/databases/postgresql/network-security): TLS modes, certificate verification, and IP allowlists.

[dbt-postgres adapter reference](https://docs.getdbt.com/docs/local/connect-data-platform/postgres-setup)

---

## Django

URL: https://appwrite.io/docs/products/databases/postgresql/integrations/django

A native PostgreSQL database is a standard PostgreSQL engine, so Django's ORM works against it with no Appwrite-specific configuration. Point the `DATABASES` setting at the credentials from the [Connections](/docs/products/databases/postgresql/connections) page, then use migrations, models, and the rest of Django exactly as you would against any PostgreSQL server.

**Before you start**

You'll need a native PostgreSQL database in a `ready` state and its credentials. See [native PostgreSQL databases](/docs/products/databases/postgresql) to create one and [Connections](/docs/products/databases/postgresql/connections) to retrieve the connection details. The primary user is `admin`, and the database name is generated for each database.

### Install a driver

Django talks to PostgreSQL through a driver. Use [psycopg 3](https://www.psycopg.org/psycopg3/):

```bash
pip install "psycopg[binary]"
```

Django's PostgreSQL backend uses the `ENGINE` value `django.db.backends.postgresql`.

### Configure `DATABASES`

In `settings.py`, point the `default` connection at your native PostgreSQL database and require TLS through `OPTIONS`. Appwrite Cloud terminates TLS at the edge, and the certificate is signed by a public certificate authority. Read every value from the environment so credentials stay out of source control:

```python
import os

DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.postgresql",
        "NAME": os.environ["DB_NAME"],
        "USER": os.environ["DB_USER"],
        "PASSWORD": os.environ["DB_PASSWORD"],
        "HOST": os.environ["DB_HOST"],
        "PORT": os.environ["DB_PORT"],
        "OPTIONS": {
            "sslmode": "require",
        },
    }
}
```

The PostgreSQL backend forwards everything in `OPTIONS` to the driver's connection, so `sslmode` is honored the same way `psql` honors it in a connection string. For full certificate verification, set `"sslmode": "verify-full"` with `"sslrootcert"` pointing at a trusted root store or your OS bundle, such as `/etc/ssl/certs/ca-certificates.crt`. See [Network security](/docs/products/databases/postgresql/network-security) for TLS and IP allowlist guidance.

Populate the environment from the Console **Credentials** dialog or from `postgresql.get()` in the [API credentials flow](/docs/products/databases/postgresql/connections#credentials):

```env
DB_NAME=<database>
DB_USER=admin
DB_PASSWORD=<password>
DB_HOST=db-<hash>.<region>.appwrite.center
DB_PORT=5432
```

Port `5432` is the direct PostgreSQL port. Keep migrations on this port, and see [pooling](#pooling) below for when to add the pooler.

### Run migrations

Generate migrations from your models, then apply them against the direct PostgreSQL port:

```bash
python manage.py makemigrations
python manage.py migrate
```

`migrate` needs a session connection with DDL privileges. The primary `admin` user owns the database and can run schema changes. Narrower [database roles](/docs/products/databases/postgresql/connections#roles) (`readonly` or `readwrite`) are intended for application traffic with reduced privileges. Always run `migrate` on the direct PostgreSQL port, not through the transaction-mode pooler.

### Define a model

Models map to tables in your native PostgreSQL database. Define one in an app's `models.py`:

```python
from django.db import models

class Article(models.Model):
    title = models.CharField(max_length=200)
    body = models.TextField()
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ["-created_at"]
```

Run `makemigrations` and `migrate` again to create the table, then query it through the ORM:

```python
from blog.models import Article

Article.objects.create(title="Hello", body="First post")

recent = Article.objects.order_by("-created_at")[:10]
```

### Persistent connections and pooling

By default Django opens a new connection per request (`CONN_MAX_AGE = 0`). On a long-running WSGI server (Gunicorn, uWSGI) you can reuse connections by raising it. Each worker thread keeps its own connection, so the database must allow at least as many connections as you run worker threads:

```python
DATABASES["default"]["CONN_MAX_AGE"] = 60
DATABASES["default"]["CONN_HEALTH_CHECKS"] = True
```

`CONN_HEALTH_CHECKS` revalidates a reused connection once per request, avoiding errors after an engine restart. Do not enable persistent connections under the development server, it spawns a thread per request and gains nothing, and disable them under ASGI.

**Persistent connections need a session**

A worker holding a connection across requests behaves like a long-lived session. Connect it to the direct PostgreSQL port or the **session-mode** pooler. The default **transaction-mode** pooler can hand each statement a different backend connection, which makes it a better fit for serverless and short-lived runtimes that open and close a connection per invocation.

If you front the database with the transaction-mode [connection pooler](/docs/products/databases/postgresql/connection-pooling), point `HOST` and `PORT` at the PostgreSQL pooler on port `6432`. Also set `DISABLE_SERVER_SIDE_CURSORS = True`, since server-side cursors can't survive being moved between backend connections:

```python
DATABASES["default"]["DISABLE_SERVER_SIDE_CURSORS"] = True
```

For prepared statements, advisory locks, `LISTEN`/`NOTIFY`, or temporary tables, use **session mode** instead, see the [pooler](/docs/products/databases/postgresql/connection-pooling#modes) page for the trade-offs.

### Use a branch for previews and CI

[Branches](/docs/products/databases/postgresql/branches) are instant, isolated copies of a database with their own hostname and connection details. They're ideal for running migrations against throwaway data in a pull-request preview or an integration-test job:

1. Create a branch from the API and read its connection details.
2. Export them as the `DB_*` environment variables your settings read.
3. Run `python manage.py migrate` and your test suite against the branch.
4. Delete the branch when the job finishes.

Because a branch starts from a storage snapshot, its schema and data match the source database at branch time, so migrations run against realistic data without touching production.

### Related

- [Connections](/docs/products/databases/postgresql/connections): Retrieve credentials, rotate the password, and create scoped database roles.
- [Connection pooler](/docs/products/databases/postgresql/connection-pooling): Pool modes, ports, and read/write splitting for serverless workloads.
- [Branches](/docs/products/databases/postgresql/branches): Ephemeral database copies for preview environments and CI.
- [Network](/docs/products/databases/postgresql/network-security): TLS, certificate verification, IP allowlists, and idle timeout settings.

---

## Node.js drivers

URL: https://appwrite.io/docs/products/databases/postgresql/integrations/drivers

A native PostgreSQL database is a standard PostgreSQL engine, so any Node.js driver that speaks the PostgreSQL wire protocol can connect over TLS without an Appwrite-specific adapter. The [Connections](/docs/products/databases/postgresql/connections#drivers) page shows the minimal snippet to run your first query. This page covers production pool configuration, certificate verification, serverless connection management, and common connection errors.

**Before you start**

You'll need a native PostgreSQL database in a `ready` state and its credentials. In the Console, open the database and click **Credentials**. Use the **Details** tab for individual values, or the **DSN**, **.env**, **Prisma**, **Drizzle**, or **psql** tab for a ready-made snippet. The primary user is `admin`, and the database name is generated for each database. Keep the password in an environment variable and never commit it.

### Raw driver, ORM, or SQL API?

There are three common ways to reach a native PostgreSQL database from Node.js. Pick by workload:

| Approach | Use it when |
|------------------------------|----------------------------------------------------------------------------------------------|
| **Raw driver** (this page) | You want a connection pool you control, hot-path queries, or a thin data layer with little overhead. |
| **ORM** ([Prisma](/docs/products/databases/postgresql/integrations/prisma), [Drizzle](/docs/products/databases/postgresql/integrations/drizzle)) | You want migrations, a typed schema, and query building. The ORM still uses a PostgreSQL driver underneath. |
| **[SQL API](/docs/products/databases/postgresql/quick-start#first-queries)** | You're on an edge runtime that cannot hold a TCP socket, or scripting a one-off query over HTTPS. |

The rest of this page is about raw drivers on a long-running or serverless Node.js server that can open TCP connections.

### node-postgres (pg)

For a long-running server, create one `Pool` at startup and reuse it for every request. The pool opens connections lazily up to `max` and hands them back to your handlers.

```js
import { Pool } from 'pg';
import process from 'node:process';

const pool = new Pool({
  host: 'db-<hash>.<region>.appwrite.center',
  port: 5432,
  user: 'admin',
  password: process.env.DB_PASSWORD,
  database: '<database>',
  ssl: { rejectUnauthorized: true },
  max: 10,
  idleTimeoutMillis: 30000,
  connectionTimeoutMillis: 5000,
});

const { rows } = await pool.query('SELECT id, email FROM users WHERE id = $1', [userId]);
```

Pool sizing is a budget. Each connection in `max`, across every running instance, counts against your specification's connection cap. A single app server with `max: 10` is fine; ten replicas with `max: 50` each is 500 connections and can exhaust many specifications. Size `max` to the connection cap divided by replica count, then put the [connection pooler](/docs/products/databases/postgresql/connection-pooling) in front if you need more client concurrency than that allows.

`ssl: { rejectUnauthorized: true }` validates the server certificate against Node's built-in CA store. The edge proxy presents a certificate signed by a well-known public CA, so this works without supplying your own bundle. See [TLS and CA verification](#tls) for custom CA bundles and mTLS.

#### Pooled port vs direct port

The pooler defaults to **transaction mode**, which does not hold a backend connection across statements, so server-side prepared statements are unavailable. In node-postgres, a query becomes a server-side prepared statement only when you pass a `name` field on the query config:

```js
// Safe on the pooler (port 6432, transaction mode): no `name`, no server-side prepared statement.
await pool.query('SELECT * FROM events WHERE user_id = $1', [userId]);

// Use the direct port or session-mode pooler for named prepared statements.
await pool.query({ name: 'fetch-events', text: 'SELECT * FROM events WHERE user_id = $1', values: [userId] });
```

Parameterized queries without a `name` are sent fresh each time and work on the pooled port. If you rely on named prepared statements, advisory locks, `LISTEN`/`NOTIFY`, temporary tables, or `SET LOCAL`, connect on the direct port (`5432`) or switch the pooler to **session mode**. See the [pooler modes](/docs/products/databases/postgresql/connection-pooling#modes) page for the trade-offs. Run migrations on the direct port.

### postgres.js

`postgres.js` (the `postgres` package) takes its own option names. Create the client once at module scope:

```js
import postgres from 'postgres';
import process from 'node:process';

const sql = postgres({
  host: 'db-<hash>.<region>.appwrite.center',
  port: 5432,
  username: 'admin',
  password: process.env.DB_PASSWORD,
  database: '<database>',
  ssl: 'verify-full',
  max: 10,
  idle_timeout: 30,
  connect_timeout: 10,
});

const users = await sql`SELECT id, email FROM users WHERE id = ${userId}`;
```

`ssl: 'verify-full'` enables TLS and verifies the server certificate against the system CA store. If your runtime image ships without one, pass an object with a public CA bundle instead: `ssl: { rejectUnauthorized: true, ca: fs.readFileSync('./ca-bundle.crt') }`.

When you point `postgres.js` at the **pooler port** (`6432`) in transaction mode, disable prepared statements. The library uses prepared statements by default, and documents `prepare: false` for PgBouncer transaction mode:

```js
const sql = postgres({
  host: 'db-<hash>.<region>.appwrite.center',
  port: 6432,
  username: 'admin',
  password: process.env.DB_PASSWORD,
  database: '<database>',
  ssl: 'verify-full',
  prepare: false,
  max: 5,
});
```

Leave `prepare` at its default when you connect on the direct port or use the pooler in session mode.

### TLS and CA verification

Connections on Appwrite Cloud are encrypted with TLS, terminated at the edge and forwarded to your database over the internal network. The connection string from the credentials dialog carries the right SSL settings for your environment, and the examples above use strict certificate verification.

| Level | node-postgres | postgres.js |
|----------------------------------------------|-------------------------------------------------|--------------------------------------------------|
| **Encrypt and verify CA** | `ssl: { rejectUnauthorized: true }` | `ssl: 'verify-full'` |
| **Custom CA bundle** (no system trust store) | `ssl: { rejectUnauthorized: true, ca: fs.readFileSync('./ca-bundle.crt') }` | `ssl: { rejectUnauthorized: true, ca: fs.readFileSync('./ca-bundle.crt') }` |

The certificate behind every database hostname is signed by a well-known public CA, so Appwrite does not require a custom CA download. The `ca` option is useful for runtimes without a system trust store, such as distroless or scratch images. Point it at a standard public CA bundle from your base image's `ca-certificates` package or [Mozilla's bundle](https://curl.se/docs/caextract.html):

```js
import fs from 'node:fs';

const ssl = {
  rejectUnauthorized: true,
  ca: fs.readFileSync('./ca-bundle.crt'),
};
```

When the CA comes from an environment variable, restore the newlines the variable strips:

```js
const ssl = {
  rejectUnauthorized: true,
  ca: process.env.DB_SSL_CA?.replace(/\\n/g, '\n'),
};
```

Do not ship `rejectUnauthorized: false` to production. It disables certificate validation and exposes the connection to interception. To require **mTLS**, where the client also presents a certificate, see the [Network security](/docs/products/databases/postgresql/network-security) page. Then add `key` and `cert` alongside `ca` in the same `ssl` object.

### Serverless connection management

On Lambda, Cloud Run, Vercel, and similar platforms, every cold start is a fresh instance with its own pool. A `max: 10` pool times 200 concurrent instances is 2,000 backend connections, which is more than most specifications allow. Let Appwrite's pooler absorb the fan-out:

- **Connect on the pooler port** (`6432`) in transaction mode.
- **Keep the per-instance pool tiny**, usually `max: 1` or `2`. One invocation rarely needs more than one connection at a time.
- **Create the client once per module scope** so warm invocations reuse it.
- **Disable per-connection prepared statements** on the pooled port: `prepare: false` for postgres.js, and avoid `name` on node-postgres query configs.

```js
import postgres from 'postgres';
import process from 'node:process';

// Module scope: reused across warm invocations.
const sql = postgres({
  host: 'db-<hash>.<region>.appwrite.center',
  port: 6432,
  username: 'admin',
  password: process.env.DB_PASSWORD,
  database: '<database>',
  ssl: 'verify-full',
  prepare: false,
  max: 1,
});

export async function handler(event) {
  const rows = await sql`SELECT id FROM users WHERE email = ${event.email}`;
  return rows[0] ?? null;
}
```

On **edge runtimes** (Cloudflare Workers, Vercel Edge, Deno Deploy), raw TCP sockets are usually unavailable. Use the [SQL API](/docs/products/databases/postgresql/quick-start#first-queries) for these workloads.

### Troubleshooting

| Symptom | Cause and fix |
|-------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------|
| `self-signed certificate` / `unable to verify the first certificate` | TLS is reaching the wrong host, or the runtime has a stale or incomplete `ca` bundle. The proxy's certificate is signed by a public CA. Use driver certificate verification with the system trust store, and supply a current public CA bundle only when the runtime has no system trust store. |
| `sorry, too many clients already` / connection attempts rejected at the proxy | Aggregate pool size across all instances exceeds the database specification's connection cap. Lower `max`, or move to the [pooler](/docs/products/databases/postgresql/connection-pooling) port and shrink the per-instance pool to 1 or 2. |
| `prepared statement "S_1" does not exist` / `unnamed prepared statement does not exist` | Server-side prepared statements on the pooler in transaction mode. Set `prepare: false` for postgres.js, drop the `name` field for node-postgres, or use the direct port or [session mode](/docs/products/databases/postgresql/connection-pooling#modes). |
| `ETIMEDOUT` / `connection timed out` on connect | The database may be cold-starting on smaller specifications or blocked by an [IP allowlist](/docs/products/databases/postgresql/network-security#ip-allowlist). Confirm your egress IP is allowed and raise `connectionTimeoutMillis` or `connect_timeout` to absorb cold starts. |
| Connections drop after a period of inactivity | The proxy closes idle connections after `networkIdleTimeoutSeconds`. Keep `idleTimeoutMillis` and `idle_timeout` below that window so the driver recycles before the proxy does, or enable keep-alive. |

### Related

- [Connections](/docs/products/databases/postgresql/connections): Retrieve credentials, rotate the password, and create scoped connection users.
- [Connection pooler](/docs/products/databases/postgresql/connection-pooling): Pool modes, ports, and read/write splitting for serverless workloads.
- [SQL API](/docs/products/databases/postgresql/quick-start#first-queries): Run SQL over HTTPS with no TCP connection for edge runtimes and scripts.
- [Network security](/docs/products/databases/postgresql/network-security): TLS, certificate verification, mTLS, and IP allowlists.

---

## Drizzle

URL: https://appwrite.io/docs/products/databases/postgresql/integrations/drizzle

Appwrite's native PostgreSQL database is a standard PostgreSQL engine, so [Drizzle ORM](https://orm.drizzle.team/) works against it with no Appwrite-specific configuration. Point Drizzle's driver at the connection string from the [Connections](/docs/products/databases/postgresql/connections) page and use Drizzle Kit, the query builder, and the rest of the toolchain as you would against any PostgreSQL server.

**Before you start**

You'll need a native PostgreSQL database in a `ready` state and its credentials. See [native PostgreSQL databases](/docs/products/databases/postgresql) to create one and [Connections](/docs/products/databases/postgresql/connections) to retrieve the connection string. The primary user is `admin`, and the database name is generated per database.

### Set the connection string

In the Console, open your database and click **Credentials**. Copy the connection string from the **DSN**, **.env**, or **Drizzle** tab, or fetch it with [`postgresql.get()`](/docs/products/databases/postgresql/connections#credentials). Put it in your environment, never commit it:

```env
DATABASE_URL="postgresql://admin:<password>@db-<hash>.<region>.appwrite.center:5432/<database>?sslmode=require"
```

The TLS parameter (`sslmode=require`) is part of the connection string Appwrite returns. Appwrite Cloud terminates TLS at the edge and forwards traffic to your database over the internal network, so no extra certificate configuration is needed. For full certificate verification (`verify-full`) or mTLS, see [Network security](/docs/products/databases/postgresql/network-security).

### Install and configure the driver

Drizzle talks to PostgreSQL through one of two driver packages. Pick the one already in your stack:

```bash
### node-postgres
npm install drizzle-orm pg
npm install -D drizzle-kit @types/pg

### or postgres.js
npm install drizzle-orm postgres
npm install -D drizzle-kit
```

With **node-postgres**, import `drizzle` from `drizzle-orm/node-postgres` and hand it the connection string:

```ts
import { drizzle } from 'drizzle-orm/node-postgres';

export const db = drizzle(process.env.DATABASE_URL!);
```

With **postgres.js**, import `drizzle` from `drizzle-orm/postgres-js`. For a direct connection to the engine you can pass the URL straight through:

```ts
import { drizzle } from 'drizzle-orm/postgres-js';

export const db = drizzle(process.env.DATABASE_URL!);
```

When you pool through the connection pooler in transaction mode (see [Pool connections from serverless](#pooling)), postgres.js must disable prepared statements. Build the client yourself with `prepare: false` and pass it to `drizzle`:

```ts
import { drizzle } from 'drizzle-orm/postgres-js';
import postgres from 'postgres';

const client = postgres(process.env.DATABASE_URL!, { prepare: false });

export const db = drizzle({ client });
```

Define your tables in a schema file Drizzle Kit can read:

```ts
import { pgTable, serial, text, timestamp } from 'drizzle-orm/pg-core';

export const users = pgTable('users', {
  id: serial('id').primaryKey(),
  email: text('email').notNull().unique(),
  createdAt: timestamp('created_at').notNull().defaultNow()
});
```

### Configure Drizzle Kit

Drizzle Kit reads `drizzle.config.ts` for migrations and introspection. Set the `dialect`, point `schema` at your table definitions, and pass the connection string through `dbCredentials`:

```ts
import { defineConfig } from 'drizzle-kit';

export default defineConfig({
  dialect: 'postgresql',
  schema: './src/schema.ts',
  out: './drizzle',
  dbCredentials: {
    url: process.env.DATABASE_URL!
  }
});
```

### Run migrations

Generate SQL migration files from your schema, then apply them:

```bash
npx drizzle-kit generate
npx drizzle-kit migrate
```

`generate` diffs your schema against the last snapshot and writes a timestamped `.sql` file into the `out` directory; `migrate` applies any pending files to the database. Point Drizzle Kit at the **direct** engine port (`5432`), not the pooler, because migrations issue DDL that needs a session-level connection. The primary `admin` user owns the generated database and can run schema changes. Use narrower [database roles](/docs/products/databases/postgresql/connections#roles) for application traffic that does not need DDL privileges.

To apply migrations from your application at startup instead of the CLI, use the matching `migrate` helper for your driver:

```ts
import { migrate } from 'drizzle-orm/node-postgres/migrator';
import { db } from './db';

await migrate(db, { migrationsFolder: './drizzle' });
```

### Query with Drizzle

Once the schema is migrated, use the query builder:

```ts
import { desc } from 'drizzle-orm';
import { db } from './db';
import { users } from './schema';

const [user] = await db
  .insert(users)
  .values({ email: 'ada@example.com' })
  .returning();

const recent = await db
  .select()
  .from(users)
  .orderBy(desc(users.createdAt))
  .limit(10);
```

On long-running servers, create the `db` instance once at module scope and reuse it. On serverless, keep a single instance per module scope so warm invocations reuse it, and rely on the pooler to absorb cold-start connection churn.

### Pool connections from serverless

Each running instance opens its own connections to the engine. On serverless and edge platforms (Vercel, Netlify, Cloudflare), short-lived instances can fan out into more backend connections than the engine allows. Route runtime traffic through the [connection pooler](/docs/products/databases/postgresql/connection-pooling) by connecting on the pooler port (`6432`) on the same hostname.

The pooler defaults to **transaction mode**, which does not keep a backend connection across statements, so server-side prepared statements are unavailable. Keep `drizzle.config.ts` and the startup migrator pointed at `DIRECT_URL` so DDL still runs over a session-level connection:

```env
### Runtime: pooled, transaction mode
DATABASE_URL="postgresql://admin:<password>@db-<hash>.<region>.appwrite.center:6432/<database>?sslmode=require"

### Migrations & introspection: direct connection to the engine
DIRECT_URL="postgresql://admin:<password>@db-<hash>.<region>.appwrite.center:5432/<database>?sslmode=require"
```

With **postgres.js**, disable prepared statements by building the client with `prepare: false`, as shown in [Install and configure the driver](#driver).

```ts
import { defineConfig } from 'drizzle-kit';

export default defineConfig({
  dialect: 'postgresql',
  schema: './src/schema.ts',
  out: './drizzle',
  dbCredentials: {
    url: process.env.DIRECT_URL!
  }
});
```

If your application relies on prepared statements, advisory locks, `LISTEN`/`NOTIFY`, temporary tables, or `SET LOCAL`, switch the pooler to **session mode** and remove `prepare: false` with postgres.js. See the [pooler](/docs/products/databases/postgresql/connection-pooling#modes) page for the trade-offs.

### Use a branch for previews and CI

[Branches](/docs/products/databases/postgresql/branches) are isolated copies of a database with their own hostname and connection string. They're ideal for running migrations against throwaway data in a pull-request preview or an integration-test job:

1. Create a branch from the API and read its `connectionString`.
2. Export it as `DIRECT_URL` (and the pooled variant as `DATABASE_URL`).
3. Run `drizzle-kit migrate` and your test suite against the branch.
4. Delete the branch when the job finishes.

Because a branch starts from a storage snapshot, the schema and data match the source database at branch time, so migrations run against realistic data without touching production.

### Related

- [Connections](/docs/products/databases/postgresql/connections): Retrieve credentials, rotate the password, and create database roles.
- [Connection pooler](/docs/products/databases/postgresql/connection-pooling): Pool modes, ports, and read/write splitting for serverless workloads.
- [Branches](/docs/products/databases/postgresql/branches): Ephemeral database copies for preview environments and CI.
- [Network security](/docs/products/databases/postgresql/network-security): TLS modes, certificate verification, mTLS, and IP allowlists.

---

## EF Core

URL: https://appwrite.io/docs/products/databases/postgresql/integrations/ef-core

A native PostgreSQL database is a standard PostgreSQL engine, so [Entity Framework Core](https://learn.microsoft.com/ef/core/) works against it with no Appwrite-specific configuration. Point the Npgsql EF Core provider at the connection string from the [Connections](/docs/products/databases/postgresql/connections) page and use `DbContext`, migrations, and the rest of the toolchain exactly as you would against any self-hosted PostgreSQL server.

**Before you start**

You'll need a native PostgreSQL database in a `ready` state and its credentials. See [native PostgreSQL databases](/docs/products/databases/postgresql) to create one and [Connections](/docs/products/databases/postgresql/connections) to retrieve the connection details. Appwrite provides the generated database name, primary username `admin`, and password in the Console.

### Install the provider

Add the EF Core provider for PostgreSQL:

```bash
dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL
```

Then add the EF Core design-time package (used by the `dotnet ef` tools) and install the CLI tool:

```bash
dotnet add package Microsoft.EntityFrameworkCore.Design

dotnet tool install --global dotnet-ef
```

If your app targets an older framework than the current .NET release, pin the packages to the matching major version instead, for example `--version 8.*` for a .NET 8 app.

### Set the connection string

ADO.NET providers use key/value connection strings rather than a URL. Store it in `appsettings.json` under `ConnectionStrings`, and keep the password out of source control (use [user secrets](https://learn.microsoft.com/aspnet/core/security/app-secrets) or an environment variable in deployments):

```json
{
  "ConnectionStrings": {
    "Default": "Host=db-<hash>.<region>.appwrite.center;Port=5432;Database=<database>;Username=admin;Password=<password>;SSL Mode=Require"
  }
}
```

The edge proxy terminates TLS for every native PostgreSQL database, so `SSL Mode=Require` is all you need. `Require` encrypts the connection without validating the server certificate hostname. For full certificate verification, set `SSL Mode=VerifyFull`: Npgsql validates the chain and hostname against the operating system's trusted roots, and the proxy's certificate is signed by a public CA those roots already include, so no `Root Certificate` parameter is needed:

```text
Host=...;Port=5432;Database=<database>;Username=admin;Password=<password>;SSL Mode=VerifyFull
```

### Configure the DbContext

Define your model and a `DbContext`. The model is ordinary EF Core code, and the provider-specific call comes when you register the context in `Program.cs`:

```csharp
using Microsoft.EntityFrameworkCore;

public class User
{
    public int Id { get; set; }
    public string Email { get; set; } = string.Empty;
    public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}

public class AppDbContext : DbContext
{
    public AppDbContext(DbContextOptions<AppDbContext> options)
        : base(options) { }

    public DbSet<User> Users => Set<User>();
}
```

### Run migrations

Migrations need a session connection and full DDL privileges, so always run them against the direct PostgreSQL host on port `5432`, not the [pooler](/docs/products/databases/postgresql/connection-pooling). The primary `admin` user owns the database; narrower [connection users](/docs/products/databases/postgresql/connections) (`readonly` / `readwrite`) intentionally cannot run DDL.

Create the first migration, then apply it:

```bash
dotnet ef migrations add InitialCreate

dotnet ef database update
```

`dotnet ef database update` reads the same `Default` connection string and applies any pending migrations, recording each in the `__EFMigrationsHistory` table so it only applies new ones next time. In CI or production, prefer generating an idempotent SQL script with `dotnet ef migrations script --idempotent` and applying it as a deploy step.

### Wire up an ASP.NET server

Register the `DbContext` in `Program.cs`, reading the connection string with `GetConnectionString`. A long-running ASP.NET server should connect to the direct PostgreSQL host and let the provider manage its own pool:

Call `UseNpgsql(...)` with the connection string. This is the single entry point for all Npgsql options:

```csharp
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDbContext<AppDbContext>(options =>
    options.UseNpgsql(builder.Configuration.GetConnectionString("Default")));
```

The route handlers use ordinary EF Core queries:

```csharp
var app = builder.Build();

app.MapGet("/users", async (AppDbContext db) =>
    await db.Users.OrderByDescending(u => u.CreatedAt).Take(10).ToListAsync());

app.MapPost("/users", async (AppDbContext db, string email) =>
{
    var user = new User { Email = email };
    db.Users.Add(user);
    await db.SaveChangesAsync();
    return Results.Created($"/users/{user.Id}", user);
});

app.Run();
```

### Connection pooling

Npgsql has a built-in connection pool, enabled by default. Each process keeps its own pool keyed on the connection string, so a long-running ASP.NET server usually connects directly to PostgreSQL and tunes the driver pool with the connection string:

```text
Host=...;Port=5432;Database=<database>;Username=admin;Password=<password>;SSL Mode=Require;Maximum Pool Size=50
```

Set `Maximum Pool Size` to cap concurrent backend connections per process (Npgsql defaults to `100`).

Keep the sum across all your processes under the engine's connection limit for the database's specification.

If you instead run many short-lived instances (serverless functions, per-request containers) that each open their own pool, route them through the Appwrite [connection pooler](/docs/products/databases/postgresql/connection-pooling) on port `6432` to fan many clients onto a small backend pool. The pooler defaults to **transaction mode**, which doesn't hold a backend connection across statements, so server-side prepared statements aren't available:

Disable Npgsql's automatic prepared statements with `Max Auto Prepare=0` when connecting to the transaction-mode pooler:

```text
Host=...;Port=6432;Database=<database>;Username=admin;Password=<password>;SSL Mode=Require;Max Auto Prepare=0
```

If your application relies on prepared statements, advisory locks, or `LISTEN`/`NOTIFY`, switch the pooler to **session mode** instead, see the [pooler](/docs/products/databases/postgresql/connection-pooling#modes) page for the trade-offs.

### Use a branch for previews and CI

Database [branches](/docs/products/databases/postgresql/branches) are instant, isolated copies of a database with their own hostname and credentials. They're ideal for running migrations against throwaway data in a pull-request preview or an integration-test job:

1. Create a branch from the API and read its connection details.
2. Build the connection string for the branch host and pass it as the `Default` connection string.
3. Run `dotnet ef database update` and your test suite against the branch.
4. Delete the branch when the job finishes.

Because a branch starts from a storage snapshot, the schema and data match the source database at branch time, so migrations run against representative data without touching production.

### Related

- [Connect](/docs/products/databases/postgresql/connections): Retrieve credentials, rotate the password, and create scoped connection users.
- [Connection pooler](/docs/products/databases/postgresql/connection-pooling): Pool modes, ports, and read/write splitting for serverless workloads.
- [Branches](/docs/products/databases/postgresql/branches): Ephemeral database copies for preview environments and CI.
- [Network](/docs/products/databases/postgresql/network-security): TLS modes, certificate verification, mTLS, and IP allowlists.

---

## FastAPI

URL: https://appwrite.io/docs/products/databases/postgresql/integrations/fastapi

An Appwrite native PostgreSQL database is a standard PostgreSQL engine, so [FastAPI](https://fastapi.tiangolo.com/) with [SQLAlchemy](https://docs.sqlalchemy.org/) and an async driver works against it with no Appwrite-specific configuration. You point `create_async_engine` at the connection string from the [connections](/docs/products/databases/postgresql/connections) page and use the SQLAlchemy ORM, the FastAPI dependency system, and Alembic exactly as you would against any self-hosted PostgreSQL server.

**Before you start**

You'll need a native PostgreSQL database in a `ready` state and its credentials. See [native PostgreSQL databases](/docs/products/databases/postgresql) to create one and [connections](/docs/products/databases/postgresql/connections) to retrieve the connection string. The primary user is `admin`, and the database name is generated for each database.

### Install dependencies

```bash
pip install "fastapi[standard]" "sqlalchemy[asyncio]" asyncpg alembic
```

### Set the connection string

Copy the connection string from the Console **Credentials** dialog, or fetch it with the [API](/docs/products/databases/postgresql/connections#credentials). Put it in your environment, never commit it. SQLAlchemy's asyncpg dialect uses the `postgresql+asyncpg://` scheme, so swap the leading `postgresql://` for it:

```env
DATABASE_URL="postgresql+asyncpg://admin:<password>@db-<hash>.<region>.appwrite.center:5432/<database>"
```

The string Appwrite returns ends with `?sslmode=require`. Drop that query parameter for asyncpg, asyncpg does not read `sslmode` from the URL. TLS is configured through `connect_args` instead, shown below.

This guide uses the [asyncpg](https://magicstack.github.io/asyncpg/current/) driver; the same patterns apply to the async `postgresql+psycopg` dialect by changing the URL scheme.

### Create the async engine

The edge proxy terminates TLS for every native PostgreSQL database, so encryption is mandatory. asyncpg's `ssl` argument accepts the libpq-style strings (`require`, `verify-ca`, `verify-full`), `True`, or an `ssl.SSLContext`. Pass it through `connect_args`:

```python
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine

engine = create_async_engine(
    settings.database_url,
    connect_args={"ssl": "require"},
    pool_size=10,
    max_overflow=5,
    pool_pre_ping=True,
)

SessionLocal = async_sessionmaker(engine, expire_on_commit=False)
```

`ssl="require"` encrypts the connection without validating the certificate chain. For full verification, pass `ssl="verify-full"` instead; the server certificate is signed by a well-known public CA, so validation succeeds against the system trust store without a custom CA bundle, see the [Network](/docs/products/databases/postgresql/network-security) page.

### Define a model

```python
from datetime import datetime

from sqlalchemy import func
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column

class Base(DeclarativeBase):
    pass

class User(Base):
    __tablename__ = "users"

    id: Mapped[int] = mapped_column(primary_key=True)
    email: Mapped[str] = mapped_column(unique=True)
    created_at: Mapped[datetime] = mapped_column(server_default=func.now())
```

### Inject a session per request

FastAPI's dependency system gives each request its own `AsyncSession` and closes it when the request finishes. Define a dependency that yields a session, then annotate path operations with it:

```python
from typing import Annotated

from fastapi import Depends, FastAPI
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

app = FastAPI()

async def get_session():
    async with SessionLocal() as session:
        yield session

SessionDep = Annotated[AsyncSession, Depends(get_session)]

@app.post("/users")
async def create_user(email: str, session: SessionDep):
    user = User(email=email)
    session.add(user)
    await session.commit()
    await session.refresh(user)
    return user

@app.get("/users")
async def list_users(session: SessionDep):
    result = await session.scalars(select(User).order_by(User.created_at.desc()))
    return result.all()
```

Create the engine once at module scope and reuse it for the whole process, the pool lives inside it. Don't open a new engine per request.

### Run migrations

Generate Alembic's async scaffold, which opens the connection asynchronously and hands a sync connection to the migration context:

```bash
alembic init -t async migrations
```

Point `target_metadata` at `Base.metadata` in `migrations/env.py`, then autogenerate and apply:

```bash
alembic revision --autogenerate -m "init"
alembic upgrade head
```

Run migrations against the **direct** engine port (`5432`), not the pooler. DDL needs a real session-level connection, and Alembic's async template already uses a `NullPool`, so a fresh connection is opened and closed per run. The primary `admin` user owns the default database and can run schema changes. Narrower [database roles](/docs/products/databases/postgresql/connections#roles) should only receive the privileges your application needs.

### Pool sizing

A long-running `uvicorn` server holds a SQLAlchemy pool for its lifetime, so connect to the **direct** engine port (`5432`) with a sized pool. Keep `pool_size` × the number of server processes under the connection limit of your [specification](/docs/products/databases/postgresql#specifications), and let `max_overflow` absorb short bursts:

```python
engine = create_async_engine(
    settings.database_url,
    connect_args={"ssl": "require"},
    pool_size=10,
    max_overflow=5,
)
```

When the same app runs on a serverless platform (for example an Appwrite [function](/docs/products/functions)) where each invocation is a fresh instance, that fans out into far more backend connections than the engine allows. Route runtime traffic through the [connection pooler](/docs/products/databases/postgresql/connection-pooling) on port `6432` instead, and size the pool small per instance:

```env
DATABASE_URL="postgresql+asyncpg://admin:<password>@db-<hash>.<region>.appwrite.center:6432/<database>"
```

### Disable prepared statements on the transaction pooler

The pooler defaults to **transaction mode**, which does not keep a backend connection across statements. asyncpg relies on server-side prepared statements, which transaction mode cannot support, so turn the caches off. This needs **two** settings: asyncpg's own `statement_cache_size` in `connect_args` and SQLAlchemy's dialect-level `prepared_statement_cache_size` in the connection URL:

```env
DATABASE_URL="postgresql+asyncpg://admin:<password>@db-<hash>.<region>.appwrite.center:6432/<database>?prepared_statement_cache_size=0"
```

And in the engine setup:

```python
from sqlalchemy import NullPool

engine = create_async_engine(
    settings.database_url,
    connect_args={"ssl": "require", "statement_cache_size": 0},
    poolclass=NullPool,
)
```

`statement_cache_size=0` disables asyncpg's prepared statement cache, and `prepared_statement_cache_size=0` disables the dialect's own per-connection statement cache. Use `NullPool` so SQLAlchemy doesn't keep its own pool on top of the pooler's.

If your application relies on prepared statements, advisory locks, `LISTEN`/`NOTIFY`, or temporary tables, switch the pooler to **session mode** instead and keep statement caching on, see the [pooler](/docs/products/databases/postgresql/connection-pooling#modes) page for the trade-offs.

### Use a branch for previews and CI

[Branches](/docs/products/databases/postgresql/branches) are instant, isolated copies of a database with their own hostname and connection string. They're ideal for running migrations against throwaway data in a pull-request preview or an integration-test job:

1. Create a branch from the API and read its `connectionString`.
2. Rewrite the scheme to `postgresql+asyncpg://` and export it as `DATABASE_URL`.
3. Run `alembic upgrade head` against the branch's direct port, then your test suite.
4. Delete the branch when the job finishes.

Because a branch starts from a storage snapshot, the schema and data match the source database at branch time, so migrations run against realistic data without touching production.

### Related

- [Connections](/docs/products/databases/postgresql/connections): Retrieve credentials, rotate the password, and create scoped database roles.
- [Connection pooler](/docs/products/databases/postgresql/connection-pooling): Pool modes, ports, and read/write splitting for serverless workloads.
- [Branches](/docs/products/databases/postgresql/branches): Ephemeral database copies for preview environments and CI.
- [Network](/docs/products/databases/postgresql/network-security): TLS modes, certificate verification, mTLS, and IP allowlists.

---

## GORM

URL: https://appwrite.io/docs/products/databases/postgresql/integrations/gorm

A native PostgreSQL database is a standard PostgreSQL engine, so [GORM](https://gorm.io/) talks to it with no Appwrite-specific configuration. You build a connection string from the credentials in the Console, hand it to the GORM PostgreSQL driver, and use models, `AutoMigrate`, and the query API exactly as you would against any self-hosted PostgreSQL server.

**Before you start**

You'll need a native PostgreSQL database in a `ready` state and its credentials. See [PostgreSQL databases](/docs/products/databases/postgresql) to create one. To retrieve the hostname, password, database name, and connection string, open the database in the Console and click **Credentials**. The **Credentials** page includes **Details**, **DSN**, **.env**, **Prisma**, **Drizzle**, and **psql** tabs. The primary user is `admin`, and Appwrite generates the database name for each database.

### Build the DSN

The edge proxy terminates TLS for every native PostgreSQL database, so no certificate file is needed. Keep the password out of source and read the connection details from the environment:

```env
DB_HOST="db-<hash>.<region>.appwrite.center"
DB_NAME="<database>"
DB_PASSWORD="<password>"
```

GORM's PostgreSQL driver accepts the pgx key/value DSN. Set `sslmode=require`:

```go
dsn := fmt.Sprintf(
    "host=%s user=admin password=%s dbname=%s port=5432 sslmode=require",
    os.Getenv("DB_HOST"), os.Getenv("DB_PASSWORD"), os.Getenv("DB_NAME"),
)
```

The URL form from the **DSN** tab works too, and the driver parses either format: `postgresql://admin:<password>@db-<hash>.<region>.appwrite.center:5432/<database>?sslmode=require`. For full certificate verification, add `sslmode=verify-full`, no CA file needed: Go's drivers validate against the system certificate pool, and the proxy's certificate is signed by a public CA.

For mTLS, see the [Network](/docs/products/databases/postgresql/network-security) page.

### Open a connection

Pass the DSN to the driver's `Open` function and call `gorm.Open`:

```go
package main

import (
    "fmt"
    "os"

    "gorm.io/driver/postgres"
    "gorm.io/gorm"
)

func main() {
    dsn := fmt.Sprintf(
        "host=%s user=admin password=%s dbname=%s port=5432 sslmode=require",
        os.Getenv("DB_HOST"), os.Getenv("DB_PASSWORD"), os.Getenv("DB_NAME"),
    )

    db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
    if err != nil {
        panic(err)
    }

    _ = db
}
```

Connect to the **direct** engine port (`5432`) for a long-running server, see [pool sizing](#pool) below. If you route through the [connection pooler](/docs/products/databases/postgresql/connection-pooling) in its default transaction mode, use `postgres.New` with `PreferSimpleProtocol: true` so the driver stops issuing implicit prepared statements, which transaction mode cannot keep across statements:

```go
db, err := gorm.Open(postgres.New(postgres.Config{
    DSN:                  dsn, // pooler host, port 6432
    PreferSimpleProtocol: true,
}), &gorm.Config{})
```

### Define a model and migrate

Declare your models as Go structs and let GORM create the tables with `AutoMigrate`:

```go
type User struct {
    ID        uint   `gorm:"primaryKey"`
    Email     string `gorm:"uniqueIndex"`
    CreatedAt time.Time
}

if err := db.AutoMigrate(&User{}); err != nil {
    panic(err)
}
```

`AutoMigrate` creates the table if it's missing and adds any missing columns and indexes. It does not drop columns or change existing column types, so it's convenient in development but not a substitute for versioned migrations in production. Either way, run schema changes against the **direct** engine port: DDL needs a real session connection, and the primary `admin` user owns the generated database. Scoped connection users (`readonly` / `readwrite`) intentionally cannot run DDL.

For versioned migrations, [golang-migrate](https://github.com/golang-migrate/migrate) runs ordered up/down files. Point it at the direct engine port:

```bash
migrate -path ./migrations \
  -database "postgresql://admin:$DB_PASSWORD@$DB_HOST:5432/$DB_NAME?sslmode=require" \
  up
```

### Size the connection pool

GORM manages a `database/sql` pool under the hood. A long-running Go server holds that pool for its whole lifetime, so connect to the **direct** engine port and cap the pool yourself against the engine's connection limit. Reach the underlying `*sql.DB` with `db.DB()`:

```go
sqlDB, err := db.DB()
if err != nil {
    panic(err)
}

sqlDB.SetMaxOpenConns(25)
sqlDB.SetMaxIdleConns(25)
sqlDB.SetConnMaxLifetime(time.Hour)
```

Keep the sum of `SetMaxOpenConns` across every instance below the specification's `maxConnections`. If you run many instances or a serverless/edge runtime that opens a fresh pool per invocation, route through the [connection pooler](/docs/products/databases/postgresql/connection-pooling) and keep each instance's pool small. The pooler multiplexes them onto a handful of backend connections.

### Use sqlx instead

If you prefer raw SQL with light struct scanning, [sqlx](https://github.com/jmoiron/sqlx) wraps `database/sql` and uses the same DSN.

Register the `pgx` stdlib driver (or `lib/pq`) and connect:

```go
import (
    _ "github.com/jackc/pgx/v5/stdlib"
    "github.com/jmoiron/sqlx"
)

db, err := sqlx.Connect("pgx",
    fmt.Sprintf("host=%s user=admin password=%s dbname=%s port=5432 sslmode=require",
        os.Getenv("DB_HOST"), os.Getenv("DB_PASSWORD"), os.Getenv("DB_NAME")))
if err != nil {
    panic(err)
}

db.SetMaxOpenConns(25)
```

The same pooler caveat applies: in transaction mode, disable implicit prepared statements (the pgx stdlib driver exposes a simple-protocol option, and `lib/pq` can be told to skip prepares), or use [session mode](/docs/products/databases/postgresql/connection-pooling#modes). `sslmode=require` needs no CA file, and neither does `verify-full`: Go's drivers fall back to the system certificate pool when `sslrootcert` is unset, and the proxy's certificate is signed by a public CA that pool already trusts. Set `sslrootcert` only when the container image ships without CA certificates.

### Use a branch for previews and CI

[Branches](/docs/products/databases/postgresql/branches) are instant, isolated copies of a database with their own hostname and connection string, ideal for running migrations against throwaway data in a pull-request preview or an integration-test job:

1. Create a branch from the API and read its `connectionString`.
2. Export the host and password into the environment your tests read.
3. Run `migrate ... up` (or `AutoMigrate`) and your test suite against the branch's direct port.
4. Delete the branch when the job finishes.

Because a branch starts from a storage snapshot, the schema and data match the source database at branch time, so migrations run against realistic data without touching production.

### Related

- [Connect](/docs/products/databases/postgresql/connections): Retrieve credentials, rotate the password, and create scoped connection users.
- [Connection pooler](/docs/products/databases/postgresql/connection-pooling): Pool modes, ports, and read/write splitting for serverless workloads.
- [Branches](/docs/products/databases/postgresql/branches): Ephemeral database copies for preview environments and CI.
- [Network](/docs/products/databases/postgresql/network-security): TLS modes, certificate verification, mTLS, and IP allowlists.

---

## Grafana

URL: https://appwrite.io/docs/products/databases/postgresql/integrations/grafana

An Appwrite [native PostgreSQL database](/docs/products/databases/postgresql) exposes a standard managed PostgreSQL 18 or 17 engine, so [Grafana](https://grafana.com/) connects to it through the built-in **PostgreSQL data source** with no Appwrite-specific configuration. Point the data source at your database hostname, authenticate with a read-only reporting role, and query your tables to build dashboards and alerts.

**Before you start**

You'll need a native PostgreSQL database in a `ready` state and its credentials. In the Console, open the database and click **Credentials**. Use the **Details** tab for individual values, or copy a ready-made string from the **DSN**, **.env**, **Prisma**, **Drizzle**, or **psql** tab. You can also call `postgresql.get()` from the Appwrite API to read `hostname`, `connectionUser`, `connectionPassword`, and `connectionString`. The primary user is `admin`, and Appwrite generates the database name for each database.

### Create a reporting role

Dashboards should not connect as the primary `admin` user, which owns the database and can run schema changes. Create a PostgreSQL role for Grafana from the **Roles** tab of your database in the Console, then grant it the `SELECT` privileges your dashboards need from the SQL editor or `psql`.

Use that role's name and password in the Grafana data source below. For more about retrieving credentials, rotating the primary password, and managing database roles, see [Connections](/docs/products/databases/postgresql/connections).

### Choose a connection target

Grafana holds its data source connections open for the lifetime of the process. Long-lived connections should use either the direct PostgreSQL port `5432` or a [connection pooler](/docs/products/databases/postgresql/connection-pooling) running in **session mode**.

Do not point Grafana at the **transaction-mode** pooler. Transaction mode hands a backend connection back to the pool after every statement, which breaks the session assumptions Grafana relies on for connection reuse and prepared statements. For a typical dashboard workload the direct PostgreSQL port is the simplest choice. See the [pooler modes](/docs/products/databases/postgresql/connection-pooling#modes) page for the trade-offs.

### Add the PostgreSQL data source

In Grafana, open **Connections** > **Data sources** > **Add data source** and select **PostgreSQL**. Fill in the connection details using the values from your native PostgreSQL database:

| Field | Value |
| --- | --- |
| **Host URL** | `db-<hash>.<region>.appwrite.center:5432` |
| **Database name** | `<database>` |
| **Username** | `grafana_ro` |
| **Password** | the password for the reporting role |
| **TLS/SSL Mode** | `require` |
| **Version** | PostgreSQL 10+; Grafana auto-detects the server version on save when it can connect |

Regions are `fra`, `nyc`, `sfo`, `sgp`, `syd`, and `tor`. The edge proxy terminates TLS for every native PostgreSQL database, so **TLS/SSL Mode** `require` works with no certificate upload.

If you use Grafana Cloud and restrict database access with an IP allowlist, add the Grafana Cloud outbound IP ranges for your stack to the database allowlist. Grafana Cloud can reach the public Appwrite database hostname directly. Private connectivity features are only needed when the database is on a private network.

Under **Connection limits**, keep the connection counts modest so Grafana doesn't exhaust the engine's connection budget. **Max open** caps total connections from this Grafana instance, **Max idle** caps pooled idle connections, and **Max lifetime** recycles connections after the given number of seconds. Select **Save & test** to verify connectivity.

### Provision from YAML

Instead of configuring the data source by hand, you can [provision](https://grafana.com/docs/grafana/latest/administration/provisioning/) it declaratively. Drop a file into Grafana's `provisioning/datasources/` directory and read the password from an environment variable so it never lands in source control:

```yaml
apiVersion: 1

datasources:
  - name: Appwrite native PostgreSQL
    type: postgres
    url: db-<hash>.<region>.appwrite.center:5432
    user: grafana_ro
    jsonData:
      database: <database>
      sslmode: require
      postgresVersion: 1500
      maxOpenConns: 5
      maxIdleConns: 2
      maxIdleConnsAuto: true
      connMaxLifetime: 14400
    secureJsonData:
      password: $GRAFANA_DB_PASSWORD
    editable: false
```

`postgresVersion` takes Grafana's encoded version values (`1500` for PostgreSQL 15, currently the highest option), so pick the highest value available for a native PostgreSQL database. The `database` key lives under `jsonData` in current Grafana releases, and Grafana expands `$GRAFANA_DB_PASSWORD` from the process environment when it loads the provisioning file. See the [PostgreSQL data source](https://grafana.com/docs/grafana/latest/datasources/postgres/configure/) docs for every available field.

### Build a panel

With the data source connected, create a dashboard and add a panel backed by it. Switch the query editor to code mode and write a read-only query against your tables. For example, to plot daily sign-ups from a `users` table over time:

```text
SELECT
  date_trunc('day', created_at) AS time,
  count(*) AS signups
FROM users
GROUP BY 1
ORDER BY 1
```

Grafana maps the `time` column to the panel's time axis and `signups` to the value. Because the data source authenticates as a read-only reporting role, any query that attempts to write fails at the database, which keeps a misconfigured panel from mutating production data.

### Related

- [PostgreSQL databases](/docs/products/databases/postgresql): Overview of native PostgreSQL database engines, versions, and regions.
- [Connect](/docs/products/databases/postgresql/connections): Retrieve credentials, rotate the password, and manage database roles.
- [Connection pooler](/docs/products/databases/postgresql/connection-pooling): Pool modes and ports. Use the direct PostgreSQL port or session mode for Grafana.
- [Network](/docs/products/databases/postgresql/network-security): TLS modes, certificate verification, and IP allowlists.

[Connect to a native PostgreSQL database](/docs/products/databases/postgresql/connections)

---

## Laravel

URL: https://appwrite.io/docs/products/databases/postgresql/integrations/laravel

A native PostgreSQL database is a standard PostgreSQL engine, so [Laravel](https://laravel.com/docs) works against it with no Appwrite-specific configuration. Point the `pgsql` connection in `config/database.php` at the credentials from the [Connections](/docs/products/databases/postgresql/connections) page, then use Eloquent, the query builder, migrations, and queues as you would against any PostgreSQL server.

**Before you start**

You'll need a native PostgreSQL database in a `ready` state and its credentials. See [PostgreSQL databases](/docs/products/databases/postgresql) to create one. To retrieve credentials, open the database in the Console, click **Credentials**, and use the **Details**, **DSN**, **.env**, **Prisma**, **Drizzle**, or **psql** tab. The primary username is `admin`, and the database name is generated per database.

### Configure the connection

Laravel reads database credentials from `.env`. Copy the values from the Console credentials dialog, or fetch them with [`postgresql.get()`](/docs/products/databases/postgresql/connections#credentials), and set the matching connection. Never commit `.env`:

```env
DB_CONNECTION=pgsql
DB_HOST=db-<hash>.<region>.appwrite.center
DB_PORT=5432
DB_DATABASE=<database>
DB_USERNAME=admin
DB_PASSWORD=<password>
DB_SSLMODE=require
```

The scaffolded `config/database.php` wires these variables into the `pgsql` connection, including SSL:

```php
'pgsql' => [
    'driver' => 'pgsql',
    'url' => env('DB_URL'),
    'host' => env('DB_HOST', '127.0.0.1'),
    'port' => env('DB_PORT', '5432'),
    'database' => env('DB_DATABASE', 'laravel'),
    'username' => env('DB_USERNAME', 'root'),
    'password' => env('DB_PASSWORD', ''),
    'charset' => env('DB_CHARSET', 'utf8'),
    'prefix' => '',
    'prefix_indexes' => true,
    'search_path' => 'public',
    'sslmode' => env('DB_SSLMODE', 'prefer'),
],
```

`sslmode` is a top-level key on the PostgreSQL connection. Setting `DB_SSLMODE=require` matches the `sslmode=require` that Appwrite uses. Appwrite Cloud terminates TLS for every native PostgreSQL database, so certificate files are not needed for `require`. For full certificate verification, set `DB_SSLMODE=verify-full` and point `sslrootcert` at a trusted root store, `system` on libpq 16+, or your OS bundle such as `/etc/ssl/certs/ca-certificates.crt`. The proxy certificate is signed by a public CA, so there is no Appwrite-specific CA to download:

```php
'sslmode' => env('DB_SSLMODE', 'prefer'),
'sslrootcert' => env('DB_SSLROOTCERT'),
```

### Run migrations

Define your schema with a migration:

```php
Schema::create('posts', function (Blueprint $table) {
    $table->id();
    $table->string('title');
    $table->text('body');
    $table->timestamps();
});
```

Apply migrations from your machine or a deploy step:

```bash
php artisan migrate

### non-interactive, for CI and production deploys
php artisan migrate --force
```

Run `migrate` against the **direct** PostgreSQL port (`5432`), not the pooler. Migrations issue DDL that needs a session-level connection, and the transaction-mode pooler can't keep state across statements. The primary `admin` user owns the default database and can run schema changes. Narrower [connection users](/docs/products/databases/postgresql/connections#roles), such as read-only reporting roles, should not run DDL.

### Query with Eloquent

Once the schema is migrated, use Eloquent models and the query builder as usual:

```php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    protected $fillable = [
        'title',
        'body',
    ];
}
```

Create and query posts through the model:

```php
use App\Models\Post;

$post = Post::create([
    'title' => 'Hello from a native PostgreSQL database',
    'body' => 'Stored in a native PostgreSQL database.',
]);

$recent = Post::query()
    ->orderByDesc('created_at')
    ->limit(10)
    ->get();
```

Nothing about the native PostgreSQL database changes how Eloquent, relationships, transactions, or the query builder behave. It is a standard PostgreSQL server behind a TLS connection.

### Pool connections from serverless

The right port depends on how your app runs.

A **long-running** PHP process, traditional PHP-FPM with persistent connections, [Laravel Octane](https://laravel.com/docs/octane), or a queue worker, holds its own backend connection for its lifetime. Point these at the **direct** PostgreSQL port (`5432`), or at the [connection pooler](/docs/products/databases/postgresql/connection-pooling) in **session mode**. Don't put a long-lived process behind the transaction-mode pooler.

A **serverless** or per-request deployment (Vercel, AWS Lambda, Cloud Run) opens a fresh connection on every invocation and fans out into far more backend connections than the engine allows. Route that traffic through the pooler's **transaction-mode** port (`6432`) on the same hostname, and give Laravel direct connection details for migrations and other schema operations:

```env
### Runtime: pooled, transaction mode
DB_HOST=db-<hash>.<region>.appwrite.center
DB_PORT=6432
DB_DATABASE=<database>
DB_USERNAME=admin
DB_PASSWORD=<password>
DB_POOLED=true

### Migrations and schema operations: direct PostgreSQL port
DB_DIRECT_HOST=db-<hash>.<region>.appwrite.center
DB_DIRECT_PORT=5432
DB_DIRECT_USERNAME=admin
DB_DIRECT_PASSWORD=<password>
DB_DIRECT_SSLMODE=require
```

Extend the `pgsql` connection in `config/database.php` with Laravel's pooled connection keys:

```php
'pgsql' => [
    'driver' => 'pgsql',
    'url' => env('DB_URL'),
    'host' => env('DB_HOST', '127.0.0.1'),
    'port' => env('DB_PORT', '5432'),
    'database' => env('DB_DATABASE', 'laravel'),
    'username' => env('DB_USERNAME', 'root'),
    'password' => env('DB_PASSWORD', ''),
    'charset' => env('DB_CHARSET', 'utf8'),
    'prefix' => '',
    'prefix_indexes' => true,
    'search_path' => 'public',
    'sslmode' => env('DB_SSLMODE', 'prefer'),
    'pooled' => env('DB_POOLED', false),
    'direct' => array_filter([
        'host' => env('DB_DIRECT_HOST'),
        'port' => env('DB_DIRECT_PORT'),
        'username' => env('DB_DIRECT_USERNAME'),
        'password' => env('DB_DIRECT_PASSWORD'),
        'sslmode' => env('DB_DIRECT_SSLMODE'),
    ]),
],
```

Laravel uses the direct connection for migrations, schema dumps, restores, and database inspection commands when pooled mode is enabled. You can also call `DB::connection('pgsql::direct')` for schema operations that need the direct port. The transaction-mode pooler does not keep a backend connection across statements, so server-side prepared statements, advisory locks, `LISTEN`/`NOTIFY`, and `SET LOCAL` are unavailable. If your app relies on those, use **session mode**. See the [pooler](/docs/products/databases/postgresql/connection-pooling#modes) page for the trade-offs.

### Queues and Horizon

A queue worker is a long-running process. `php artisan queue:work` boots once and processes jobs for its whole lifetime, holding a persistent database connection the entire time. The same applies to every worker that [Laravel Horizon](https://laravel.com/docs/horizon) supervises. Treat workers like any other long-lived process:

- Connect them to the **direct** PostgreSQL port (`5432`) or the **session-mode** pooler, never the transaction-mode pooler.
- Restart workers periodically with `--max-time` or `--max-jobs` so a fresh process reclaims memory and reopens its connection. Supervisor or Horizon restarts them automatically.

```bash
php artisan queue:work --max-time=3600 --max-jobs=500
```

Each worker counts as one backend connection, so size your worker pool (and Horizon's `maxProcesses`) against the connection budget of your [specification](/docs/products/databases/postgresql). Horizon itself requires Redis for the queue backend; only your application's data connection touches the native PostgreSQL database.

### Use a branch for previews and CI

PostgreSQL [branches](/docs/products/databases/postgresql/branches) are instant, isolated copies of a database with their own hostname. Create them from the API for running migrations against throwaway data in a pull-request preview or an integration-test job:

1. Create a branch from the API and read its connection details.
2. Export them as the `DB_*` variables for the job.
3. Run `php artisan migrate --force` and your test suite against the branch.
4. Delete the branch when the job finishes.

Because a branch starts from a storage snapshot, the schema and data match the source database at branch time, so migrations and tests run against realistic data without touching production.

### Related

- [Connect](/docs/products/databases/postgresql/connections): Retrieve credentials, rotate the password, and create scoped connection users.
- [Connection pooler](/docs/products/databases/postgresql/connection-pooling): Pool modes, ports, and read/write splitting for serverless workloads.
- [Branches](/docs/products/databases/postgresql/branches): Ephemeral database copies for preview environments and CI.
- [Network](/docs/products/databases/postgresql/network-security): TLS modes, certificate verification, mTLS, and IP allowlists.

---

## Metabase

URL: https://appwrite.io/docs/products/databases/postgresql/integrations/metabase

Appwrite's native PostgreSQL database is a standard PostgreSQL engine, so [Metabase](https://www.metabase.com/) can connect to it without an Appwrite-specific adapter. Add the database in Metabase, point it at the host from the [Connections](/docs/products/databases/postgresql/connections) page, and Metabase will sync the schema so your team can build questions and dashboards.

**Before you start**

You'll need a native PostgreSQL database in a `ready` state and its credentials. In the Console, open the database and click **Credentials**. Copy the individual values from the **Details** tab, or use the **DSN**, **.env**, or **psql** tab. You can also fetch the same values with [`postgresql.get()`](/docs/products/databases/postgresql/connections#credentials). The primary user is `admin`, and the database name is generated per database.

### Use a read-only PostgreSQL role

Dashboards should not connect as the primary `admin` user. That role owns the database and can run schema changes. For analytics, create a read-only PostgreSQL role for Metabase from the database **Roles** tab in the Console, then grant it access only to the schemas and tables Metabase should query.

At minimum, a reporting role needs permission to connect to the database, usage on the schemas you want to expose, and `SELECT` on the tables and views in those schemas. Use the [SQL editor](/docs/products/databases/postgresql/quick-start#first-queries), `psql`, or your migration workflow to apply the grants after you create the role. See [Database roles](/docs/products/databases/postgresql/connections#roles) for how roles work with native PostgreSQL databases.

If you plan to use Metabase features that write data, such as editable table data, use a separate connection with the specific write privileges that feature needs. Keep the analytics connection read-only.

### Add the database in Metabase

In Metabase, click the grid icon, then open **Admin > Databases > Add a database**. Choose **PostgreSQL** as the database type and fill in the connection form with the values from the [Connections](/docs/products/databases/postgresql/connections) page.

| Field | Value |
|-------|-------|
| Host | `db-<hash>.<region>.appwrite.center` |
| Port | `5432` |
| Database name | `<database>` |
| Username | your read-only PostgreSQL role |
| Password | the role password |

Turn on **Use a secure connection (SSL)** and set **SSL Mode** to `require`. Appwrite Cloud requires TLS for native PostgreSQL connections. If you choose `verify-ca` or `verify-full`, Metabase also requires a root certificate in PEM format. See [Network security](/docs/products/databases/postgresql/network-security) for Appwrite TLS and network controls.

**Use a session-safe connection**

Metabase keeps database sessions open and can use session-level PostgreSQL behavior while exploring data. Connect it to the direct PostgreSQL port (`5432`) or to the [connection pooler](/docs/products/databases/postgresql/connection-pooling) in `session` mode. Transaction-mode pooling can break session-level features because a client session is not bound to one backend connection.

Click **Save changes**. Metabase verifies the connection and starts its first schema sync.

### Build a question or dashboard

Once the first sync finishes, your tables appear in the data picker. To build your first chart:

1. Click **+ New > Question** and pick your Appwrite PostgreSQL database as the data source.
2. Choose a table, add a summary such as count, sum, or average, and group by a column such as a timestamp.
3. Switch the visualization to a line, bar, or table view, then save the question.
4. Add saved questions to a dashboard and use filters to slice data across cards.

Because the analytics connection is read-only, native SQL questions can inspect data without changing it. Queries that attempt writes will fail unless you intentionally connect Metabase with a role that has write privileges.

### How Metabase syncs your schema

After you connect, Metabase scans the database to discover tables, columns, constraints, and field metadata, then keeps that metadata current on a schedule:

- A lightweight schema sync runs hourly by default.
- A more intensive field-value scan runs daily by default to populate filter dropdowns.

New tables and columns appear after the next sync. To pull them in immediately, open **Admin > Databases > your database** and click **Sync database schema**. You can also restrict which schemas Metabase tracks and adjust the sync and scan cadence from the database settings. Refer to the [Metabase documentation](https://www.metabase.com/docs/latest/databases/sync-scan) for the full set of sync and scan options.

### Use a branch for testing

[Branches](/docs/products/databases/postgresql/branches) are isolated copies of a native PostgreSQL database with their own host and credentials. Point a second Metabase database connection at a branch when you want to validate a dashboard against a snapshot of production data without querying the live database. Branches are managed through the API, and you can delete the branch when testing is complete.

### Related

- [PostgreSQL](/docs/products/databases/postgresql): Provision and manage a native PostgreSQL database for your project.
- [Connections](/docs/products/databases/postgresql/connections): Retrieve credentials, rotate the primary password, and manage PostgreSQL roles.
- [Connection pooling](/docs/products/databases/postgresql/connection-pooling): Pool modes and ports, including why BI tools need session or direct connections.
- [Network security](/docs/products/databases/postgresql/network-security): TLS, certificate verification, IP allowlists, and idle connection timeouts.

---

## Next.js

URL: https://appwrite.io/docs/products/databases/postgresql/integrations/nextjs

An Appwrite native PostgreSQL database works with standard PostgreSQL drivers and ORMs, so a [Next.js](https://nextjs.org/) App Router application can query it from server-side code. Point your driver at the connection string from the [Connections](/docs/products/databases/postgresql/connections) page and keep all database access on the server.

**Before you start**

You'll need a native PostgreSQL database in a `ready` state and its credentials. See [PostgreSQL](/docs/products/databases/postgresql) to create one and [Connections](/docs/products/databases/postgresql/connections) to retrieve the connection string. The primary user is `admin`, and Appwrite generates the database name for each database.

### Where to connect

In the App Router, every server-side execution context runs on the **Node.js runtime by default**, and the Node.js runtime can open raw TCP sockets. That means you can use a standard database driver from any of these:

- **Route Handlers** (`app/api/.../route.ts`) for public endpoints, webhooks, and REST-style APIs.
- **Server Actions** (`'use server'` functions) for form submissions and app-internal mutations.
- **Server Components** (`async` components) for read queries that render straight into the page.

Never import a database driver into a Client Component (`'use client'`) or ship the connection string to the browser. Keep all database access on the server.

The one exception is the **Edge runtime** (`export const runtime = 'edge'`), which runs on a constrained environment that **cannot open TCP database sockets**. If a route opts into Edge, use the [SQL API](/docs/products/databases/postgresql/quick-start#first-queries) over HTTPS instead, see [the Edge runtime section](#edge) below.

### Environment variables

Put the connection string in your environment and never commit it. For local development, use `.env.local` (Next.js loads it automatically and it's git-ignored by default):

```env
DATABASE_URL="postgresql://admin:<password>@db-<hash>.<region>.appwrite.center:6432/<database>?sslmode=require"
DIRECT_URL="postgresql://admin:<password>@db-<hash>.<region>.appwrite.center:5432/<database>?sslmode=require"
```

`DATABASE_URL` points at the [connection pooler](/docs/products/databases/postgresql/connection-pooling) port (`6432`) for runtime traffic, and `DIRECT_URL` points at the PostgreSQL engine port (`5432`) for migrations. The next section explains why. The `sslmode=require` parameter is already part of the string Appwrite returns, so no extra certificate configuration is needed. For full verification (`verify-full`) or mTLS, see the [Network security](/docs/products/databases/postgresql/network-security) page.

### Pool serverless connections

When you deploy to Vercel, Netlify, or any serverless platform, each invocation can spin up a fresh instance with its own connection pool. Hundreds of concurrent invocations fan out into far more backend connections than the engine allows. Route runtime traffic through the [connection pooler](/docs/products/databases/postgresql/connection-pooling) on the pooler port so it can multiplex those instances over a small number of backend connections.

The pooler defaults to **transaction mode**, which does not keep a backend connection across statements, so server-side prepared statements aren't available. Reserve the engine port for migrations, which need a session-level connection. The two-URL split above (`DATABASE_URL` pooled, `DIRECT_URL` direct) is exactly what an ORM like Prisma expects.

### Use a singleton client

Instantiate one client per module scope and reuse it across invocations, so warm serverless instances don't reconnect on every request. In development, Next.js hot-reload re-evaluates modules, which can leak connections, so cache the client on `globalThis`.

With [postgres.js](https://github.com/porsager/postgres) (note `prepare: false` for transaction-mode pooling):

```ts
// lib/db.ts
import postgres from 'postgres';

const globalForDb = globalThis as unknown as { sql?: ReturnType<typeof postgres> };

export const sql =
  globalForDb.sql ??
  postgres(process.env.DATABASE_URL!, {
    prepare: false, // required: pooler transaction mode has no server-side prepared statements
  });

if (process.env.NODE_ENV !== 'production') globalForDb.sql = sql;
```

Query it from a Server Component or Route Handler:

```ts
// app/users/route.ts
import { sql } from '@/lib/db';

export async function GET() {
  const users = await sql`SELECT id, email FROM users ORDER BY created_at DESC LIMIT 10`;
  return Response.json(users);
}
```

### Use Prisma or Drizzle

For a typed schema, migrations, and a query builder, reach for an ORM. Both integrate with the pooled `DATABASE_URL` plus direct `DIRECT_URL` pattern above.

- **Prisma**: point the runtime at the pooled connection string and keep the Prisma CLI on the engine port for migrations, then run `prisma migrate deploy`. See the [Prisma](/docs/products/databases/postgresql/integrations/prisma) guide for the full config and migration flow.
- **Drizzle**: use a pooled client (`prepare: false` on postgres.js) for runtime and the direct URL for `drizzle-kit` migrations. See the [Drizzle](/docs/products/databases/postgresql/integrations/drizzle) guide.

[Set up Prisma against native PostgreSQL](/docs/products/databases/postgresql/integrations/prisma)

### Edge runtime: use the SQL API

If a Route Handler or route segment opts into the Edge runtime, it can't open a TCP socket, so no engine driver will work there:

```ts
export const runtime = 'edge'; // no TCP sockets available
```

From the Edge runtime, you can execute one parameterised SQL statement over HTTPS and get JSON back. `SELECT`, `INSERT`, `UPDATE`, and `DELETE` statements are allowed by default. Use the global `fetch` available in the Edge runtime:

```ts
// app/edge-users/route.ts
export const runtime = 'edge';

export async function GET() {
  const response = await fetch(
    'https://<REGION>.cloud.appwrite.io/v1/postgresql/<DATABASE_ID>/executions',
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-Appwrite-Project': process.env.APPWRITE_PROJECT_ID!,
        'X-Appwrite-Key': process.env.APPWRITE_API_KEY!,
      },
      body: JSON.stringify({
        sql: 'SELECT id, email FROM users WHERE created_at > $1 ORDER BY created_at DESC LIMIT $2',
        bindings: ['2026-05-01T00:00:00Z', 10],
      }),
    },
  );

  const { rows } = await response.json();
  return Response.json(rows);
}
```

The response is `{ rows, rowCount, columns, durationMs, truncated, bytes }`. Bindings are sent separately and never interpolated into the SQL string. Add `APPWRITE_PROJECT_ID` and `APPWRITE_API_KEY` (a key with the `databases.read` scope) to your environment alongside the database URLs.

[Read the SQL API reference](/docs/products/databases/postgresql/quick-start#first-queries)

### Local development

`next dev` runs on the Node.js runtime, so a local server connects to the native PostgreSQL database over TLS exactly like production. Keep `DATABASE_URL` and `DIRECT_URL` in `.env.local`.

For throwaway data in tests or experiments, create a [branch](/docs/products/databases/postgresql/branches), an instant, isolated copy with its own connection string, and point `.env.local` at it. Delete the branch when you're done.

### Deploy

When you deploy, set the same `DATABASE_URL` (pooled) and `DIRECT_URL` (direct) as environment variables on your hosting platform, run your migrations in the build step, and fall back to the SQL API from any Edge Functions.

### Related

- [Prisma](/docs/products/databases/postgresql/integrations/prisma): Datasource config, pooled and direct URLs, and the migration workflow.
- [SQL API](/docs/products/databases/postgresql/quick-start#first-queries): Query over HTTPS from the Edge runtime without a TCP connection.
- [Connection pooler](/docs/products/databases/postgresql/connection-pooling): Pool modes, ports, and read/write splitting for serverless workloads.

---

## Prisma

URL: https://appwrite.io/docs/products/databases/postgresql/integrations/prisma

[Prisma ORM](https://www.prisma.io/) works with Appwrite's native PostgreSQL database as a standard PostgreSQL target. Configure Prisma with the connection string from Appwrite, run Prisma Migrate against the direct database connection, and use Prisma Client from your application code.

**Before you start**

You'll need a native PostgreSQL database in a `ready` state and its credentials. Open the database in the Console and click **Credentials** to copy values from the **Details**, **DSN**, **.env**, **Prisma**, **Drizzle**, or **psql** tabs. You can also call `postgresql.get()` from the Appwrite API to read `hostname`, `connectionUser`, `connectionPassword`, and `connectionString`.

### Initialize Prisma

Install Prisma, Prisma Client, the PostgreSQL driver adapter, and the TypeScript tools used by the examples:

```bash
npm install -D prisma typescript tsx @types/node @types/pg
npm install @prisma/client @prisma/adapter-pg dotenv pg
```

Initialize Prisma for PostgreSQL:

```bash
npx prisma init --datasource-provider postgresql --output ../generated/prisma
```

Prisma 7 generates a `prisma.config.ts` file and a `prisma/schema.prisma` file. Set `"type": "module"` in `package.json` if your project does not already use ECMAScript modules.

### Configure connection strings

Copy the connection string from the Console **Credentials** view. Keep it in environment variables and do not commit it:

```env
DATABASE_URL="postgresql://admin:<password>@db-<hash>.<region>.appwrite.center:5432/<database>?sslmode=require"
DIRECT_URL="postgresql://admin:<password>@db-<hash>.<region>.appwrite.center:5432/<database>?sslmode=require"
SHADOW_DATABASE_URL="postgresql://admin:<password>@db-<hash>.<region>.appwrite.center:5432/<database>?sslmode=require&schema=prisma_shadow"
```

`DATABASE_URL` is the runtime connection used by Prisma Client. `DIRECT_URL` is the direct database connection used by Prisma CLI commands. `SHADOW_DATABASE_URL` is required by `prisma migrate dev`, because Appwrite's `admin` user can run schema changes but cannot create additional PostgreSQL databases for Prisma's default shadow database workflow.

Create the shadow schema once before you run `prisma migrate dev`:

```sql
CREATE SCHEMA IF NOT EXISTS prisma_shadow;
```

Appwrite Cloud uses TLS for PostgreSQL connections, so keep `sslmode=require`. For full certificate verification or mTLS, see [Network security](/docs/products/databases/postgresql/network-security).

### Configure Prisma

In `prisma.config.ts`, read the CLI connection strings from the environment:

```ts
import "dotenv/config";
import { defineConfig } from "prisma/config";

export default defineConfig({
  schema: "prisma/schema.prisma",
  migrations: {
    path: "prisma/migrations",
    seed: "tsx prisma/seed.ts",
  },
  datasource: {
    url: process.env["DIRECT_URL"] ?? process.env["DATABASE_URL"],
    shadowDatabaseUrl: process.env["SHADOW_DATABASE_URL"],
  },
});
```

In `prisma/schema.prisma`, keep the datasource provider in the schema file and generate Prisma Client into the output directory created by `prisma init`:

```prisma
generator client {
  provider = "prisma-client"
  output   = "../generated/prisma"
}

datasource db {
  provider = "postgresql"
}

model User {
  id        String   @id @default(uuid())
  email     String   @unique
  createdAt DateTime @default(now())
}
```

If you are connecting to an existing database, run `npx prisma db pull` after you configure the environment variables to introspect its schema.

### Pool connections from serverless

Prisma Client opens database connections from each running application instance. On serverless platforms, many cold starts can quickly multiply the number of backend PostgreSQL connections. Route runtime traffic through the [connection pooler](/docs/products/databases/postgresql/connection-pooling) by using the pooler port, `6432`, for `DATABASE_URL` while keeping `DIRECT_URL` on the direct PostgreSQL port, `5432`, for migrations and introspection:

```env
### Runtime: pooled connection through the Appwrite connection pooler
DATABASE_URL="postgresql://admin:<password>@db-<hash>.<region>.appwrite.center:6432/<database>?sslmode=require"

### Prisma CLI: direct PostgreSQL connection for migrations and introspection
DIRECT_URL="postgresql://admin:<password>@db-<hash>.<region>.appwrite.center:5432/<database>?sslmode=require"

### Prisma Migrate development shadow schema
SHADOW_DATABASE_URL="postgresql://admin:<password>@db-<hash>.<region>.appwrite.center:5432/<database>?sslmode=require&schema=prisma_shadow"
```

The pooler defaults to **transaction mode**, which gives the highest connection multiplexing. If your application depends on session-level features such as advisory locks, `LISTEN`/`NOTIFY`, temporary tables, or session-scoped prepared statements, switch the pooler to **session mode**. See [Connection pooling](/docs/products/databases/postgresql/connection-pooling#modes) for pool mode trade-offs.

### Run migrations

Generate and apply a migration in development:

```bash
npx prisma migrate dev --name init
```

In CI or production, apply committed migrations without prompting:

```bash
npx prisma migrate deploy
```

Generate Prisma Client after you install dependencies or change `prisma/schema.prisma`:

```bash
npx prisma generate
```

The primary `admin` user owns the database and can run schema changes. Scoped [database roles](/docs/products/databases/postgresql/connections#roles), such as `readonly` and `readwrite` users, are intended for application access and should not run migrations.

### Seed data

With the seed command configured in `prisma.config.ts`, add a seed script:

```ts
import "dotenv/config";
import { PrismaPg } from "@prisma/adapter-pg";
import { PrismaClient } from "../generated/prisma/client";

const adapter = new PrismaPg(
  { connectionString: process.env.DATABASE_URL! },
  { schema: process.env.DATABASE_SCHEMA },
);

const prisma = new PrismaClient({ adapter });

await prisma.user.upsert({
  where: { email: "ada@example.com" },
  update: {},
  create: { email: "ada@example.com" },
});

await prisma.$disconnect();
```

Run the seed command:

```bash
npx prisma db seed
```

`DATABASE_SCHEMA` is optional. Set it only if you connect to a PostgreSQL schema other than `public`.

### Query with Prisma Client

Instantiate Prisma Client with the PostgreSQL driver adapter:

```ts
import "dotenv/config";
import { PrismaPg } from "@prisma/adapter-pg";
import { PrismaClient } from "./generated/prisma/client";

const adapter = new PrismaPg(
  { connectionString: process.env.DATABASE_URL! },
  { schema: process.env.DATABASE_SCHEMA },
);

const prisma = new PrismaClient({ adapter });

const user = await prisma.user.create({
  data: { email: "grace@example.com" },
});

const recent = await prisma.user.findMany({
  orderBy: { createdAt: "desc" },
  take: 10,
});

console.log({ user, recent });

await prisma.$disconnect();
```

On long-running servers, instantiate `PrismaClient` once and reuse it. On serverless platforms, keep a single client per module scope so warm invocations reuse it, and rely on the pooler to absorb cold-start connection churn.

### Use a branch for previews and CI

[Branches](/docs/products/databases/postgresql/branches) are instant, isolated copies of a database with their own hostname and connection string. They are useful for running migrations against throwaway data in a pull-request preview or integration-test job:

1. Create a branch from the API and read its `connectionString`.
2. Export it as `DIRECT_URL`, and use the pooled variant as `DATABASE_URL` if the branch has the pooler enabled.
3. Run `npx prisma migrate deploy` and your test suite against the branch.
4. Delete the branch when the job finishes.

Because a branch starts from a storage snapshot, the schema and data match the source database at branch time, so migrations run against realistic data without touching production.

### Related

- [Connections](/docs/products/databases/postgresql/connections): Retrieve credentials, rotate the password, and create scoped connection users.
- [Connection pooling](/docs/products/databases/postgresql/connection-pooling): Pool modes, ports, and read/write splitting for serverless workloads.
- [Branches](/docs/products/databases/postgresql/branches): Ephemeral database copies for preview environments and CI.
- [Network security](/docs/products/databases/postgresql/network-security): TLS modes, certificate verification, mTLS, and IP allowlists.

---

## Rails

URL: https://appwrite.io/docs/products/databases/postgresql/integrations/rails

A native PostgreSQL database is a standard PostgreSQL engine, so [Ruby on Rails](https://rubyonrails.org/) works against it through ActiveRecord with no Appwrite-specific configuration. Point `config/database.yml` at the connection details from the [Connections](/docs/products/databases/postgresql/connections) page and use ActiveRecord, migrations, and the rest of the Rails toolchain exactly as you would against any PostgreSQL server.

**Before you start**

You'll need a native PostgreSQL database in a `ready` state and its credentials. In the Appwrite Console, open the database and click **Credentials**. Use the **Details** tab for individual values, or copy a ready-made string from the **DSN**, **.env**, **Prisma**, **Drizzle**, or **psql** tab. You can also call `postgresql.get()` from the Appwrite API to read `hostname`, `connectionUser`, `connectionPassword`, and `connectionString`. The primary user is `admin`, and the database name is generated for each database.

### Install the database driver

ActiveRecord talks to PostgreSQL through the `pg` driver gem. Add it to your `Gemfile`:

```ruby
### Gemfile
gem 'pg'
```

The `pg` gem builds against `libpq`, so the PostgreSQL client headers must be available at install time.

Then install:

```bash
bundle install
```

### Set the connection string

Copy the connection string from the Console **Credentials** dialog, or fetch it with the [API](/docs/products/databases/postgresql/connections#credentials). Keep it in an environment variable and do not commit it:

```env
DATABASE_URL="postgresql://admin:<password>@db-<hash>.<region>.appwrite.center:5432/<database>?sslmode=require"
```

The TLS parameter (`sslmode=require`) is already part of the string Appwrite returns. Appwrite Cloud terminates TLS at the edge, so no extra certificate configuration is needed. For full certificate verification (`verify-full`) or mTLS, see the [Network](/docs/products/databases/postgresql/network-security) page.

### Configure database.yml

Rails reads `DATABASE_URL` automatically. The simplest configuration points the `url` at the environment variable and lets ActiveRecord parse the host, port, database, and credentials out of it:

```yaml
### config/database.yml
production:
  adapter: postgresql
  url: <%= ENV["DATABASE_URL"] %>
  pool: <%= ENV.fetch("RAILS_MAX_THREADS", 5) %>
```

If you'd rather set the fields explicitly, the discrete keys map one-to-one to the values from the [Connections](/docs/products/databases/postgresql/connections#credentials) response. Use ERB to read each value from the environment so no secret lands in source control:

```yaml
### config/database.yml
production:
  adapter: postgresql
  host: <%= ENV["DB_HOST"] %>       # db-<hash>.<region>.appwrite.center
  port: <%= ENV.fetch("DB_PORT", 5432) %>
  database: <%= ENV["DB_NAME"] %>   # <database>
  username: <%= ENV.fetch("DB_USER", "admin") %>
  password: <%= ENV["DB_PASSWORD"] %>
  sslmode: require
  pool: <%= ENV.fetch("RAILS_MAX_THREADS", 5) %>
```

When both `DATABASE_URL` and explicit keys are present, Rails merges them. `sslmode` and `pool` can still be set in `database.yml`, so a `url`-based config can carry the TLS parameter in the string and override `pool` in the YAML.

### Size the connection pool

ActiveRecord manages a per-process connection pool. The `pool:` value caps how many backend connections a single Rails process holds, and it defaults to `5`. It must be large enough for every thread that checks out a connection, your Puma worker threads plus any background job threads in the same process.

```yaml
production:
  adapter: postgresql
  url: <%= ENV["DATABASE_URL"] %>
  pool: <%= ENV.fetch("RAILS_MAX_THREADS", 5) %>
```

Tying `pool` to `RAILS_MAX_THREADS` keeps it aligned with Puma's thread count. Each Puma **worker** is a separate process with its own pool, so the backend connection count is roughly `pool × workers × server instances`. Keep that product within your specification's `maxConnections`, see [specifications](/docs/products/databases/postgresql#specifications).

### Run migrations

Generate and apply migrations the usual way:

```bash
bin/rails db:migrate
```

Migrations issue DDL and need a session-level connection, so run them against the direct PostgreSQL port `5432`, not the transaction-mode pooler. Point `DATABASE_URL` (or a separate migration URL) at the direct port when you run `db:migrate`. The primary `admin` user owns the database and can run schema changes. Narrower [database roles](/docs/products/databases/postgresql/connections#roles) should only receive the privileges your application needs.

### Use ActiveRecord

Once `database.yml` is configured, models work with no further setup. Define a migration and model, then query through ActiveRecord:

```ruby
### db/migrate/20240101000000_create_users.rb
class CreateUsers < ActiveRecord::Migration[7.1]
  def change
    create_table :users do |t|
      t.string :email, null: false
      t.timestamps
    end
    add_index :users, :email, unique: true
  end
end
```

Define the matching model:

```ruby
### app/models/user.rb
class User < ApplicationRecord
  validates :email, presence: true, uniqueness: true
end
```

Create and query records as usual:

```ruby
User.create!(email: 'ada@example.com')

recent = User.order(created_at: :desc).limit(10)
```

ActiveRecord opens connections lazily and reuses them from the pool, so a long-running Puma server keeps a small, stable set of backend connections rather than opening one per request.

### Pooling for a long-running server

A Rails app under Puma is a long-running process: it holds an ActiveRecord pool for its lifetime. That pairs naturally with the direct PostgreSQL port `5432`, sized so `pool × workers` stays within your connection budget. This is the recommended setup for a persistent server.

If you instead route through the [connection pooler](/docs/products/databases/postgresql/connection-pooling) to absorb spikes or many app instances, prefer **session mode**, which keeps a backend connection for the whole client session and behaves like a direct connection to ActiveRecord. The pooler defaults to **transaction mode**, which hands out a different backend connection per transaction. ActiveRecord uses server-side prepared statements by default, and those are tied to one backend connection, so on the transaction-mode pooler you must disable them:

```yaml
production:
  adapter: postgresql
  url: <%= ENV["DATABASE_URL"] %>       # pooler host, port 6432
  pool: <%= ENV.fetch("RAILS_MAX_THREADS", 5) %>
  prepared_statements: false
```

See the [pooler](/docs/products/databases/postgresql/connection-pooling#modes) page for the mode trade-offs. Whichever runtime connection you choose, always run `bin/rails db:migrate` against the direct PostgreSQL port.

### Use a branch for previews and CI

[Branches](/docs/products/databases/postgresql/branches) are instant, isolated copies of a database with their own hostname and connection string. They're ideal for running migrations against throwaway data in a pull-request preview or an integration-test job:

1. Create a branch from the API and read its `connectionString`.
2. Export it as `DATABASE_URL` for the job.
3. Run `bin/rails db:migrate` and your test suite against the branch.
4. Delete the branch when the job finishes.

A branch has no pooler and exposes the PostgreSQL port directly, which gives migrations the session-level connection they need. Because a branch starts from a storage snapshot, the schema and data match the source database at branch time, so migrations run against realistic data without touching production.

### Related

- [Connections](/docs/products/databases/postgresql/connections): Retrieve credentials, rotate the password, and create database roles.
- [Connection pooler](/docs/products/databases/postgresql/connection-pooling): Pool modes, ports, and read/write splitting for high-concurrency workloads.
- [Branches](/docs/products/databases/postgresql/branches): Ephemeral database copies for preview environments and CI.
- [Network](/docs/products/databases/postgresql/network-security): TLS modes, certificate verification, mTLS, and IP allowlists.

For ActiveRecord and migration details beyond this guide, see the [Rails configuration guide](https://guides.rubyonrails.org/configuring.html).

---

## Retool

URL: https://appwrite.io/docs/products/databases/postgresql/integrations/retool

An Appwrite [native PostgreSQL database](/docs/products/databases/postgresql) exposes a standard PostgreSQL connection, so [Retool](https://retool.com/) connects to it through the built-in **PostgreSQL** resource. Use the database hostname, generated database name, and credentials from Appwrite, then build queries, tables, and forms in Retool for dashboards and admin panels.

**Before you start**

You'll need a native PostgreSQL database in a `ready` state and permission to create resources in Retool. In the Appwrite Console, open the database and click **Credentials**. Use the **Details** tab for individual values, or copy a ready-made string from the **DSN**, **.env**, **Prisma**, **Drizzle**, or **psql** tab. You can also fetch credentials with the API by calling `postgresql.get()`, which returns `hostname`, `connectionUser`, `connectionPassword`, and `connectionString`. See [Connections](/docs/products/databases/postgresql/connections) for the full flow.

### Choose credentials

The primary user is `admin`, and the database name is generated for each database. The connection string has this form: `postgresql://admin:<password>@db-<hash>.<region>.appwrite.center:5432/<database>`.

Use `admin` for initial setup or schema work because it owns the database and can run DDL. For a Retool app that only needs application-level access, create a narrower PostgreSQL role from the database **Roles** tab in the Console, then grant only the table privileges the app needs from the SQL editor or `psql`. For example, a dashboard role usually needs `SELECT`, while an admin tool may need `SELECT`, `INSERT`, `UPDATE`, and `DELETE` on specific tables.

[Read more about database roles](/docs/products/databases/postgresql/connections#roles)

### Create the PostgreSQL resource

In Retool, go to **Resources**, click **Create new** > **Resource**, search for `PostgreSQL`, and select the PostgreSQL tile. Give the resource a clear **Name** and optional **Description** that identifies the Appwrite database and environment.

In **Resource credentials**, either paste the Appwrite connection string or fill in the fields manually:

| Retool setting | Value |
|----------------|-------|
| Host | `db-<hash>.<region>.appwrite.center` |
| Port | `5432` |
| Database name | `<database>` |
| Connection options | Optional PostgreSQL parameters, such as `application_name=retool` |
| Authentication | **Username and password** |
| Username | `admin` or a narrower PostgreSQL role |
| Password | The password from the Appwrite credentials dialog or API response |

In **Advanced options**, choose an outbound region if your Retool organization uses regional egress and you want the resource traffic to originate near your Appwrite database.

Enable **SSL/TLS**. Appwrite Cloud terminates TLS at the edge, and the certificate is signed by a public CA. If Retool shows **Reject unauthorized**, keep it enabled. If Retool shows **Verification mode**, choose **Full verification**. Leave **CA certificate** empty.

Click **Test connection**. If the test succeeds, click **Create resource**.

### Pick the connection path

For most Retool apps, connect directly to PostgreSQL on port `5432`. Retool uses prepared statements for PostgreSQL queries, and Appwrite's transaction-mode pooler does not preserve session-level state across statements. If you use the Appwrite [connection pooler](/docs/products/databases/postgresql/connection-pooling), configure it in **session** mode for Retool.

[Compare pooler modes](/docs/products/databases/postgresql/connection-pooling#modes)

### Allow Retool Cloud through the network

If you enabled an [IP allowlist](/docs/products/databases/postgresql/network-security#ip-allowlist) for the database, add the Retool Cloud egress addresses for the resource's outbound region. Retool's default outbound region is `us-west-2`, and Retool also documents `eu-central-1` and `ap-southeast-1` egress addresses. Retrieve the current list from [Retool's IP address documentation](https://docs.retool.com/data-sources/reference/ip-allowlist-cloud-orgs) instead of copying static addresses into your runbook.

If you leave the database allowlist open, rely on TLS plus database credentials and least-privilege roles to protect access.

[Configure the IP allowlist](/docs/products/databases/postgresql/network-security#ip-allowlist)

### Build an admin tool

After the resource is connected, create PostgreSQL queries in Retool and wire them to components:

- Use **SQL mode** for read queries that feed a **Table**, chart, or other display component.
- Use **GUI mode** actions such as **Insert a record**, **Update an existing record**, and **Delete a record** for forms and editable tables.
- Bind table edits to the **Save changes** event and refresh the read query after writes complete.
- Show a confirmation modal before delete actions.

Reference component values with Retool's `{{ }}` embedded expressions. Retool converts PostgreSQL queries to prepared statements by default, which separates values from SQL text and helps prevent SQL injection. Keep that protection enabled unless you have a specific, reviewed reason to disable it.

Use database roles as the last line of defense. A dashboard role with only `SELECT` cannot write even if a query is misconfigured. A data-entry role with table-level write privileges can create, update, and delete rows without also owning the schema.

### Use a branch for staging

[Branches](/docs/products/databases/postgresql/branches) are API-created, isolated copies of a PostgreSQL database with their own connection details. Create a branch for staging or preview work, fetch its `connectionString`, and configure a second Retool PostgreSQL resource against that branch. Delete the branch when the staging tool is no longer needed.

### Related

- [PostgreSQL](/docs/products/databases/postgresql): Create and manage a native PostgreSQL database.
- [Connections](/docs/products/databases/postgresql/connections): Retrieve credentials, rotate the primary password, and create database roles.
- [Network security](/docs/products/databases/postgresql/network-security): TLS, IP allowlists, and other network controls.
- [Branches](/docs/products/databases/postgresql/branches): Create isolated database copies for staging and preview environments.

---

## Spring Boot

URL: https://appwrite.io/docs/products/databases/postgresql/integrations/spring-boot

An Appwrite native PostgreSQL database is a standard PostgreSQL engine, so a Spring Boot application connects to it through the PostgreSQL JDBC driver with no Appwrite-specific runtime configuration. Point `spring.datasource` at the JDBC URL from your database credentials, size the built-in HikariCP pool, and use Spring Data JPA, Hibernate, Flyway, or Liquibase as you would with any managed PostgreSQL server.

**Before you start**

You'll need a native PostgreSQL database in a `ready` state and its credentials. In the Appwrite Console, open the database and click **Credentials**. Use the **Details** tab for individual values, or copy a ready-made string from the **DSN**, **.env**, **Prisma**, **Drizzle**, or **psql** tab. You can also fetch credentials with the API by calling `postgresql.get()`, which returns `hostname`, `connectionUser`, `connectionPassword`, and `connectionString`. See [Connections](/docs/products/databases/postgresql/connections) for the full flow.

### Add the dependencies

A Spring Data JPA application needs the JPA starter and the PostgreSQL JDBC driver. HikariCP ships with `spring-boot-starter-data-jpa`, and Spring Boot picks the driver class from the JDBC URL.

```text
org.springframework.boot:spring-boot-starter-data-jpa
org.postgresql:postgresql
```

If you use Flyway or Liquibase, add that migration tool through Spring Initializr or your build file as well. Current Spring Boot projects that use Flyway with PostgreSQL include the Flyway integration and `org.flywaydb:flyway-database-postgresql`.

### Configure the datasource

Build the JDBC URL from the host and database name in the credentials dialog. Appwrite uses port `5432`, the primary user is `admin`, and the database name is generated per database. Read the password from the environment:

```yaml
spring:
    datasource:
        url: jdbc:postgresql://db-<hash>.<region>.appwrite.center:5432/<database>?sslmode=require
        username: admin
        password: ${DB_PASSWORD}
        hikari:
            maximum-pool-size: 10
            minimum-idle: 2
            connection-timeout: 30000
            max-lifetime: 1200000
    jpa:
        hibernate:
            ddl-auto: validate
```

The equivalent `application.properties`:

```ini
spring.datasource.url=jdbc:postgresql://db-<hash>.<region>.appwrite.center:5432/<database>?sslmode=require
spring.datasource.username=admin
spring.datasource.password=${DB_PASSWORD}
spring.datasource.hikari.maximum-pool-size=10
spring.datasource.hikari.minimum-idle=2
spring.datasource.hikari.connection-timeout=30000
spring.datasource.hikari.max-lifetime=1200000
spring.jpa.hibernate.ddl-auto=validate
```

`sslmode=require` enables TLS for the PostgreSQL connection. If your security policy requires host certificate validation, configure pgJDBC with `sslmode=verify-full` and a JVM trust configuration that trusts the certificate chain. The [Network security](/docs/products/databases/postgresql/network-security) page covers TLS, mTLS, and IP allowlists.

### Size the HikariCP pool

A Spring Boot server is long-running, so it holds its HikariCP pool open for the lifetime of the process. Keep `maximum-pool-size` modest. HikariCP guidance is that throughput usually peaks at a small pool, roughly `(CPU cores x 2) + 1` for the database, not hundreds of connections. A pool that exceeds what PostgreSQL can serve only queues work inside the database and adds latency.

Each replica of your application opens its own pool, so multiply `maximum-pool-size` by the number of instances and keep the total under the connection budget of your native PostgreSQL database [specification](/docs/products/databases/postgresql#specifications). Set `max-lifetime` a little below your infrastructure's idle timeout so HikariCP recycles connections before they are closed.

### Map an entity

Define a JPA entity and a Spring Data repository as usual. This example uses a prefixed table name so it is easy to identify in a shared database:

```java
package com.example.demo;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;

@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, unique = true)
    private String email;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }
}
```

Add a repository interface for it:

```java
package com.example.demo;

import java.util.Optional;

import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository<User, Long> {
    Optional<User> findByEmail(String email);
}
```

Inject the repository wherever you need it and call `save`, `findById`, `findByEmail`, and the rest of the generated query methods. HikariCP hands each transaction a pooled connection and returns it on commit.

### Run migrations

Let a migration tool own the schema and set `ddl-auto: validate` so Hibernate checks the mapping against the live tables at startup but never alters them. Add Flyway or Liquibase to your build and Spring Boot runs pending migrations automatically on boot.

For example, a Flyway migration at `src/main/resources/db/migration/V1__init.sql` can create the table used by the entity above:

```sql
CREATE TABLE users (
    id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
    email VARCHAR(255) NOT NULL UNIQUE
);
```

Flyway reads versioned scripts from `src/main/resources/db/migration`. Point Flyway at the direct PostgreSQL port `5432` so migrations run on a session connection with DDL privileges. Setting `spring.flyway.url` gives Flyway its own datasource, independent of the runtime pool:

```ini
spring.flyway.url=jdbc:postgresql://db-<hash>.<region>.appwrite.center:5432/<database>?sslmode=require
spring.flyway.user=admin
spring.flyway.password=${DB_PASSWORD}
```

Liquibase is equivalent: it reads a changelog from `src/main/resources/db/changelog` and accepts its own `spring.liquibase.url`, `spring.liquibase.user`, and `spring.liquibase.password` pointing at the same PostgreSQL port.

The primary `admin` user owns the database and can run schema changes. Scoped [database roles](/docs/products/databases/postgresql/connections#roles) intentionally cannot run DDL, so run migrations as `admin`.

### Pooling and the connection pooler

HikariCP is already a connection pool, so a long-running Spring Boot server should connect to the direct PostgreSQL port `5432` and let HikariCP manage connections. Routing a server's traffic through the [connection pooler](/docs/products/databases/postgresql/connection-pooling) in `transaction` mode stacks HikariCP on top of a transaction pooler and can break server-side prepared statements, which the PostgreSQL JDBC driver uses.

If you put the pooler in front of your server, use `session` mode so PostgreSQL keeps a backend connection for the whole client session and preserves prepared statements. Connect HikariCP on the pooler port `6432` and keep `maximum-pool-size` small. See the [connection pooler](/docs/products/databases/postgresql/connection-pooling#modes) page for the mode trade-offs. Always run Flyway or Liquibase against port `5432` regardless of how runtime traffic connects.

### Use a branch for previews and CI

[Branches](/docs/products/databases/postgresql/branches) are instant, isolated copies of a database with their own hostname and connection string. They are useful for running migrations against throwaway data in a pull-request preview or integration-test job.

1. Create a branch with the API and read its `connectionString`.
2. Convert the connection string to a JDBC URL and inject it into `spring.datasource.url`, or split it into `spring.datasource.url`, `spring.datasource.username`, and `spring.datasource.password`.
3. Boot the application so Flyway or Liquibase applies migrations, then run your test suite against the branch.
4. Delete the branch when the job finishes.

Because a branch starts from a storage snapshot, the schema and data match the source database at branch time, so migrations and `@DataJpaTest` integration tests run against realistic data without touching production.

### Related

- [Connections](/docs/products/databases/postgresql/connections): Retrieve credentials, rotate the password, and create scoped database roles.
- [Connection pooler](/docs/products/databases/postgresql/connection-pooling): Pool modes, ports, and read/write splitting for the connection pooler.
- [Branches](/docs/products/databases/postgresql/branches): Ephemeral database copies for preview environments and CI.
- [Network security](/docs/products/databases/postgresql/network-security): TLS modes, certificate verification, mTLS, and IP allowlists.

[Back to native PostgreSQL overview](/docs/products/databases/postgresql)

---

## Maintenance

URL: https://appwrite.io/docs/products/databases/postgresql/maintenance

Appwrite manages the infrastructure around your database: security patches, engine upgrades, and instance health. This page covers the controls you have over when and how that maintenance happens.

### Maintenance window

Routine maintenance that can briefly affect the database runs inside a weekly window that you choose. Set it under **Settings** > **Maintenance** in the Console by picking a day and start hour (UTC), or through the API:

![Maintenance window grid in database settings](/images/docs/products/databases/postgresql/settings-maintenance.avif)

```server-nodejs
import { Client, Postgresql } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const postgresql = new Postgresql(client);

await postgresql.updateMaintenance({
    databaseId: '<DATABASE_ID>',
    day: 'sun',
    hourUtc: 3,
});
```
```server-deno
import { Client, Postgresql } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const postgresql = new Postgresql(client);

await postgresql.updateMaintenance({
    databaseId: '<DATABASE_ID>',
    day: 'sun',
    hourUtc: 3,
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Postgresql;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$postgresql = new Postgresql($client);

$postgresql->updateMaintenance(
    databaseId: '<DATABASE_ID>',
    day: 'sun',
    hourUtc: 3,
);
```
```server-python
from appwrite.client import Client
from appwrite.services.postgresql import Postgresql

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

postgresql = Postgresql(client)

postgresql.update_maintenance(
    database_id='<DATABASE_ID>',
    day='sun',
    hour_utc=3,
)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

postgresql = Postgresql.new(client)

postgresql.update_maintenance(
    database_id: '<DATABASE_ID>',
    day: 'sun',
    hour_utc: 3,
)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Postgresql postgresql = new Postgresql(client);

await postgresql.UpdateMaintenance(
    databaseId: "<DATABASE_ID>",
    day: "sun",
    hourUtc: 3
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Postgresql postgresql = Postgresql(client);

await postgresql.updateMaintenance(
    databaseId: '<DATABASE_ID>',
    day: 'sun',
    hourUtc: 3,
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Postgresql

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val postgresql = Postgresql(client)

postgresql.updateMaintenance(
    databaseId = "<DATABASE_ID>",
    day = "sun",
    hourUtc = 3,
)
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let postgresql = Postgresql(client)

_ = try await postgresql.updateMaintenance(
    databaseId: "<DATABASE_ID>",
    day: "sun",
    hourUtc: 3
)
```
```server-go
package main

import (
    "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("<YOUR_API_KEY>"),
    )

    service := appwrite.NewPostgresql(client)

    _, err := service.UpdateMaintenance("<DATABASE_ID>", "sun", 3)
    if err != nil {
        panic(err)
    }
}
```
```server-rust
use appwrite::client::Client;
use appwrite::services::postgresql::Postgresql;

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

    let postgresql = Postgresql::new(&client);

    postgresql.update_maintenance("<DATABASE_ID>", "sun", 3).await?;

    Ok(())
}
```
```bash
curl -X PATCH \
  -H "X-Appwrite-Project: <PROJECT_ID>" \
  -H "X-Appwrite-Key: <API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{
      "day": "sun",
      "hourUtc": 3
  }' \
  https://<REGION>.cloud.appwrite.io/v1/postgresql/<DATABASE_ID>/maintenance
```

`day` accepts `sun` through `sat`, and `hourUtc` accepts `0` to `23`.

### Engine version upgrades

You can upgrade the PostgreSQL version online. A second instance is provisioned on the target version, data streams over with logical replication, and traffic cuts over once replication has caught up. Reads continue throughout. At the cutover the old instance is fenced read-only and existing client connections are closed, so writes pause for that moment and the application has to reconnect. The target version must be higher than the current one:

```server-nodejs
import { Client, Postgresql } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const postgresql = new Postgresql(client);

await postgresql.createUpgrade({
    databaseId: '<DATABASE_ID>',
    targetVersion: '18',
});
```
```server-deno
import { Client, Postgresql } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const postgresql = new Postgresql(client);

await postgresql.createUpgrade({
    databaseId: '<DATABASE_ID>',
    targetVersion: '18',
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Postgresql;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$postgresql = new Postgresql($client);

$postgresql->createUpgrade(
    databaseId: '<DATABASE_ID>',
    targetVersion: '18',
);
```
```server-python
from appwrite.client import Client
from appwrite.services.postgresql import Postgresql

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

postgresql = Postgresql(client)

postgresql.create_upgrade(
    database_id='<DATABASE_ID>',
    target_version='18',
)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

postgresql = Postgresql.new(client)

postgresql.create_upgrade(
    database_id: '<DATABASE_ID>',
    target_version: '18',
)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Postgresql postgresql = new Postgresql(client);

await postgresql.CreateUpgrade(
    databaseId: "<DATABASE_ID>",
    targetVersion: "18"
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Postgresql postgresql = Postgresql(client);

await postgresql.createUpgrade(
    databaseId: '<DATABASE_ID>',
    targetVersion: '18',
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Postgresql

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val postgresql = Postgresql(client)

postgresql.createUpgrade(
    databaseId = "<DATABASE_ID>",
    targetVersion = "18",
)
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let postgresql = Postgresql(client)

_ = try await postgresql.createUpgrade(
    databaseId: "<DATABASE_ID>",
    targetVersion: "18"
)
```
```server-go
package main

import (
    "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("<YOUR_API_KEY>"),
    )

    service := appwrite.NewPostgresql(client)

    _, err := service.CreateUpgrade("<DATABASE_ID>", "18")
    if err != nil {
        panic(err)
    }
}
```
```server-rust
use appwrite::client::Client;
use appwrite::services::postgresql::Postgresql;

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

    let postgresql = Postgresql::new(&client);

    postgresql.create_upgrade("<DATABASE_ID>", "18").await?;

    Ok(())
}
```
```bash
curl -X POST \
  -H "X-Appwrite-Project: <PROJECT_ID>" \
  -H "X-Appwrite-Key: <API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{
      "targetVersion": "18"
  }' \
  https://<REGION>.cloud.appwrite.io/v1/postgresql/<DATABASE_ID>/upgrades
```

Before a major version upgrade, check that your installed [extensions](/docs/products/databases/postgresql/extensions) support the target version.

### Pause and resume

![Database settings status card](/images/docs/products/databases/postgresql/settings.avif)

A paused database stops its compute but keeps its storage, configuration, and credentials. Pause a database you are not using to stop paying for compute; resume it when you need it again. In the Console, open **Settings** > **General** and use the **Running** toggle in the Details card, then click **Update** to apply it. From the API, update the status:

```server-nodejs
import { Client, Postgresql } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const postgresql = new Postgresql(client);

await postgresql.update({
    databaseId: '<DATABASE_ID>',
    status: 'paused',
});
```
```server-deno
import { Client, Postgresql } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const postgresql = new Postgresql(client);

await postgresql.update({
    databaseId: '<DATABASE_ID>',
    status: 'paused',
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Postgresql;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$postgresql = new Postgresql($client);

$postgresql->update(
    databaseId: '<DATABASE_ID>',
    status: 'paused',
);
```
```server-python
from appwrite.client import Client
from appwrite.services.postgresql import Postgresql

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

postgresql = Postgresql(client)

postgresql.update(
    database_id='<DATABASE_ID>',
    status='paused',
)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

postgresql = Postgresql.new(client)

postgresql.update(
    database_id: '<DATABASE_ID>',
    status: 'paused',
)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Postgresql postgresql = new Postgresql(client);

await postgresql.Update(
    databaseId: "<DATABASE_ID>",
    status: "paused"
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Postgresql postgresql = Postgresql(client);

await postgresql.update(
    databaseId: '<DATABASE_ID>',
    status: 'paused',
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Postgresql

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val postgresql = Postgresql(client)

postgresql.update(
    databaseId = "<DATABASE_ID>",
    status = "paused",
)
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let postgresql = Postgresql(client)

_ = try await postgresql.update(
    databaseId: "<DATABASE_ID>",
    status: "paused"
)
```
```server-go
package main

import (
    "github.com/appwrite/sdk-for-go/appwrite"
    "github.com/appwrite/sdk-for-go/postgresql"
)

func main() {
    client := appwrite.NewClient(
        appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"),
        appwrite.WithProject("<PROJECT_ID>"),
        appwrite.WithKey("<YOUR_API_KEY>"),
    )

    service := appwrite.NewPostgresql(client)

    _, err := service.Update(
        "<DATABASE_ID>",
        postgresql.WithUpdateStatus("paused"),
    )
    if err != nil {
        panic(err)
    }
}
```
```server-rust
use appwrite::client::Client;
use appwrite::services::postgresql::Postgresql;

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

    let postgresql = Postgresql::new(&client);

    postgresql.update("<DATABASE_ID>", None, Some("paused"), None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None).await?;

    Ok(())
}
```
```bash
curl -X PATCH \
  -H "X-Appwrite-Project: <PROJECT_ID>" \
  -H "X-Appwrite-Key: <API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{
      "status": "paused"
  }' \
  https://<REGION>.cloud.appwrite.io/v1/postgresql/<DATABASE_ID>
```

Set `status` back to `ready` to resume. Both transitions are asynchronous: the request returns immediately and the database moves through `pausing` or `resuming` before settling. A database in the `failed` state can also be recovered by setting its status to `ready`.

### Lifecycle states

| Status | Meaning |
|----------------|------------------------------------------------------------------|
| `provisioning` | Being created |
| `ready` | Online and accepting connections |
| `scaling` | A configuration or specification change is being applied |
| `pausing` | Transitioning to paused |
| `paused` | Compute stopped, storage retained |
| `resuming` | Transitioning back to ready |
| `restoring` | A backup or point-in-time restore is in progress |
| `upgrading` | An engine version upgrade is in progress |
| `migrating` | A migration is in progress |
| `failing-over` | A replica is being promoted to primary |
| `inactive` | Idle and scaled down to zero |
| `deleting` | Deletion is in progress |
| `deleted` | Deletion has finished |
| `failed` | An infrastructure error occurred; the database can be resumed |

### Deleting a database

Delete a database from **Settings** > **General** in the Console, or programmatically. The database moves to `deleting` and the teardown runs in the background. Deleting a database also deletes its backups, so export anything you need first.

---

## Monitoring

URL: https://appwrite.io/docs/products/databases/postgresql/monitoring

Every native database ships with built-in observability: live metrics in the Console, an active-connections inspector, and programmatic health checks. There is nothing to install; metrics collection runs next to the database.

### Monitor tab

![Database monitor tab](/images/docs/products/databases/postgresql/monitor.avif)

Open your database and select the **Monitor** tab. The view is organized into sections:

- **Overview**: key health indicators at a glance, including connection usage against your limit, storage used, cache hit ratio, uptime, and commit/rollback counts
- **Compute**: CPU, memory, queries per second, and disk IOPS over time
- **Connections**: connection counts over time, a breakdown by state and by application, and session signals for idle-in-transaction and long-running queries
- **Storage**: disk usage and growth, largest tables, dead tuples, and sequential scans

Use the date range picker to zoom into an incident window, and **Refresh metrics** to pull the latest samples.

A healthy OLTP database typically shows a cache hit ratio above 99%. A sustained lower ratio means the working set does not fit in memory, which is usually solved by moving up a [specification](/docs/products/databases/postgresql/scaling).

### Active connections

![Active connections tab](/images/docs/products/databases/postgresql/connections.avif)

The **Connections** tab lists live connections from PostgreSQL's `pg_stat_activity`, with the connection state, duration, client, application name, and current query for each.

You can filter by state (**Active**, **Idle**, **Idle in transaction**, **Long-running**), and act on problem connections directly: cancel a running query, terminate a connection, or terminate all idle-in-transaction sessions at once. Idle-in-transaction sessions hold locks and block vacuum, so terminating them is often the fastest way to unblock a stuck workload.

### Database health

Poll the database status for live health information. The response carries a `health` field, one of `healthy`, `degraded`, `unhealthy`, or `unknown`, alongside uptime, connection counts, replica and replication state, and storage volumes. Use it from deploy pipelines to wait for the database, or from your own monitoring:

```server-nodejs
import { Client, Postgresql } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const postgresql = new Postgresql(client);

const status = await postgresql.getStatus({
    databaseId: '<DATABASE_ID>',
});
```
```server-deno
import { Client, Postgresql } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const postgresql = new Postgresql(client);

const status = await postgresql.getStatus({
    databaseId: '<DATABASE_ID>',
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Postgresql;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$postgresql = new Postgresql($client);

$status = $postgresql->getStatus(
    databaseId: '<DATABASE_ID>',
);
```
```server-python
from appwrite.client import Client
from appwrite.services.postgresql import Postgresql

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

postgresql = Postgresql(client)

status = postgresql.get_status(
    database_id='<DATABASE_ID>',
)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

postgresql = Postgresql.new(client)

status = postgresql.get_status(
    database_id: '<DATABASE_ID>',
)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Postgresql postgresql = new Postgresql(client);

var status = await postgresql.GetStatus(
    databaseId: "<DATABASE_ID>"
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Postgresql postgresql = Postgresql(client);

final status = await postgresql.getStatus(
    databaseId: '<DATABASE_ID>',
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Postgresql

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val postgresql = Postgresql(client)

val status = postgresql.getStatus(
    databaseId = "<DATABASE_ID>",
)
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let postgresql = Postgresql(client)

let status = try await postgresql.getStatus(
    databaseId: "<DATABASE_ID>"
)
```
```server-go
package main

import (
    "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("<YOUR_API_KEY>"),
    )

    service := appwrite.NewPostgresql(client)

    result, err := service.GetStatus("<DATABASE_ID>")
    if err != nil {
        panic(err)
    }
    _ = result
}
```
```server-rust
use appwrite::client::Client;
use appwrite::services::postgresql::Postgresql;

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

    let postgresql = Postgresql::new(&client);

    let status = postgresql.get_status("<DATABASE_ID>").await?;

    Ok(())
}
```
```bash
curl -X GET \
  -H "X-Appwrite-Project: <PROJECT_ID>" \
  -H "X-Appwrite-Key: <API_KEY>" \
  https://<REGION>.cloud.appwrite.io/v1/postgresql/<DATABASE_ID>/status
```

For the database lifecycle state (`ready`, `scaling`, `restoring`, and friends), read the `status` field of the database object itself; see [lifecycle states](/docs/products/databases/postgresql/maintenance#states).

### Explain a query

The Console's **SQL editor** tab has an **Explain** button that shows the execution plan for the query in the editor, without leaving the browser. Use it to check whether a slow query uses your indexes. To see measured timings rather than estimates, prefix the query with `EXPLAIN ANALYZE` before pressing **Explain**, which runs the query and reports what it actually did.

---

## Network security

URL: https://appwrite.io/docs/products/databases/postgresql/network-security

Every native database is reachable through a unique public hostname, secured with TLS, and protected by network controls that you configure per database.

### Hostname

Each database gets a stable hostname in the form:

```
db-<hash>.<region>.appwrite.center
```

The hostname does not change for the lifetime of the database, across restarts, resizes, failovers, and version upgrades. You can copy it from the Console credentials dialog or the database response.

### TLS

Connections on Appwrite Cloud are encrypted with TLS, terminated at Appwrite's edge and forwarded to your database over the internal network. The connection string from the credentials dialog carries the right SSL settings for your environment, so drivers need no extra configuration.

### IP allowlist

![Network settings](/images/docs/products/databases/postgresql/settings-network.avif)

By default, any host that has your credentials can reach the database over the public internet. To restrict access to known networks, configure an IP allowlist. Connections from addresses outside the allowlist are dropped at the network layer, before authentication.

In the Console, add entries under **Settings** > **Network**.

From the API, pass CIDR blocks or single addresses:

```server-nodejs
import { Client, Postgresql } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const postgresql = new Postgresql(client);

await postgresql.update({
    databaseId: '<DATABASE_ID>',
    networkIPAllowlist: ['203.0.113.0/24', '198.51.100.7'],
});
```
```server-deno
import { Client, Postgresql } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const postgresql = new Postgresql(client);

await postgresql.update({
    databaseId: '<DATABASE_ID>',
    networkIPAllowlist: ['203.0.113.0/24', '198.51.100.7'],
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Postgresql;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$postgresql = new Postgresql($client);

$postgresql->update(
    databaseId: '<DATABASE_ID>',
    networkIPAllowlist: ['203.0.113.0/24', '198.51.100.7'],
);
```
```server-python
from appwrite.client import Client
from appwrite.services.postgresql import Postgresql

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

postgresql = Postgresql(client)

postgresql.update(
    database_id='<DATABASE_ID>',
    network_ip_allowlist=['203.0.113.0/24', '198.51.100.7'],
)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

postgresql = Postgresql.new(client)

postgresql.update(
    database_id: '<DATABASE_ID>',
    network_ip_allowlist: ['203.0.113.0/24', '198.51.100.7'],
)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Postgresql postgresql = new Postgresql(client);

await postgresql.Update(
    databaseId: "<DATABASE_ID>",
    networkIPAllowlist: new List<string> { "203.0.113.0/24", "198.51.100.7" }
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Postgresql postgresql = Postgresql(client);

await postgresql.update(
    databaseId: '<DATABASE_ID>',
    networkIPAllowlist: ['203.0.113.0/24', '198.51.100.7'],
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Postgresql

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val postgresql = Postgresql(client)

postgresql.update(
    databaseId = "<DATABASE_ID>",
    networkIPAllowlist = listOf("203.0.113.0/24", "198.51.100.7"),
)
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let postgresql = Postgresql(client)

_ = try await postgresql.update(
    databaseId: "<DATABASE_ID>",
    networkIPAllowlist: ["203.0.113.0/24", "198.51.100.7"]
)
```
```server-go
package main

import (
    "github.com/appwrite/sdk-for-go/appwrite"
    "github.com/appwrite/sdk-for-go/postgresql"
)

func main() {
    client := appwrite.NewClient(
        appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"),
        appwrite.WithProject("<PROJECT_ID>"),
        appwrite.WithKey("<YOUR_API_KEY>"),
    )

    service := appwrite.NewPostgresql(client)

    _, err := service.Update(
        "<DATABASE_ID>",
        postgresql.WithUpdateNetworkIPAllowlist([]string{"203.0.113.0/24", "198.51.100.7"}),
    )
    if err != nil {
        panic(err)
    }
}
```
```server-rust
use appwrite::client::Client;
use appwrite::services::postgresql::Postgresql;

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

    let postgresql = Postgresql::new(&client);

    postgresql.update("<DATABASE_ID>", None, None, None, None, None, None, Some(vec!["203.0.113.0/24".into(), "198.51.100.7".into()]), None, None, None, None, None, None, None, None, None, None, None, None, None).await?;

    Ok(())
}
```
```bash
curl -X PATCH \
  -H "X-Appwrite-Project: <PROJECT_ID>" \
  -H "X-Appwrite-Key: <API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{
      "networkIPAllowlist": [
          "203.0.113.0/24",
          "198.51.100.7"
      ]
  }' \
  https://<REGION>.cloud.appwrite.io/v1/postgresql/<DATABASE_ID>
```

Rules:

- Entries are IPv4 or IPv6 addresses or CIDR blocks, up to 100 entries per database.
- An empty allowlist means the database accepts connections from any address.
- The allowlist applies to the database port, the pooler port, and the SQL API. Appwrite's own traffic for backups, monitoring, and replication is unaffected.

**Don't lock yourself out**

If you connect from networks with changing addresses (home ISPs, mobile networks, serverless platforms without static egress), an allowlist can block you. Add your serverless provider's egress ranges, or leave the allowlist empty and rely on strong credentials and rotation.

### Idle timeout

`networkIdleTimeoutSeconds` sets how long a session may sit idle inside an open transaction before PostgreSQL ends it, 900 seconds by default and adjustable between 60 and 86400. It targets the case where an application opens a transaction and then stalls, holding locks and blocking other writers. A connection that is simply idle between queries is not affected.

This is separate from `idleTimeoutMinutes`, which controls how long the whole database sits without any connections before it scales down to zero. That one accepts 5 to 60 minutes and defaults to 15.

```server-nodejs
import { Client, Postgresql } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const postgresql = new Postgresql(client);

await postgresql.update({
    databaseId: '<DATABASE_ID>',
    networkIdleTimeoutSeconds: 900,
});
```
```server-deno
import { Client, Postgresql } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const postgresql = new Postgresql(client);

await postgresql.update({
    databaseId: '<DATABASE_ID>',
    networkIdleTimeoutSeconds: 900,
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Postgresql;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$postgresql = new Postgresql($client);

$postgresql->update(
    databaseId: '<DATABASE_ID>',
    networkIdleTimeoutSeconds: 900,
);
```
```server-python
from appwrite.client import Client
from appwrite.services.postgresql import Postgresql

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

postgresql = Postgresql(client)

postgresql.update(
    database_id='<DATABASE_ID>',
    network_idle_timeout_seconds=900,
)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

postgresql = Postgresql.new(client)

postgresql.update(
    database_id: '<DATABASE_ID>',
    network_idle_timeout_seconds: 900,
)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Postgresql postgresql = new Postgresql(client);

await postgresql.Update(
    databaseId: "<DATABASE_ID>",
    networkIdleTimeoutSeconds: 900
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Postgresql postgresql = Postgresql(client);

await postgresql.update(
    databaseId: '<DATABASE_ID>',
    networkIdleTimeoutSeconds: 900,
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Postgresql

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val postgresql = Postgresql(client)

postgresql.update(
    databaseId = "<DATABASE_ID>",
    networkIdleTimeoutSeconds = 900,
)
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let postgresql = Postgresql(client)

_ = try await postgresql.update(
    databaseId: "<DATABASE_ID>",
    networkIdleTimeoutSeconds: 900
)
```
```server-go
package main

import (
    "github.com/appwrite/sdk-for-go/appwrite"
    "github.com/appwrite/sdk-for-go/postgresql"
)

func main() {
    client := appwrite.NewClient(
        appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"),
        appwrite.WithProject("<PROJECT_ID>"),
        appwrite.WithKey("<YOUR_API_KEY>"),
    )

    service := appwrite.NewPostgresql(client)

    _, err := service.Update(
        "<DATABASE_ID>",
        postgresql.WithUpdateNetworkIdleTimeoutSeconds(900),
    )
    if err != nil {
        panic(err)
    }
}
```
```server-rust
use appwrite::client::Client;
use appwrite::services::postgresql::Postgresql;

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

    let postgresql = Postgresql::new(&client);

    postgresql.update("<DATABASE_ID>", None, None, None, None, None, Some(900), None, None, None, None, None, None, None, None, None, None, None, None, None, None).await?;

    Ok(())
}
```
```bash
curl -X PATCH \
  -H "X-Appwrite-Project: <PROJECT_ID>" \
  -H "X-Appwrite-Key: <API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{
      "networkIdleTimeoutSeconds": 900
  }' \
  https://<REGION>.cloud.appwrite.io/v1/postgresql/<DATABASE_ID>
```

The same setting is available in the Console under **Settings** > **Network**.

### Locking down access

For a production database:

1. Set an IP allowlist covering only your application's egress addresses.
2. Rotate the [primary password](/docs/products/databases/postgresql/connections#rotate) on a schedule, and after anyone with access leaves your team.
3. Watch the [Connections tab](/docs/products/databases/postgresql/monitoring#connections) for unexpected clients.

---

## Quick start

URL: https://appwrite.io/docs/products/databases/postgresql/quick-start

You can create a PostgreSQL database and run your first query in a few minutes.

### Create a database

**Available regions**

Native databases are rolling out region by region, starting with Frankfurt (`fra`) and New York (`nyc`), and more regions are on the way. A database takes the region of the project that owns it and there is no per-database region selector, so create your project in a supported region before you start.

1. In your project, go to **Databases**.
2. Click **Create database**.
3. Under **Choose database type**, select **PostgreSQL** from the **Native databases** group.
4. Give your database a name, and optionally a custom database ID. This step appears once you pick a type.

![Create database type selection](/images/docs/products/databases/postgresql/create-database-type.avif)

5. Under **Specifications**, select your preferred tier.
6. Optionally configure **Read replicas** and **Point-in-time recovery (PITR)**. You can change both later.

![Database specifications](/images/docs/products/databases/postgresql/create-database-specs.avif)

7. Review the database summary and click **Create database**.

Your database starts provisioning and becomes ready shortly after. You land in the SQL editor once it is created.

### Run your first queries

![SQL editor](/images/docs/products/databases/postgresql/sql-editor-run.avif)

The **SQL editor** tab gives you an editor with formatting, query explain, and a results panel, without leaving the browser.

The editor runs one statement per query, so run these three in turn rather than pasting them together.

Create a table:

```sql
CREATE TABLE books (
    id SERIAL PRIMARY KEY,
    title TEXT NOT NULL,
    author TEXT NOT NULL
);
```

Insert a row:

```sql
INSERT INTO books (title, author)
VALUES ('The Hitchhiker''s Guide to the Galaxy', 'Douglas Adams');
```

Read it back:

```sql
SELECT * FROM books;
```

### Get your connection details

To connect from outside the Console:

1. Open your database and click **Credentials**.
2. The **Details** tab shows the host, port, username, password, and database name.
3. The **DSN**, **.env**, **Prisma**, **Drizzle**, and **psql** tabs have ready-made snippets for each workflow.

### Connect with psql

Copy the command from the **psql** tab of the credentials dialog, or build it from the details. The port shown there is `6432`, the connection pooler, which is what you want for everyday use:

```bash
PGSSLMODE=require psql -h <host> -p 6432 -U admin -d <database>
```

Port `5432` reaches the database directly, bypassing the pooler. Use it for migrations and other work that needs one session for the whole run.

Enter the password when prompted. You can run the same queries from your terminal that you ran in the SQL editor, or connect from your application with any PostgreSQL driver. See [Connections](/docs/products/databases/postgresql/connections) for driver examples.

### Next steps

- [Connections](/docs/products/databases/postgresql/connections): Connect from your application with any PostgreSQL driver or ORM.
- [Extensions](/docs/products/databases/postgresql/extensions): Install PostgreSQL extensions like PostGIS and pgvector.
- [Backups](/docs/products/databases/postgresql/backups): Configure backup policies and point-in-time recovery.
- [Monitoring](/docs/products/databases/postgresql/monitoring): Watch compute, connections, storage, and workload metrics live.

---

## Scaling

URL: https://appwrite.io/docs/products/databases/postgresql/scaling

Native databases scale in two dimensions: the compute specification (CPU, memory, and connection limit) and storage. Both can change after creation, without dump-and-restore migrations.

### List available specifications

Each database runs against a specification that defines its CPU, memory, included storage, and maximum connections. Listing specifications returns the whole catalogue, with an `enabled` flag showing which ones your plan allows:

```server-nodejs
import { Client, Postgresql } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const postgresql = new Postgresql(client);

const specifications = await postgresql.listSpecifications({

});
```
```server-deno
import { Client, Postgresql } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const postgresql = new Postgresql(client);

const specifications = await postgresql.listSpecifications({

});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Postgresql;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$postgresql = new Postgresql($client);

$specifications = $postgresql->listSpecifications(

);
```
```server-python
from appwrite.client import Client
from appwrite.services.postgresql import Postgresql

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

postgresql = Postgresql(client)

specifications = postgresql.list_specifications(

)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

postgresql = Postgresql.new(client)

specifications = postgresql.list_specifications(

)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Postgresql postgresql = new Postgresql(client);

var specifications = await postgresql.ListSpecifications(

);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Postgresql postgresql = Postgresql(client);

final specifications = await postgresql.listSpecifications(

);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Postgresql

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val postgresql = Postgresql(client)

val specifications = postgresql.listSpecifications(

)
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let postgresql = Postgresql(client)

let specifications = try await postgresql.listSpecifications(

)
```
```server-go
package main

import (
    "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("<YOUR_API_KEY>"),
    )

    service := appwrite.NewPostgresql(client)

    result, err := service.ListSpecifications()
    if err != nil {
        panic(err)
    }
    _ = result
}
```
```server-rust
use appwrite::client::Client;
use appwrite::services::postgresql::Postgresql;

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

    let postgresql = Postgresql::new(&client);

    let specifications = postgresql.list_specifications().await?;

    Ok(())
}
```
```bash
curl -X GET \
  -H "X-Appwrite-Project: <PROJECT_ID>" \
  -H "X-Appwrite-Key: <API_KEY>" \
  https://<REGION>.cloud.appwrite.io/v1/postgresql/specifications
```

### Change the compute specification

![Compute tier settings](/images/docs/products/databases/postgresql/settings-compute-tier.avif)

To resize in the Console, open your database, go to **Settings** > **Compute**, and pick a larger specification. The Console offers upgrades only.

From the API, pass the new specification ID:

```server-nodejs
import { Client, Postgresql } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const postgresql = new Postgresql(client);

await postgresql.update({
    databaseId: '<DATABASE_ID>',
    specification: '<SPECIFICATION>',
});
```
```server-deno
import { Client, Postgresql } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const postgresql = new Postgresql(client);

await postgresql.update({
    databaseId: '<DATABASE_ID>',
    specification: '<SPECIFICATION>',
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Postgresql;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$postgresql = new Postgresql($client);

$postgresql->update(
    databaseId: '<DATABASE_ID>',
    specification: '<SPECIFICATION>',
);
```
```server-python
from appwrite.client import Client
from appwrite.services.postgresql import Postgresql

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

postgresql = Postgresql(client)

postgresql.update(
    database_id='<DATABASE_ID>',
    specification='<SPECIFICATION>',
)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

postgresql = Postgresql.new(client)

postgresql.update(
    database_id: '<DATABASE_ID>',
    specification: '<SPECIFICATION>',
)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Postgresql postgresql = new Postgresql(client);

await postgresql.Update(
    databaseId: "<DATABASE_ID>",
    specification: "<SPECIFICATION>"
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Postgresql postgresql = Postgresql(client);

await postgresql.update(
    databaseId: '<DATABASE_ID>',
    specification: '<SPECIFICATION>',
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Postgresql

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val postgresql = Postgresql(client)

postgresql.update(
    databaseId = "<DATABASE_ID>",
    specification = "<SPECIFICATION>",
)
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let postgresql = Postgresql(client)

_ = try await postgresql.update(
    databaseId: "<DATABASE_ID>",
    specification: "<SPECIFICATION>"
)
```
```server-go
package main

import (
    "github.com/appwrite/sdk-for-go/appwrite"
    "github.com/appwrite/sdk-for-go/postgresql"
)

func main() {
    client := appwrite.NewClient(
        appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"),
        appwrite.WithProject("<PROJECT_ID>"),
        appwrite.WithKey("<YOUR_API_KEY>"),
    )

    service := appwrite.NewPostgresql(client)

    _, err := service.Update(
        "<DATABASE_ID>",
        postgresql.WithUpdateSpecification("<SPECIFICATION>"),
    )
    if err != nil {
        panic(err)
    }
}
```
```server-rust
use appwrite::client::Client;
use appwrite::services::postgresql::Postgresql;

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

    let postgresql = Postgresql::new(&client);

    postgresql.update("<DATABASE_ID>", None, None, Some("<SPECIFICATION>"), None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None, None).await?;

    Ok(())
}
```
```bash
curl -X PATCH \
  -H "X-Appwrite-Project: <PROJECT_ID>" \
  -H "X-Appwrite-Key: <API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{
      "specification": "<SPECIFICATION>"
  }' \
  https://<REGION>.cloud.appwrite.io/v1/postgresql/<DATABASE_ID>
```

Resizes apply with zero downtime and the database keeps its storage in place. When the new size fits on the current node, the resize applies to the running instance without a restart. When it needs a larger node, the database fails over onto an instance of the target size and writes pause briefly at the cutover. The database status shows `scaling` while the resize is in progress.

You cannot move a database to a smaller specification. Every specification in the catalogue includes more storage than the one below it, and a resize is rejected when the target includes less storage than the database currently has.

### Storage

Each specification includes a storage allowance, and storage beyond the allowance is billed per GB. Storage only grows; you cannot shrink a database's storage after it has expanded. To reclaim a smaller footprint, create a second database and restore a [backup](/docs/products/databases/postgresql/backups) into it.

### Storage autoscaling

With storage autoscaling enabled, Appwrite grows the storage automatically when usage crosses a threshold. Configure it under **Settings** > **Storage** in the Console, or through the API:

![Storage settings with the autoscaling toggle](/images/docs/products/databases/postgresql/settings-storage.avif)

```server-nodejs
import { Client, Postgresql } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const postgresql = new Postgresql(client);

await postgresql.update({
    databaseId: '<DATABASE_ID>',
    storageAutoscaling: true,
    storageAutoscalingThresholdPercent: 85,
    storageAutoscalingMaxGb: 500,
});
```
```server-deno
import { Client, Postgresql } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const postgresql = new Postgresql(client);

await postgresql.update({
    databaseId: '<DATABASE_ID>',
    storageAutoscaling: true,
    storageAutoscalingThresholdPercent: 85,
    storageAutoscalingMaxGb: 500,
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Postgresql;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$postgresql = new Postgresql($client);

$postgresql->update(
    databaseId: '<DATABASE_ID>',
    storageAutoscaling: true,
    storageAutoscalingThresholdPercent: 85,
    storageAutoscalingMaxGb: 500,
);
```
```server-python
from appwrite.client import Client
from appwrite.services.postgresql import Postgresql

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

postgresql = Postgresql(client)

postgresql.update(
    database_id='<DATABASE_ID>',
    storage_autoscaling=True,
    storage_autoscaling_threshold_percent=85,
    storage_autoscaling_max_gb=500,
)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

postgresql = Postgresql.new(client)

postgresql.update(
    database_id: '<DATABASE_ID>',
    storage_autoscaling: true,
    storage_autoscaling_threshold_percent: 85,
    storage_autoscaling_max_gb: 500,
)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Postgresql postgresql = new Postgresql(client);

await postgresql.Update(
    databaseId: "<DATABASE_ID>",
    storageAutoscaling: true,
    storageAutoscalingThresholdPercent: 85,
    storageAutoscalingMaxGb: 500
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Postgresql postgresql = Postgresql(client);

await postgresql.update(
    databaseId: '<DATABASE_ID>',
    storageAutoscaling: true,
    storageAutoscalingThresholdPercent: 85,
    storageAutoscalingMaxGb: 500,
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Postgresql

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val postgresql = Postgresql(client)

postgresql.update(
    databaseId = "<DATABASE_ID>",
    storageAutoscaling = true,
    storageAutoscalingThresholdPercent = 85,
    storageAutoscalingMaxGb = 500,
)
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let postgresql = Postgresql(client)

_ = try await postgresql.update(
    databaseId: "<DATABASE_ID>",
    storageAutoscaling: true,
    storageAutoscalingThresholdPercent: 85,
    storageAutoscalingMaxGb: 500
)
```
```server-go
package main

import (
    "github.com/appwrite/sdk-for-go/appwrite"
    "github.com/appwrite/sdk-for-go/postgresql"
)

func main() {
    client := appwrite.NewClient(
        appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"),
        appwrite.WithProject("<PROJECT_ID>"),
        appwrite.WithKey("<YOUR_API_KEY>"),
    )

    service := appwrite.NewPostgresql(client)

    _, err := service.Update(
        "<DATABASE_ID>",
        postgresql.WithUpdateStorageAutoscaling(true),
        postgresql.WithUpdateStorageAutoscalingThresholdPercent(85),
        postgresql.WithUpdateStorageAutoscalingMaxGb(500),
    )
    if err != nil {
        panic(err)
    }
}
```
```server-rust
use appwrite::client::Client;
use appwrite::services::postgresql::Postgresql;

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

    let postgresql = Postgresql::new(&client);

    postgresql.update("<DATABASE_ID>", None, None, None, None, None, None, None, None, None, None, Some(true), Some(85), Some(500), None, None, None, None, None, None, None).await?;

    Ok(())
}
```
```bash
curl -X PATCH \
  -H "X-Appwrite-Project: <PROJECT_ID>" \
  -H "X-Appwrite-Key: <API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{
      "storageAutoscaling": true,
      "storageAutoscalingThresholdPercent": 85,
      "storageAutoscalingMaxGb": 500
  }' \
  https://<REGION>.cloud.appwrite.io/v1/postgresql/<DATABASE_ID>
```

| Parameter | Range | Description |
|---------------------------------------|--------------------|-----------------------------------------------------------|
| `storageAutoscaling` | boolean | Enable automatic storage growth |
| `storageAutoscalingThresholdPercent` | 50 - 95 | Usage percentage that triggers an expansion (default 85) |
| `storageAutoscalingMaxGb` | 0 - 16384 | Upper bound for automatic growth, in GB. `0` means no cap |

A cap has to be larger than the database's current storage, otherwise the request is rejected. Once storage reaches the cap, autoscaling stops and the disk can fill, so leave the cap off unless you have a reason to bound growth.

### Picking a specification

Guidelines for choosing a starting tier:

- **Connections**: count the maximum concurrent connections your application opens, including all replicas of your app server. If it exceeds the specification's connection cap, either move up a tier or put the [connection pooler](/docs/products/databases/postgresql/connection-pooling) in front.
- **Memory**: PostgreSQL performs best when the working set fits in memory. Watch the cache hit ratio in the [Monitor tab](/docs/products/databases/postgresql/monitoring); a sustained ratio below ~99% for an OLTP workload is a sign to add memory.
- **CPU**: sustained CPU above 70-80% at normal load leaves no headroom for spikes, migrations, or backups.

Start small and resize up when the metrics say so; resizes are online, so there is no penalty for growing later.

---

## TablesDB

URL: https://appwrite.io/docs/products/databases/tablesdb

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.

**Looking for file storage?**

Databases store data, if you need to store files like images, PDFs or videos, use [Appwrite Storage](/docs/products/storage).

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.

[Quick start](/docs/products/databases/tablesdb/quick-start)

### Concepts

- [Databases](/docs/products/databases/tablesdb/databases): Create and manage databases, the largest organizational unit.
- [Tables](/docs/products/databases/tablesdb/tables): Define typed columns and indexes for structured data.
- [Rows](/docs/products/databases/tablesdb/rows): Create, read, update, and delete rows.
- [Permissions](/docs/products/databases/tablesdb/permissions): Control access at the table and row level.
- [Relationships](/docs/products/databases/tablesdb/relationships): Model one-to-one, one-to-many, and many-to-many relations.
- [Queries](/docs/products/databases/tablesdb/queries): Filter rows with comparison, string, and logical operators.
- [Order](/docs/products/databases/tablesdb/order): Sort results by one or more columns.
- [Backups](/docs/products/databases/tablesdb/backups): Schedule backup policies and restore from any backup.

### Guides

- [Pagination](/docs/products/databases/tablesdb/pagination): Page through results with offset or cursor pagination.
- [Transactions](/docs/products/databases/tablesdb/transactions): Stage operations and commit or roll them back atomically.
- [Type generation](/docs/products/databases/tablesdb/type-generation): Generate types for your tables in your language of choice.
- [Offline sync](/docs/products/databases/tablesdb/offline): Keep data available offline and sync when back online.
- [Bulk operations](/docs/products/databases/tablesdb/bulk-operations): Create, update, upsert, or delete many rows in one request.
- [CSV imports](/docs/products/databases/tablesdb/csv-imports): Import rows from CSV files.
- [CSV exports](/docs/products/databases/tablesdb/csv-exports): Export tables to CSV files.

---

## AI suggestions

URL: https://appwrite.io/docs/products/databases/tablesdb/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.

#### 1. 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.

#### 2. Enable AI suggestions

In the table creation dialog, enable **AI suggestions**.

![Enable AI suggestions](/images/docs/databases/ai-suggestions-enable.avif)

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.

#### 3. Review suggestions

AI suggestions will generate recommended columns and indexes for your table.

![Review AI suggestions](/images/docs/databases/ai-suggestions-review.avif)

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.

#### 4. 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.

**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.

---

## Atomic numeric operations

URL: https://appwrite.io/docs/products/databases/tablesdb/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.

These operations work with `integer`, `bigint`, and `float` columns. Use `bigint` columns when your counters or accumulators may exceed the 32-bit integer range.

### 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

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

Use the `incrementRowColumn` and `decrementRowColumn` methods to perform atomic numeric operations. The server will apply these changes atomically under concurrency control.

#### Increment a field

```client-web
import { Client, TablesDB } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

const tablesDB = new TablesDB(client);

const result = await tablesDB.incrementRowColumn({
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    rowId: '<ROW_ID>',
    column: 'likes', // column
    value: 1 // value
});
```
```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 row = await tablesDB.incrementRowColumn(
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    rowId: '<ROW_ID>',
    column: 'likes',
    value: 1
);
```
```client-apple
import Appwrite
import AppwriteModels

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

let tablesDB = TablesDB(client)

let row = try await tablesDB.incrementRowColumn(
    databaseId: "<DATABASE_ID>",
    tableId: "<TABLE_ID>",
    rowId: "<ROW_ID>",
    column: "likes",
    value: 1
)
```
```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 row = tablesDB.incrementRowColumn(
    databaseId = "<DATABASE_ID>",
    tableId = "<TABLE_ID>",
    rowId = "<ROW_ID>",
    column = "likes",
    value = 1
)
```
```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('<API_KEY>'); // Your secret API key

const tablesDB = new sdk.TablesDB(client);

const result = await tablesDB.incrementRowColumn({
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    rowId: '<ROW_ID>',
    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://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint
client.set_project('<PROJECT_ID>') # Your project ID
client.set_key('<API_KEY>') # Your secret API key

tablesDB = TablesDB(client)

result = tablesDB.increment_row_column(
    database_id = '<DATABASE_ID>',
    table_id = '<TABLE_ID>',
    row_id = '<ROW_ID>',
    column = 'likes', # column
    value = 1 # value
)
```
```rust
use appwrite::Client;
use appwrite::services::tables_db::TablesDB;

let client = Client::new()
    .set_endpoint("https://<REGION>.cloud.appwrite.io/v1")
    .set_project("<PROJECT_ID>")
    .set_key("<API_KEY>");

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

let result = tables_db.increment_row_column(
    "<DATABASE_ID>",
    "<TABLE_ID>",
    "<ROW_ID>",
    "likes",       // column
    Some(1.0),     // value
    None,          // max
    None,          // transaction_id
).await?;
```
```graphql
mutation {
    databasesIncrementRowColumn(
        databaseId: "<DATABASE_ID>",
        tableId: "<TABLE_ID>",
        rowId: "<ROW_ID>",
        column: "likes",
        value: 1
    ) {
        _id
        _tableId
        _databaseId
        _createdAt
        _updatedAt
        _permissions
        data
    }
}
```

#### Decrement a field

Use the `decrementRowColumn` method to decrease numeric fields:

```client-web
import { Client, TablesDB } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

const tablesDB = new TablesDB(client);

const result = await tablesDB.decrementRowColumn({
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    rowId: '<ROW_ID>',
    column: 'credits', // column
    value: 5 // value
});
```
```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 row = await tablesDB.decrementRowColumn(
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    rowId: '<ROW_ID>',
    column: 'credits',
    value: 5
);
```
```client-apple
import Appwrite
import AppwriteModels

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

let tablesDB = TablesDB(client)

let row = try await tablesDB.decrementRowColumn(
    databaseId: "<DATABASE_ID>",
    tableId: "<TABLE_ID>",
    rowId: "<ROW_ID>",
    column: "credits",
    value: 5
)
```
```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 row = tablesDB.decrementRowColumn(
    databaseId = "<DATABASE_ID>",
    tableId = "<TABLE_ID>",
    rowId = "<ROW_ID>",
    column = "credits",
    value = 5
)
```
```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('<API_KEY>'); // Your secret API key

const tablesDB = new sdk.TablesDB(client);

const result = await tablesDB.decrementRowColumn({
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    rowId: '<ROW_ID>',
    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://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint
client.set_project('<PROJECT_ID>') # Your project ID
client.set_key('<API_KEY>') # Your secret API key

tablesDB = TablesDB(client)

result = tablesDB.decrement_row_column(
    database_id = '<DATABASE_ID>',
    table_id = '<TABLE_ID>',
    row_id = '<ROW_ID>',
    column = 'credits', # column
    value = 5 # value
)
```
```rust
use appwrite::Client;
use appwrite::services::tables_db::TablesDB;

let client = Client::new()
    .set_endpoint("https://<REGION>.cloud.appwrite.io/v1")
    .set_project("<PROJECT_ID>")
    .set_key("<API_KEY>");

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

let result = tables_db.decrement_row_column(
    "<DATABASE_ID>",
    "<TABLE_ID>",
    "<ROW_ID>",
    "credits",     // column
    Some(5.0),     // value
    None,          // min
    None,          // transaction_id
).await?;
```
```graphql
mutation {
    databasesDecrementRowColumn(
        databaseId: "<DATABASE_ID>",
        tableId: "<TABLE_ID>",
        rowId: "<ROW_ID>",
        column: "credits",
        value: 5
    ) {
        _id
        _tableId
        _databaseId
        _createdAt
        _updatedAt
        _permissions
        data
    }
}
```

### 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

```client-web
// Increment with maximum constraint
const result = await tablesDB.incrementRowColumn({
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    rowId: '<ROW_ID>',
    column: 'credits', // column
    value: 100, // value
    max: 1000 // max (optional)
});

// Decrement with minimum constraint
const result2 = await tablesDB.decrementRowColumn({
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    rowId: '<ROW_ID>',
    column: 'credits', // column
    value: 50, // value
    min: 0 // min (optional)
});
```
```client-flutter
// Increment with maximum constraint
final row = await tablesDB.incrementRowColumn(
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    rowId: '<ROW_ID>',
    column: 'credits',
    value: 100,
    max: 1000
);

// Decrement with minimum constraint
final row2 = await tablesDB.decrementRowColumn(
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    rowId: '<ROW_ID>',
    column: 'credits',
    value: 50,
    min: 0
);
```
```client-apple
// Increment with maximum constraint
let row = try await tablesDB.incrementRowColumn(
    databaseId: "<DATABASE_ID>",
    tableId: "<TABLE_ID>",
    rowId: "<ROW_ID>",
    column: "credits",
    value: 100,
    max: 1000
)

// Decrement with minimum constraint
let row2 = try await tablesDB.decrementRowColumn(
    databaseId: "<DATABASE_ID>",
    tableId: "<TABLE_ID>",
    rowId: "<ROW_ID>",
    column: "credits",
    value: 50,
    min: 0
)
```
```client-android-kotlin
// Increment with maximum constraint
val row = tablesDB.incrementRowColumn(
    databaseId = "<DATABASE_ID>",
    tableId = "<TABLE_ID>",
    rowId = "<ROW_ID>",
    column = "credits",
    value = 100,
    max = 1000
)

// Decrement with minimum constraint
val row2 = tablesDB.decrementRowColumn(
    databaseId = "<DATABASE_ID>",
    tableId = "<TABLE_ID>",
    rowId = "<ROW_ID>",
    column = "credits",
    value = 50,
    min = 0
)
```
```server-nodejs
// Increment with maximum constraint
const result = await tablesDB.incrementRowColumn({
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    rowId: '<ROW_ID>',
    column: 'credits', // column
    value: 100, // value
    max: 1000 // max (optional)
});

// Decrement with minimum constraint
const result2 = await tablesDB.decrementRowColumn({
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    rowId: '<ROW_ID>',
    column: 'credits', // column
    value: 50, // value
    min: 0 // min (optional)
});
```
```server-python
### Increment with maximum constraint
result = tablesDB.increment_row_column(
    database_id = '<DATABASE_ID>',
    table_id = '<TABLE_ID>',
    row_id = '<ROW_ID>',
    column = 'credits', # column
    value = 100, # value
    max = 1000 # max (optional)
)

### Decrement with minimum constraint
result2 = tablesDB.decrement_row_column(
    database_id = '<DATABASE_ID>',
    table_id = '<TABLE_ID>',
    row_id = '<ROW_ID>',
    column = 'credits', # column
    value = 50, # value
    min = 0 # min (optional)
)
```
```rust
// Increment with maximum constraint
let result = tables_db.increment_row_column(
    "<DATABASE_ID>",
    "<TABLE_ID>",
    "<ROW_ID>",
    "credits",       // column
    Some(100.0),     // value
    Some(1000.0),    // max (optional)
    None,            // transaction_id
).await?;

// Decrement with minimum constraint
let result2 = tables_db.decrement_row_column(
    "<DATABASE_ID>",
    "<TABLE_ID>",
    "<ROW_ID>",
    "credits",       // column
    Some(50.0),      // value
    Some(0.0),       // min (optional)
    None,            // transaction_id
).await?;
```

### Follow best practices

#### 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

For complex updates that include both atomic operations and regular field changes, you'll need to use separate API calls:

```client-web
// First, increment the likes atomically
const likeResult = await tablesDB.incrementRowColumn({
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    rowId: '<ROW_ID>',
    column: 'likes',
    value: 1
});

// Then, update other fields
const updateResult = await tablesDB.updateRow({
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    rowId: '<ROW_ID>',
    data: {
        lastLikedBy: userId,
        lastLikedAt: new Date().toISOString()
    }
});
```
```client-flutter
// First, increment the likes atomically
final likeResult = await tablesDB.incrementRowColumn(
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    rowId: '<ROW_ID>',
    column: 'likes',
    value: 1
);

// Then, update other fields
final updateResult = await tablesDB.updateRow(
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    rowId: '<ROW_ID>',
    data: {
        'lastLikedBy': userId,
        'lastLikedAt': DateTime.now().toIso8601String()
    }
);
```
```client-apple
// First, increment the likes atomically
let likeResult = try await tablesDB.incrementRowColumn(
    databaseId: "<DATABASE_ID>",
    tableId: "<TABLE_ID>",
    rowId: "<ROW_ID>",
    column: "likes",
    value: 1
)

// Then, update other fields
let updateResult = try await tablesDB.updateRow(
    databaseId: "<DATABASE_ID>",
    tableId: "<TABLE_ID>",
    rowId: "<ROW_ID>",
    data: [
        "lastLikedBy": userId,
        "lastLikedAt": ISO8601DateFormatter().string(from: Date())
    ]
)
```
```client-android-kotlin
// First, increment the likes atomically
val likeResult = tablesDB.incrementRowColumn(
    databaseId = "<DATABASE_ID>",
    tableId = "<TABLE_ID>",
    rowId = "<ROW_ID>",
    column = "likes",
    value = 1
)

// Then, update other fields
val updateResult = tablesDB.updateRow(
    databaseId = "<DATABASE_ID>",
    tableId = "<TABLE_ID>",
    rowId = "<ROW_ID>",
    data = mapOf(
        "lastLikedBy" to userId,
        "lastLikedAt" to Instant.now().toString()
    )
)
```
```server-nodejs
// First, increment the likes atomically
const likeResult = await tablesDB.incrementRowColumn({
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    rowId: '<ROW_ID>',
    column: 'likes',
    value: 1
});

// Then, update other fields
const updateResult = await tablesDB.updateRow({
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    rowId: '<ROW_ID>',
    data: {
        lastLikedBy: userId,
        lastLikedAt: new Date().toISOString()
    }
});
```
```server-python
### First, increment the likes atomically
like_result = tablesDB.increment_row_column(
    database_id = '<DATABASE_ID>',
    table_id = '<TABLE_ID>',
    row_id = '<ROW_ID>',
    column = 'likes', # column
    value = 1 # value
)

### Then, update other fields
update_result = tablesDB.update_row(
    database_id = '<DATABASE_ID>',
    table_id = '<TABLE_ID>',
    row_id = '<ROW_ID>',
    data = {
        'lastLikedBy': user_id,
        'lastLikedAt': datetime.now().isoformat()
    }
)
```
```rust
use serde_json::json;

// First, increment the likes atomically
let like_result = tables_db.increment_row_column(
    "<DATABASE_ID>",
    "<TABLE_ID>",
    "<ROW_ID>",
    "likes",       // column
    Some(1.0),     // value
    None,          // max
    None,          // transaction_id
).await?;

// Then, update other fields
let update_result = tables_db.update_row(
    "<DATABASE_ID>",
    "<TABLE_ID>",
    "<ROW_ID>",
    Some(json!({
        "lastLikedBy": user_id,
        "lastLikedAt": chrono::Utc::now().to_rfc3339()
    })),
    None,          // permissions
    None,          // transaction_id
).await?;
```

### Use transactions

Atomic numeric operations accept `transactionId`. When provided, increments/decrements are staged and applied on commit.

```client-web
await tablesDB.incrementRowColumn({
  databaseId: '<DATABASE_ID>',
  tableId: '<TABLE_ID>',
  rowId: '<ROW_ID>',
  column: 'likes',
  value: 1,
  transactionId: '<TRANSACTION_ID>'
});
```
```client-flutter
await tablesDB.incrementRowColumn(
  databaseId: '<DATABASE_ID>',
  tableId: '<TABLE_ID>',
  rowId: '<ROW_ID>',
  column: 'likes',
  value: 1,
  transactionId: '<TRANSACTION_ID>'
);
```
```client-apple
try await tablesDB.incrementRowColumn(
  databaseId: "<DATABASE_ID>",
  tableId: "<TABLE_ID>",
  rowId: "<ROW_ID>",
  column: "likes",
  value: 1,
  transactionId: "<TRANSACTION_ID>"
)
```
```client-android-kotlin
tablesDB.incrementRowColumn(
  databaseId = "<DATABASE_ID>",
  tableId = "<TABLE_ID>",
  rowId = "<ROW_ID>",
  column = "likes",
  value = 1,
  transactionId = "<TRANSACTION_ID>"
)
```
```client-android-java
tablesDB.incrementRowColumn(
  "<DATABASE_ID>",
  "<TABLE_ID>",
  "<ROW_ID>",
  "likes",
  1,
  "<TRANSACTION_ID>",
  new CoroutineCallback<>((result, error) -> {
    if (error != null) {
      error.printStackTrace();
      return null;
    }
    System.out.println(result);
    return null;
  })
);
```
```client-react-native
await tablesDB.incrementRowColumn({
  databaseId: '<DATABASE_ID>',
  tableId: '<TABLE_ID>',
  rowId: '<ROW_ID>',
  column: 'likes',
  value: 1,
  transactionId: '<TRANSACTION_ID>'
});
```
```server-nodejs
await tablesDB.incrementRowColumn({
  databaseId: '<DATABASE_ID>',
  tableId: '<TABLE_ID>',
  rowId: '<ROW_ID>',
  column: 'likes',
  value: 1,
  transactionId: '<TRANSACTION_ID>'
});
```
```server-deno
await tablesDB.incrementRowColumn({
  databaseId: '<DATABASE_ID>',
  tableId: '<TABLE_ID>',
  rowId: '<ROW_ID>',
  column: 'likes',
  value: 1,
  transactionId: '<TRANSACTION_ID>'
});
```
```server-python
tablesDB.increment_row_column(
  database_id = '<DATABASE_ID>',
  table_id = '<TABLE_ID>',
  row_id = '<ROW_ID>',
  column = 'likes',
  value = 1,
  transaction_id = '<TRANSACTION_ID>'
)
```
```rust
tables_db.increment_row_column(
    "<DATABASE_ID>",
    "<TABLE_ID>",
    "<ROW_ID>",
    "likes",
    Some(1.0),
    None,                          // max
    Some("<TRANSACTION_ID>"),      // transaction_id
).await?;
```
```server-php
$tablesDB->incrementRowColumn(
  databaseId: '<DATABASE_ID>',
  tableId: '<TABLE_ID>',
  rowId: '<ROW_ID>',
  column: 'likes',
  value: 1,
  transactionId: '<TRANSACTION_ID>'
);
```
```server-ruby
tablesDB.increment_row_column(
  database_id: '<DATABASE_ID>',
  table_id: '<TABLE_ID>',
  row_id: '<ROW_ID>',
  column: 'likes',
  value: 1,
  transaction_id: '<TRANSACTION_ID>'
)
```
```server-dotnet
await tablesDB.IncrementRowColumn(
  databaseId: "<DATABASE_ID>",
  tableId: "<TABLE_ID>",
  rowId: "<ROW_ID>",
  column: "likes",
  value: 1,
  transactionId: "<TRANSACTION_ID>"
);
```
```server-dart
await tablesDB.incrementRowColumn(
  databaseId: '<DATABASE_ID>',
  tableId: '<TABLE_ID>',
  rowId: '<ROW_ID>',
  column: 'likes',
  value: 1,
  transactionId: '<TRANSACTION_ID>'
);
```
```server-swift
try await tablesDB.incrementRowColumn(
  databaseId: "<DATABASE_ID>",
  tableId: "<TABLE_ID>",
  rowId: "<ROW_ID>",
  column: "likes",
  value: 1,
  transactionId: "<TRANSACTION_ID>"
)
```
```server-kotlin
tablesDB.incrementRowColumn(
  databaseId = "<DATABASE_ID>",
  tableId = "<TABLE_ID>",
  rowId = "<ROW_ID>",
  column = "likes",
  value = 1,
  transactionId = "<TRANSACTION_ID>"
)
```
```server-java
tablesDB.incrementRowColumn(
  "<DATABASE_ID>",
  "<TABLE_ID>",
  "<ROW_ID>",
  "likes",
  1,
  "<TRANSACTION_ID>",
  new CoroutineCallback<>((result, error) -> {
    if (error != null) {
      error.printStackTrace();
      return null;
    }
    System.out.println(result);
    return null;
  })
);
```

#### 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

URL: https://appwrite.io/docs/products/databases/tablesdb/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.

**Backups are available on Appwrite Cloud for all Pro and Enterprise customers.**

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 allow you to automate your backup process. The Enterprise plan allows for more customization and offers options like how often backups should occur, how long they should be retained, and when they should run.

#### Creating a backup policy

To automate your database backups, you need to create backup policies that run at scheduled intervals.

![Create databases screen](/images/docs/databases/databases.avif)

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\
&nbsp;
    * On a **Pro** plan, you get access to a Daily backup policy

    ![Pro plan policy](/images/docs/databases/pro-policy.avif)

    * On the **Enterprise** plan, you get access to more & custom policies\
    &nbsp;
        * Select a pre-defined policy

            ![Backup policies](/images/docs/databases/scale-policies.avif)
        * Or create a custom policy and adjust the settings as you like

            ![Custom backup policies](/images/docs/databases/scale-custom-policies.avif)

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

You can always create an on-demand backup whenever necessary.

![Manual backup](/images/docs/databases/manual-backup.avif)

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

To restore a database, you must have a backup of the database you want to restore.

![Restore databases](/images/docs/databases/restore.avif)

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

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

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

URL: https://appwrite.io/docs/products/databases/tablesdb/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.

**Important notes**

- Bulk operations trigger Functions, Webhooks, or Realtime events for each row manipulated. Rather than a single event for the entire bulk operation, each row generates a separate event on the existing realtime channels for its operation type.
- Tables that contain relationship columns are not supported via bulk operations. Use individual row operations for tables with relationships.

### 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.

### Plan limits

Bulk operations have different limits based on your Appwrite plan:

| Plan | Rows per request |
|------|----------------------|
| Free | 100 |
| Pro | 1,000 |

These limits apply to all bulk operations including create, update, upsert, and delete operations. If you need higher limits than what the Pro plan offers, you can [inquire](/contact-us/enterprise) about a custom plan.

### Create rows

You can create multiple rows in a single request using the `createRows` method.

**Custom timestamps**

When creating, updating or upserting in bulk, you can set `$createdAt` and `$updatedAt` for each row in the payload. Values must be ISO 8601 date-time strings. If omitted, Appwrite sets them automatically.

```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 result = await tablesDB.createRows({
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    rows: [
        {
            $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://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<API_KEY>')

tablesDB = TablesDB(client)

result = tablesDB.create_rows(
    database_id = '<DATABASE_ID>',
    table_id = '<TABLE_ID>',
    rows = [
        {
            '$id': appwrite.ID.unique(),
            'name': 'Row 1'
        },
        {
            '$id': appwrite.ID.unique(),
            'name': 'Row 2'
        }
    ]
)
```

```rust
use appwrite::Client;
use appwrite::services::tables_db::TablesDB;
use appwrite::id::ID;
use serde_json::json;

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

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

    let result = tables_db.create_rows(
        "<DATABASE_ID>",
        "<TABLE_ID>",
        vec![
            json!({
                "$id": ID::unique(),
                "name": "Row 1"
            }),
            json!({
                "$id": ID::unique(),
                "name": "Row 2"
            }),
        ],
        None,
    ).await?;

    Ok(())
}
```

### Update rows

**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)

You can update multiple rows in a single request using the `updateRows` method.

```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 result = await tablesDB.updateRows({
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    data: {
        status: 'published'
    },
    queries: [
        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://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<API_KEY>')

tablesDB = TablesDB(client)

result = tablesDB.update_rows(
    database_id = '<DATABASE_ID>',
    table_id = '<TABLE_ID>',
    data = {
        'status': 'published'
    },
    queries = [
        Query.equal('status', 'draft')
    ]
)
```

```rust
use appwrite::Client;
use appwrite::services::tables_db::TablesDB;
use appwrite::query::Query;
use serde_json::json;

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

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

    let result = tables_db.update_rows(
        "<DATABASE_ID>",
        "<TABLE_ID>",
        Some(json!({
            "status": "published"
        })),
        Some(vec![
            Query::equal("status", "draft").to_string(),
        ]),
        None,
    ).await?;

    Ok(())
}
```

### Upsert rows

**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)

You can upsert multiple rows in a single request using the `upsertRows(` method.

```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 result = await tablesDB.upsertRows({
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    rows: [
        {
            $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://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<API_KEY>')

tablesDB = TablesDB(client)

result = tablesDB.upsert_rows(
    database_id = '<DATABASE_ID>',
    table_id = '<TABLE_ID>',
    rows = [
        {
            '$id': appwrite.ID.unique(),
            'name': 'Row 1'
        },
        {
            '$id': 'row-id-2',  # Existing row ID
            'name': 'New Row 2'
        }
    ]
)
```

```rust
use appwrite::Client;
use appwrite::services::tables_db::TablesDB;
use appwrite::id::ID;
use serde_json::json;

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

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

    let result = tables_db.upsert_rows(
        "<DATABASE_ID>",
        "<TABLE_ID>",
        vec![
            json!({
                "$id": ID::unique(),
                "name": "New Row 1"
            }),
            json!({
                "$id": "row-id-2", // Existing row ID
                "name": "New Row 2"
            }),
        ],
        None,
    ).await?;

    Ok(())
}
```

### Delete rows

**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)

You can delete multiple rows in a single request using the `deleteRows` method.

```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 result = await tablesDB.deleteRows({
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    queries: [
        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://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<API_KEY>')

tablesDB = TablesDB(client)

result = tablesDB.delete_rows(
    database_id = '<DATABASE_ID>',
    table_id = '<TABLE_ID>',
    queries = [
        Query.equal('status', 'archived')
    ]
)
```

```rust
use appwrite::Client;
use appwrite::services::tables_db::TablesDB;
use appwrite::query::Query;

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

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

    let result = tables_db.delete_rows(
        "<DATABASE_ID>",
        "<TABLE_ID>",
        Some(vec![
            Query::equal("status", "archived").to_string(),
        ]),
        None,
    ).await?;

    Ok(())
}
```

**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).

### 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).

```server-nodejs
await tablesDB.createRows({
  databaseId: '<DATABASE_ID>',
  tableId: '<TABLE_ID>',
  rows: [
    { $id: sdk.ID.unique(), name: 'One' },
    { $id: sdk.ID.unique(), name: 'Two' }
  ],
  transactionId: '<TRANSACTION_ID>'
});
```
```server-python
tablesDB.create_rows(
  database_id = '<DATABASE_ID>',
  table_id = '<TABLE_ID>',
  rows = [
    { '$id': appwrite.ID.unique(), 'name': 'One' },
    { '$id': appwrite.ID.unique(), 'name': 'Two' }
  ],
  transaction_id = '<TRANSACTION_ID>'
)
```
```server-deno
await tablesDB.createRows({
  databaseId: '<DATABASE_ID>',
  tableId: '<TABLE_ID>',
  rows: [
    { $id: sdk.ID.unique(), name: 'One' },
    { $id: sdk.ID.unique(), name: 'Two' }
  ],
  transactionId: '<TRANSACTION_ID>'
});
```
```server-php
$tablesDB->createRows(
  databaseId: '<DATABASE_ID>',
  tableId: '<TABLE_ID>',
  rows: [
    [ '$id' => ID::unique(), 'name' => 'One' ],
    [ '$id' => ID::unique(), 'name' => 'Two' ]
  ],
  transactionId: '<TRANSACTION_ID>'
);
```
```server-ruby
tablesDB.create_rows(
  database_id: '<DATABASE_ID>',
  table_id: '<TABLE_ID>',
  rows: [
    { '$id' => ID.unique(), 'name' => 'One' },
    { '$id' => ID.unique(), 'name' => 'Two' }
  ],
  transaction_id: '<TRANSACTION_ID>'
)
```
```server-dotnet
await tablesDB.CreateRows(
  databaseId: "<DATABASE_ID>",
  tableId: "<TABLE_ID>",
  rows: new List<Dictionary<string, object>>
  {
    new Dictionary<string, object>
    {
      ["$id"] = ID.Unique(),
      ["name"] = "One"
    },
    new Dictionary<string, object>
    {
      ["$id"] = ID.Unique(),
      ["name"] = "Two"
    }
  },
  transactionId: "<TRANSACTION_ID>"
);
```
```server-dart
await tablesDB.createRows(
  databaseId: '<DATABASE_ID>',
  tableId: '<TABLE_ID>',
  rows: [
    { '\$id': ID.unique(), 'name': 'One' },
    { '\$id': ID.unique(), 'name': 'Two' }
  ],
  transactionId: '<TRANSACTION_ID>'
);
```
```server-swift
try await tablesDB.createRows(
  databaseId: "<DATABASE_ID>",
  tableId: "<TABLE_ID>",
  rows: [
    ["$id": ID.unique(), "name": "One"],
    ["$id": ID.unique(), "name": "Two"]
  ],
  transactionId: "<TRANSACTION_ID>"
)
```
```server-kotlin
tablesDB.createRows(
  databaseId = "<DATABASE_ID>",
  tableId = "<TABLE_ID>",
  rows = listOf(
    mapOf("\$id" to ID.unique(), "name" to "One"),
    mapOf("\$id" to ID.unique(), "name" to "Two")
  ),
  transactionId = "<TRANSACTION_ID>"
)
```
```server-java
tablesDB.createRows(
  "<DATABASE_ID>",
  "<TABLE_ID>",
  Arrays.asList(
    Map.of(
      "$id", ID.unique(),
      "name", "One"
    ),
    Map.of(
      "$id", ID.unique(),
      "name", "Two"
    )
  ),
  "<TRANSACTION_ID>",
  new CoroutineCallback<>((result, error) -> {
    if (error != null) {
      error.printStackTrace();
      return null;
    }
    System.out.println(result);
    return null;
  })
);
```
```rust
let result = tables_db.create_rows(
    "<DATABASE_ID>",
    "<TABLE_ID>",
    vec![
        json!({
            "$id": ID::unique(),
            "name": "One"
        }),
        json!({
            "$id": ID::unique(),
            "name": "Two"
        }),
    ],
    Some("<TRANSACTION_ID>"),
).await?;
```

---

## CSV exports

URL: https://appwrite.io/docs/products/databases/tablesdb/csv-exports

Appwrite's CSV Export feature allows you to export rows from a table to a CSV file. This is especially useful for reporting, sharing data with non-technical team members, creating custom backups, or handing off datasets to analytics tools.

This feature is available in both Appwrite Cloud and the self-hosted version.

### Export configuration

Before exporting, you can configure several options to control the output format and contents. These settings ensure you get exactly the data you need in the format your tools expect.

#### Apply filters

You can apply filters to your table from the table view and use those to export only the rows you need. This is especially useful when you want to export a subset of your data for a specific use case.

#### Select columns

You can choose which columns to include in your export. By default, all columns are exported, but selecting specific columns creates cleaner, more focused datasets that are easier to work with in spreadsheets and analytics tools.

**Good to know**

System columns like `$id`, `$createdAt`, and `$updatedAt` are automatically included in the export.

#### Custom delimiter

You can set a custom delimiter for your CSV file. While commas are standard, you can use tabs, semicolons, or other delimiters based on your requirements or the tools you're importing into.

Common delimiters:
- **Comma (`,`)**: Standard format, compatible with most tools
- **Tab**: Useful when your data contains many commas
- **Semicolon (`;`)**: Common in European Excel versions
- **Pipe (`|`)**: Useful when your data contains many semicolons

#### Header row

You can choose whether to include a header row with column names. Headers make it easier to understand the data in spreadsheets, but some import tools work better without them.

### Relationship handling

When exporting data that includes [relationships](/docs/products/databases/relationships) to other tables, Appwrite exports only the related row IDs by default. This keeps your CSV files clean and prevents deeply nested data structures that can break spreadsheet tools.

For example, if you have a `posts` table with a relationship to an `authors` table, the export will include the author ID rather than all author details.

An example of exported data with relationships:

```text
$id,title,author_id,published
post-1,Getting started with Appwrite,user-123,true
post-2,Advanced queries,user-456,true
post-3,CSV exports guide,user-123,false
```

### Timestamps

The `$createdAt` and `$updatedAt` columns are exported in ISO 8601 format, making them compatible with most spreadsheet and database tools.

### Permissions

If row-level security is enabled for your table, the `$permissions` column will be included in the export. Permission strings are formatted as comma-separated role definitions within quotes.

```text
$id,title,$permissions
post-1,Public post,"read(""any""),update(""user:user-123"")"
post-2,Team post,"read(""team:team-456""),update(""team:team-456"")"
```

The roles used are API strings that can be found in the [permissions documentation](/docs/products/databases/permissions).

### Background processing

Large exports run as background tasks to avoid blocking your workflow. When an export completes, you'll receive an email with a short-lived download link to retrieve your CSV file.

This means you can start an export, close the Console, and return later to download your file. The Console displays a floating progress bar while the export is active.

### Export from the Console

To export rows using the Appwrite Console:

1. Go to your project and navigate to **Databases**
2. Select your target database and navigate to your target table
3. Click the download icon in the table action area
4. Configure your export options:
   - Choose which columns to include (optional)
   - Set your preferred delimiter
   - Choose whether to include headers
5. Click **Export**

![CSV export screen](/images/docs/databases/csv-export.avif)

The export will begin processing in the background. You'll see a progress indicator and receive an email when the export is ready to download.

### Use cases

CSV exports are useful for many common workflows:

- **Reporting**: Generate reports for stakeholders who need data in spreadsheet format
- **Data sharing**: Share clean datasets with non-technical team members
- **Analytics hand-off**: Provide datasets to analysts using BI tools
- **Compliance exports**: Create audit trails and compliance records
- **Custom backups**: Archive specific data subsets for record-keeping
- **Migration preparation**: Extract data for migration to other systems

### Best practices

To get the most out of CSV exports:

1. **Use filters to filter data**: Export only the rows you need to reduce file size and processing time
2. **Select specific columns**: Choose relevant columns to create cleaner, more focused datasets
4. **Choose appropriate delimiters**: Use tabs or semicolons if your data contains many commas
5. **Consider header requirements**: Include headers for human readability, exclude them for automated imports

### Additional resources

- [CSV Imports](/docs/products/databases/csv-imports) - Import data from CSV files
- [Relationships](/docs/products/databases/relationships) - Define connections between tables
- [Database Permissions](/docs/products/databases/permissions) - Configure row-level security
- [Database Backups](/docs/products/databases/backups) - Automated backup policies

---

## CSV imports

URL: https://appwrite.io/docs/products/databases/tablesdb/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

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.

**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.

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

Different column types handle empty values differently:

- **Text columns (varchar, text, mediumtext, longtext):** Empty values are interpreted as empty strings. To add null values to a text 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

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
```

**Timestamps format**

`$createdAt` and `$updatedAt` must be valid ISO 8601 date-time strings, for example: `2025-08-10T12:34:56.000Z`.

### 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

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

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

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 **text type** (varchar, text, mediumtext, or longtext), you can enclose the value in double quotes (`"comma,used,here"`).
- If your column type is a **text type array** (varchar, text, mediumtext, or longtext), 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 text column (varchar, text, mediumtext, or longtext), you need to escape them by doubling them (`""`), otherwise it will be treated as an array.

### Import rows from the Console

To import rows using the Appwrite Console:

1. Go to your project -> Databases
2. Navigate to your target Table
3. Click the upload icon in the table action area
4. Upload a new CSV file or choose an existing file from your Storage bucket

![CSV import screen](/images/docs/databases/csv-import.avif)

CSV imports run as background tasks. The Console displays a floating progress bar while the import is active.

### Additional resources

- [Appwrite CLI](/docs/command-line)
- [Database Permissions](/docs/products/databases/permissions)
- [TablesDB API Reference](/docs/references/cloud/client-web/tablesDB)

---

## Databases

URL: https://appwrite.io/docs/products/databases/tablesdb/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.

A database runs on shared or dedicated infrastructure; see [shared and dedicated databases](/docs/products/databases#shared-and-dedicated).

### 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
You can programmatically create databases using a [Server SDK](/docs/sdks#server). Appwrite [Server SDKs](/docs/sdks#server) require an [API key](/docs/partners/project/api-keys).

```server-nodejs
const sdk = require('node-appwrite');

// Init SDK
const client = new sdk.Client();

const tablesDB = new sdk.TablesDB(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 = tablesDB.create({
    databaseId: '<DATABASE_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 tablesDB = new sdk.TablesDB(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 = tablesDB.create({
    databaseId: '<DATABASE_ID>',
    name: '[NAME]'
});

promise.then(function (response) {
    console.log(response);
}, function (error) {
    console.log(error);
});
```
```php
<?php

use Appwrite\Client;
use Appwrite\Services\TablesDB;

$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
;

$tablesDB = new TablesDB($client);

$result = $tablesDB->create('<DATABASE_ID>', '<NAME>');
```
```python
from appwrite.client import Client
from appwrite.services.tables_db import TablesDB

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
)

tablesDB = TablesDB(client)

result = tablesDB.create(database_id='<DATABASE_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

tablesDB = TablesDB.new(client)

response = tablesDB.create(database_id: '<DATABASE_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 tablesDB = new TablesDB(client);

Database result = await tablesDB.Create(
    databaseId: "<DATABASE_ID>",
    name: "<NAME>");
```
```dart
import 'package:dart_appwrite/dart_appwrite.dart';

void main() { // Init SDK
  Client client = Client();
  TablesDB tablesDB = TablesDB(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 = tablesDB.create(
    databaseId: '<DATABASE_ID>',
    name: '<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://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<PROJECT_ID>") // Your project ID
    .setKey("919c2d18fb5d4...a2ae413da83346ad2") // Your secret API key

val tablesDB = TablesDB(client)

val response = tablesDB.create(
    databaseId = "<DATABASE_ID>",
    name = "<NAME>",
)
```
```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") // Your API Endpoint
    .setProject("<PROJECT_ID>") // Your project ID
    .setKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key

TablesDB tablesDB = new TablesDB(client);

tablesDB.create(
    "<DATABASE_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 tablesDB = TablesDB(client)

let response = try await tablesDB.create(
    databaseId: "<DATABASE_ID>",
    name: "<NAME>"
)
```

```server-rust
use appwrite::Client;
use appwrite::services::databases::Databases;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let 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

    let databases = Databases::new(&client);

    let response = databases.create(
        "<DATABASE_ID>",
        "<NAME>",
        None, // enabled (optional)
    ).await?;

    println!("{:?}", response);
    Ok(())
}
```

---

## Geo queries

URL: https://appwrite.io/docs/products/databases/tablesdb/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.

**Also called spatial queries**

In database terminology, these could also be known as **spatial queries**.

Coordinates are specified as `[longitude, latitude]` arrays. Distance measurements can be specified in meters or degrees.

### 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

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

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

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).

**Self-hosted installations**

If you're self-hosting Appwrite, you will have to choose **MariaDB** as your preferred database to use geo-queries. Self-hosted Appwrite instances using **MongoDB** do not currently support spatial columns or indexes.

---

## Atomic numeric operations

URL: https://appwrite.io/docs/products/databases/tablesdb/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

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

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

Use the `incrementDocumentColumn` and `decrementDocumentColumn` methods to perform atomic numeric operations. The server will apply these changes atomically under concurrency control.

#### Increment a field

```client-web
import { Client, Databases } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

const databases = new Databases(client);

const result = await databases.incrementDocumentColumn(
    '<DATABASE_ID>',
    '<COLLECTION_ID>',
    '<DOCUMENT_ID>',
    'likes', // column
    1 // value
);
```
```client-flutter
import 'package:appwrite/appwrite.dart';

final client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

final databases = Databases(client);

final document = await databases.incrementDocumentColumn(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>',
    column: 'likes',
    value: 1
);
```
```client-apple
import Appwrite
import AppwriteModels

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

let databases = Databases(client)

let document = try await databases.incrementDocumentColumn(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    documentId: "<DOCUMENT_ID>",
    column: "likes",
    value: 1
)
```
```client-android-kotlin
import io.appwrite.Client
import io.appwrite.services.Databases

val client = Client(applicationContext)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

val databases = Databases(client)

val document = databases.incrementDocumentColumn(
    databaseId = "<DATABASE_ID>",
    collectionId = "<COLLECTION_ID>",
    documentId = "<DOCUMENT_ID>",
    column = "likes",
    value = 1
)
```
```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('<API_KEY>'); // Your secret API key

const databases = new sdk.Databases(client);

const result = await databases.incrementDocumentColumn(
    '<DATABASE_ID>',
    '<COLLECTION_ID>',
    '<DOCUMENT_ID>',
    'likes', // column
    1 // value
);
```
```server-python
from appwrite.client import Client
from appwrite.services.databases import Databases

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('<API_KEY>') # Your secret API key

databases = Databases(client)

result = databases.increment_document_column(
    database_id = '<DATABASE_ID>',
    collection_id = '<COLLECTION_ID>',
    document_id = '<DOCUMENT_ID>',
    column = 'likes', # column
    value = 1 # value
)
```
```server-rust
use appwrite::Client;
use appwrite::services::databases::Databases;

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

    let databases = Databases::new(&client);

    let result = databases.increment_document_attribute(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
        "<DOCUMENT_ID>",
        "likes",       // attribute
        Some(1.0),     // value
        None,          // max (optional)
        None,          // transaction_id (optional)
    ).await?;

    println!("{:?}", result);
    Ok(())
}
```
```graphql
mutation {
    databasesIncrementDocumentColumn(
        databaseId: "<DATABASE_ID>",
        collectionId: "<COLLECTION_ID>",
        documentId: "<DOCUMENT_ID>",
        column: "likes",
        value: 1
    ) {
        _id
        _collectionId
        _databaseId
        _createdAt
        _updatedAt
        _permissions
        data
    }
}
```

#### Decrement a field

Use the `decrementDocumentColumn` method to decrease numeric fields:

```client-web
import { Client, Databases } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<YOUR_PROJECT_ID>'); // Your project ID

const databases = new Databases(client);

const result = await databases.decrementDocumentColumn(
    '<DATABASE_ID>',
    '<COLLECTION_ID>',
    '<DOCUMENT_ID>',
    'credits', // column
    5 // value
);
```
```client-flutter
import 'package:appwrite/appwrite.dart';

final client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

final databases = Databases(client);

final document = await databases.decrementDocumentColumn(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>',
    column: 'credits',
    value: 5
);
```
```client-apple
import Appwrite
import AppwriteModels

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

let databases = Databases(client)

let document = try await databases.decrementDocumentColumn(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    documentId: "<DOCUMENT_ID>",
    column: "credits",
    value: 5
)
```
```client-android-kotlin
import io.appwrite.Client
import io.appwrite.services.Databases

val client = Client(applicationContext)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

val databases = Databases(client)

val document = databases.decrementDocumentColumn(
    databaseId = "<DATABASE_ID>",
    collectionId = "<COLLECTION_ID>",
    documentId = "<DOCUMENT_ID>",
    column = "credits",
    value = 5
)
```
```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('<API_KEY>'); // Your secret API key

const databases = new sdk.Databases(client);

const result = await databases.decrementDocumentColumn(
    '<DATABASE_ID>',
    '<COLLECTION_ID>',
    '<DOCUMENT_ID>',
    'credits', // column
    5 // value
);
```
```server-python
from appwrite.client import Client
from appwrite.services.databases import Databases

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('<API_KEY>') # Your secret API key

databases = Databases(client)

result = databases.decrement_document_column(
    database_id = '<DATABASE_ID>',
    collection_id = '<COLLECTION_ID>',
    document_id = '<DOCUMENT_ID>',
    column = 'credits', # column
    value = 5 # value
)
```
```server-rust
use appwrite::Client;
use appwrite::services::databases::Databases;

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

    let databases = Databases::new(&client);

    let result = databases.decrement_document_attribute(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
        "<DOCUMENT_ID>",
        "credits",     // attribute
        Some(5.0),     // value
        None,          // min (optional)
        None,          // transaction_id (optional)
    ).await?;

    println!("{:?}", result);
    Ok(())
}
```
```graphql
mutation {
    databasesDecrementDocumentColumn(
        databaseId: "<DATABASE_ID>",
        collectionId: "<COLLECTION_ID>",
        documentId: "<DOCUMENT_ID>",
        column: "credits",
        value: 5
    ) {
        _id
        _collectionId
        _databaseId
        _createdAt
        _updatedAt
        _permissions
        data
    }
}
```

### 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

```client-web
// Increment with maximum constraint
const result = await databases.incrementDocumentColumn(
    '<DATABASE_ID>',
    '<COLLECTION_ID>',
    '<DOCUMENT_ID>',
    'credits', // column
    100, // value
    1000 // max (optional)
);

// Decrement with minimum constraint
const result2 = await databases.decrementDocumentColumn(
    '<DATABASE_ID>',
    '<COLLECTION_ID>',
    '<DOCUMENT_ID>',
    'credits', // column
    50, // value
    0 // min (optional)
);
```
```client-flutter
// Increment with maximum constraint
final document = await databases.incrementDocumentColumn(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>',
    column: 'credits',
    value: 100,
    max: 1000
);

// Decrement with minimum constraint
final document2 = await databases.decrementDocumentColumn(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>',
    column: 'credits',
    value: 50,
    min: 0
);
```
```client-apple
// Increment with maximum constraint
let document = try await databases.incrementDocumentColumn(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    documentId: "<DOCUMENT_ID>",
    column: "credits",
    value: 100,
    max: 1000
)

// Decrement with minimum constraint
let document2 = try await databases.decrementDocumentColumn(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    documentId: "<DOCUMENT_ID>",
    column: "credits",
    value: 50,
    min: 0
)
```
```client-android-kotlin
// Increment with maximum constraint
val document = databases.incrementDocumentColumn(
    databaseId = "<DATABASE_ID>",
    collectionId = "<COLLECTION_ID>",
    documentId = "<DOCUMENT_ID>",
    column = "credits",
    value = 100,
    max = 1000
)

// Decrement with minimum constraint
val document2 = databases.decrementDocumentColumn(
    databaseId = "<DATABASE_ID>",
    collectionId = "<COLLECTION_ID>",
    documentId = "<DOCUMENT_ID>",
    column = "credits",
    value = 50,
    min = 0
)
```
```server-nodejs
// Increment with maximum constraint
const result = await databases.incrementDocumentColumn(
    '<DATABASE_ID>',
    '<COLLECTION_ID>',
    '<DOCUMENT_ID>',
    'credits', // column
    100, // value
    1000 // max (optional)
);

// Decrement with minimum constraint
const result2 = await databases.decrementDocumentColumn(
    '<DATABASE_ID>',
    '<COLLECTION_ID>',
    '<DOCUMENT_ID>',
    'credits', // column
    50, // value
    0 // min (optional)
);
```
```server-python
### Increment with maximum constraint
result = databases.increment_document_column(
    database_id = '<DATABASE_ID>',
    collection_id = '<COLLECTION_ID>',
    document_id = '<DOCUMENT_ID>',
    column = 'credits', # column
    value = 100, # value
    max = 1000 # max (optional)
)

### Decrement with minimum constraint
result2 = databases.decrement_document_column(
    database_id = '<DATABASE_ID>',
    collection_id = '<COLLECTION_ID>',
    document_id = '<DOCUMENT_ID>',
    column = 'credits', # column
    value = 50, # value
    min = 0 # min (optional)
)
```
```server-rust
use appwrite::Client;
use appwrite::services::databases::Databases;

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

    let databases = Databases::new(&client);

    // Increment with maximum constraint
    let result = databases.increment_document_attribute(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
        "<DOCUMENT_ID>",
        "credits",      // attribute
        Some(100.0),    // value
        Some(1000.0),   // max (optional)
        None,           // transaction_id (optional)
    ).await?;

    // Decrement with minimum constraint
    let result2 = databases.decrement_document_attribute(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
        "<DOCUMENT_ID>",
        "credits",     // attribute
        Some(50.0),    // value
        Some(0.0),     // min (optional)
        None,          // transaction_id (optional)
    ).await?;

    println!("{:?}", result);
    println!("{:?}", result2);
    Ok(())
}
```

### Follow best practices

#### 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

For complex updates that include both atomic operations and regular field changes, you'll need to use separate API calls:

```client-web
// First, increment the likes atomically
const likeResult = await databases.incrementDocumentColumn(
    '<DATABASE_ID>',
    '<COLLECTION_ID>',
    '<DOCUMENT_ID>',
    'likes', // column
    1 // value
);

// Then, update other fields
const updateResult = await databases.updateDocument(
    '<DATABASE_ID>',
    '<COLLECTION_ID>',
    '<DOCUMENT_ID>',
    {
        lastLikedBy: userId,
        lastLikedAt: new Date().toISOString()
    }
);
```
```client-flutter
// First, increment the likes atomically
final likeResult = await databases.incrementDocumentColumn(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>',
    column: 'likes',
    value: 1
);

// Then, update other fields
final updateResult = await databases.updateDocument(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>',
    data: {
        'lastLikedBy': userId,
        'lastLikedAt': DateTime.now().toIso8601String()
    }
);
```
```client-apple
// First, increment the likes atomically
let likeResult = try await databases.incrementDocumentColumn(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    documentId: "<DOCUMENT_ID>",
    column: "likes",
    value: 1
)

// Then, update other fields
let updateResult = try await databases.updateDocument(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    documentId: "<DOCUMENT_ID>",
    data: [
        "lastLikedBy": userId,
        "lastLikedAt": ISO8601DateFormatter().string(from: Date())
    ]
)
```
```client-android-kotlin
// First, increment the likes atomically
val likeResult = databases.incrementDocumentColumn(
    databaseId = "<DATABASE_ID>",
    collectionId = "<COLLECTION_ID>",
    documentId = "<DOCUMENT_ID>",
    column = "likes",
    value = 1
)

// Then, update other fields
val updateResult = databases.updateDocument(
    databaseId = "<DATABASE_ID>",
    collectionId = "<COLLECTION_ID>",
    documentId = "<DOCUMENT_ID>",
    data = mapOf(
        "lastLikedBy" to userId,
        "lastLikedAt" to Instant.now().toString()
    )
)
```
```server-nodejs
// First, increment the likes atomically
const likeResult = await databases.incrementDocumentColumn(
    '<DATABASE_ID>',
    '<COLLECTION_ID>',
    '<DOCUMENT_ID>',
    'likes', // column
    1 // value
);

// Then, update other fields
const updateResult = await databases.updateDocument(
    '<DATABASE_ID>',
    '<COLLECTION_ID>',
    '<DOCUMENT_ID>',
    {
        lastLikedBy: userId,
        lastLikedAt: new Date().toISOString()
    }
);
```
```server-python
### First, increment the likes atomically
like_result = databases.increment_document_column(
    database_id = '<DATABASE_ID>',
    collection_id = '<COLLECTION_ID>',
    document_id = '<DOCUMENT_ID>',
    column = 'likes', # column
    value = 1 # value
)

### Then, update other fields
update_result = databases.update_document(
    database_id = '<DATABASE_ID>',
    collection_id = '<COLLECTION_ID>',
    document_id = '<DOCUMENT_ID>',
    data = {
        'lastLikedBy': user_id,
        'lastLikedAt': datetime.now().isoformat()
    }
)
```
```server-rust
use appwrite::Client;
use appwrite::services::databases::Databases;
use serde_json::json;

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

    let databases = Databases::new(&client);

    // First, increment the likes atomically
    let like_result = databases.increment_document_attribute(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
        "<DOCUMENT_ID>",
        "likes",       // attribute
        Some(1.0),     // value
        None,          // max (optional)
        None,          // transaction_id (optional)
    ).await?;

    // Then, update other fields
    let update_result = databases.update_document(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
        "<DOCUMENT_ID>",
        Some(json!({
            "lastLikedBy": "<USER_ID>",
            "lastLikedAt": "<TIMESTAMP>"
        })),
        None, // permissions (optional)
        None, // transaction_id (optional)
    ).await?;

    println!("{:?}", like_result);
    println!("{:?}", update_result);
    Ok(())
}
```

#### 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

URL: https://appwrite.io/docs/products/databases/tablesdb/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.

**Important notes**

- Bulk operations trigger Functions, Webhooks, or Realtime events for each document manipulated. Rather than a single event for the entire bulk operation, each document generates a separate event on the existing realtime channels for its operation type.
- Collections that contain relationship attributes are not supported via bulk operations. Use individual document operations for collections with relationships.

### 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.

### Plan limits

Bulk operations have different limits based on your Appwrite plan:

| Plan | Columns per request |
|------|----------------------|
| Free | 100 |
| Pro | 1,000 |

These limits apply to all bulk operations including create, update, upsert, and delete operations. If you need higher limits than what the Pro plan offers, you can [inquire](/contact-us/enterprise) about a custom plan.

### Create documents

You can create multiple documents in a single request using the `createDocuments` method.

**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.

```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 databases = new sdk.Databases(client);

const result = await databases.createDocuments(
    '<DATABASE_ID>',
    '<COLLECTION_ID>',
    [
        {
            $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://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<API_KEY>')

databases = Databases(client)

result = databases.create_documents(
    database_id = '<DATABASE_ID>',
    collection_id = '<COLLECTION_ID>',
    documents = [
        {
            '$id': ID.unique(),
            'name': 'Document 1'
        },
        {
            '$id': ID.unique(),
            'name': 'Document 2'
        }
    ]
)
```
```server-rust
use appwrite::Client;
use appwrite::services::databases::Databases;
use appwrite::id::ID;
use serde_json::json;

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

    let databases = Databases::new(&client);

    let result = databases.create_documents(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
        vec![
            json!({
                "$id": ID::unique(),
                "name": "Document 1"
            }),
            json!({
                "$id": ID::unique(),
                "name": "Document 2"
            }),
        ],
        None, // transaction_id (optional)
    ).await?;

    println!("{:?}", result);
    Ok(())
}
```

### Update documents

**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)

You can update multiple documents in a single request using the `updateDocuments` method.

```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 databases = new sdk.Databases(client);

const result = await databases.updateDocuments(
    '<DATABASE_ID>',
    '<COLLECTION_ID>',
    {
        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://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<API_KEY>')

databases = Databases(client)

result = databases.update_documents(
    database_id = '<DATABASE_ID>',
    collection_id = '<COLLECTION_ID>',
    data = {
        'status': 'published'
    },
    queries = [
        Query.equal('status', 'draft')
    ]
)
```
```server-rust
use appwrite::Client;
use appwrite::services::databases::Databases;
use appwrite::query::Query;
use serde_json::json;

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

    let databases = Databases::new(&client);

    let result = databases.update_documents(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
        Some(json!({
            "status": "published"
        })),
        Some(vec![
            Query::equal("status", "draft").to_string()
        ]),
        None, // transaction_id (optional)
    ).await?;

    println!("{:?}", result);
    Ok(())
}
```

### Upsert documents

**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)

You can upsert multiple documents in a single request using the `upsertDocuments` method.

```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 databases = new sdk.Databases(client);

const result = await databases.upsertDocuments(
    '<DATABASE_ID>',
    '<COLLECTION_ID>',
    [
        {
            $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://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<API_KEY>')

databases = Databases(client)

result = databases.upsert_documents(
    database_id = '<DATABASE_ID>',
    collection_id = '<COLLECTION_ID>',
    documents = [
        {
            '$id': ID.unique(),
            'name': 'New Document 1'
        },
        {
            '$id': 'document-id-2',  # Existing document ID
            'name': 'New Document 2'
        }
    ]
)
```
```server-rust
use appwrite::Client;
use appwrite::services::databases::Databases;
use appwrite::id::ID;
use serde_json::json;

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

    let databases = Databases::new(&client);

    let result = databases.upsert_documents(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
        vec![
            json!({
                "$id": ID::unique(),
                "name": "New Document 1"
            }),
            json!({
                "$id": "document-id-2",
                "name": "New Document 2"
            }),
        ],
        None, // transaction_id (optional)
    ).await?;

    println!("{:?}", result);
    Ok(())
}
```

### Delete documents

**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)

You can delete multiple documents in a single request using the `deleteDocuments` method.

```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 databases = new sdk.Databases(client);

const result = await databases.deleteDocuments(
    '<DATABASE_ID>',
    '<COLLECTION_ID>',
    [
        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://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<API_KEY>')

databases = Databases(client)

result = databases.delete_documents(
    database_id = '<DATABASE_ID>',
    collection_id = '<COLLECTION_ID>',
    queries = [
        Query.equal('status', 'archived')
    ]
)
```
```server-rust
use appwrite::Client;
use appwrite::services::databases::Databases;
use appwrite::query::Query;

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

    let databases = Databases::new(&client);

    let result = databases.delete_documents(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
        Some(vec![
            Query::equal("status", "archived").to_string()
        ]),
        None, // transaction_id (optional)
    ).await?;

    println!("{:?}", result);
    Ok(())
}
```

**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).

---

## Collections

URL: https://appwrite.io/docs/products/databases/tablesdb/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
You can create collections using the Appwrite Console, a [Server SDK](/docs/sdks#server), or using the [CLI](/docs/tooling/command-line/installation).

**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**.

**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/partners/project/api-keys).

```server-nodejs
const sdk = require('node-appwrite');

// Init SDK
const client = new sdk.Client();

const databases = new sdk.Databases(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 = databases.createCollection({
    databaseId: '<DATABASE_ID>',
    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://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<PROJECT_ID>') // Your project ID
    .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key
;


let promise = databases.createCollection({
    databaseId: '<DATABASE_ID>',
    collectionId: '[COLLECTION_ID]',
    name: '[NAME]'
});

promise.then(function (response) {
    console.log(response);
}, function (error) {
    console.log(error);
});
```
```php
<?php

use Appwrite\Client;
use Appwrite\Services\Databases;

$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
;

$databases = new Databases($client);

$result = $databases->createCollection('<DATABASE_ID>', '<COLLECTION_ID>', '<NAME>');
```
```python
from appwrite.client import Client
from appwrite.services.databases import Databases

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
)

databases = Databases(client)

result = databases.create_collection('<DATABASE_ID>', '<COLLECTION_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

databases = Databases.new(client)

response = databases.create_collection(database_id: '<DATABASE_ID>', collection_id: '<COLLECTION_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 databases = new Databases(client);

Collection result = await databases.CreateCollection(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    name: "[NAME]");
```
```dart
import 'package:dart_appwrite/dart_appwrite.dart';

void main() { // Init SDK
  Client client = Client();
  Databases databases = Databases(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 = databases.createCollection(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    name: '<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://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<PROJECT_ID>") // Your project ID
    .setKey("919c2d18fb5d4...a2ae413da83346ad2") // Your secret API key

val databases = Databases(client)

val response = databases.createCollection(
    databaseId = "<DATABASE_ID>",
    collectionId = "<COLLECTION_ID>",
    name = "[NAME]",
)
```
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Databases;

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

Databases databases = new Databases(client);

databases.createCollection(
    "<DATABASE_ID>",
    "<COLLECTION_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 databases = Databases(client)

let collection = try await databases.createCollection(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    name: "[NAME]"
)
```
```server-rust
use appwrite::Client;
use appwrite::services::databases::Databases;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let 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

    let databases = Databases::new(&client);

    let collection = databases.create_collection(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
        "[NAME]",
        None, // permissions
        None, // document_security
        None, // enabled
        None, // attributes
        None, // indexes
    ).await?;

    println!("{:?}", collection);
    Ok(())
}
```

You can also configure **permissions** in the `createCollection` method, learn more about the `createCollection` in the [API references](/docs/references).

**CLI**

**Before proceeding**

Ensure you [**install**](/docs/tooling/command-line/installation#getting-started) the CLI, [**log in**](/docs/tooling/command-line/installation#login) to your Appwrite account, and [**initialize**](/docs/tooling/command-line/installation#initialization) your Appwrite project.

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.

[Learn more about the CLI collections commands](/docs/tooling/command-line/collections#commands)

### 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
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

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

URL: https://appwrite.io/docs/products/databases/tablesdb/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
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
You can programmatically create databases using a [Server SDK](/docs/sdks#server). Appwrite [Server SDKs](/docs/sdks#server) require an [API key](/docs/partners/project/api-keys).

```server-nodejs
const sdk = require('node-appwrite');

// Init SDK
const client = new sdk.Client();

const databases = new sdk.Databases(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 = databases.create('<DATABASE_ID>', '<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://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<PROJECT_ID>') // Your project ID
    .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key
;


let promise = databases.create('<DATABASE_ID>', '<NAME>');

promise.then(function (response) {
    console.log(response);
}, function (error) {
    console.log(error);
});
```
```php
<?php

use Appwrite\Client;
use Appwrite\Services\Databases;

$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
;

$databases = new Databases($client);

$result = $databases->create('<DATABASE_ID>', '<NAME>');
```
```python
from appwrite.client import Client
from appwrite.services.databases import Databases

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
)

databases = Databases(client)

result = databases.create('<DATABASE_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

databases = Databases.new(client)

response = databases.create(database_id: '<DATABASE_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 databases = new Databases(client);

Database result = await databases.Create(
    databaseId: "<DATABASE_ID>",
    name: "<NAME>");
```
```dart
import 'package:dart_appwrite/dart_appwrite.dart';

void main() { // Init SDK
  Client client = Client();
  Databases databases = Databases(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 = databases.create(
    databaseId: '<DATABASE_ID>',
    name: '<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://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<PROJECT_ID>") // Your project ID
    .setKey("919c2d18fb5d4...a2ae413da83346ad2") // Your secret API key

val databases = Databases(client)

val response = databases.create(
    databaseId = "<DATABASE_ID>",
    name = "<NAME>",
)
```
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Databases;

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

Databases databases = new Databases(client);

databases.create(
    "<DATABASE_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 databases = Databases(client)

let response = try await databases.create(
    databaseId: "<DATABASE_ID>",
    name: "<NAME>"
)
```
```server-rust
use appwrite::Client;
use appwrite::services::databases::Databases;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let 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

    let databases = Databases::new(&client);

    let response = databases.create(
        "<DATABASE_ID>",
        "<NAME>",
        None, // enabled
    ).await?;

    println!("{:?}", response);
    Ok(())
}
```

---

## Documents

URL: https://appwrite.io/docs/products/databases/tablesdb/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

**Permissions required**

You must grant **create** permissions to users at the **collection level** before users can create documents.
[Learn more about permissions](#permissions)

In most use cases, you will create documents programmatically.

```client-web
import { Client, Databases, ID } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

const databases = new Databases(client);

const promise = databases.createDocument(
    '<DATABASE_ID>',
    '<COLLECTION_ID>',
    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://<REGION>.cloud.appwrite.io/v1')
        .setProject('<PROJECT_ID>');

    final databases = Databases(client);

    try {
        final document = databases.createDocument(
            databaseId: '<DATABASE_ID>',
            collectionId: '<COLLECTION_ID>',
            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://<REGION>.cloud.appwrite.io/v1")
        .setProject("<PROJECT_ID>")

    let databases = Databases(client)

    do {
        let document = try await databases.createDocument(
            databaseId: "<DATABASE_ID>",
            collectionId: "<COLLECTION_ID>",
            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://<REGION>.cloud.appwrite.io/v1")
        .setProject("<PROJECT_ID>")

    val databases = Databases(client)

    try {
        val document = databases.createDocument(
            databaseId = "<DATABASE_ID>",
            collectionId = "<COLLECTION_ID>",
            documentId = ID.unique(),
            data = mapOf("a" to "b"),
        )
    } catch (e: Exception) {
        Log.e("Appwrite", "Error: " + e.message)
    }
}
```
```graphql
mutation {
    databasesCreateDocument(
        databaseId: "<DATABASE_ID>",
        collectionId: "<COLLECTION_ID>",
        documentId: "<DOCUMENT_ID>",
        data: "{}"
    ) {
        _id
        _collectionId
        _databaseId
        _createdAt
        _updatedAt
        _permissions
        data
    }
}
```

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

**Permissions required**

You must grant **read** permissions to users at the **collection level** before users can read documents.
[Learn more about permissions](#permissions)

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).

```client-web
import { Client, Databases, Query } from "appwrite";

const client = new Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>");

const databases = new Databases(client);

let promise = databases.listDocuments(
    "<DATABASE_ID>",
    "<COLLECTION_ID>",
    [
        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://<REGION>.cloud.appwrite.io/v1")
        .setProject("<PROJECT_ID>")

    final databases = Databases(client);

    try {
        final documents = await databases.listDocuments(
            databaseId: '<DATABASE_ID>',
            collectionId: '<COLLECTION_ID>',
            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://<REGION>.cloud.appwrite.io/v1")
        .setProject("<PROJECT_ID>")

    let databases = Databases(client)

    do {
        let documents = try await databases.listDocuments(
            databaseId: "<DATABASE_ID>",
            collectionId: "<COLLECTION_ID>",
            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://<REGION>.cloud.appwrite.io/v1")
        .setProject("<PROJECT_ID>")

    val databases = Databases(client)

    try {
        val documents = databases.listDocuments(
            databaseId = "<DATABASE_ID>",
            collectionId = "<COLLECTION_ID>",
            queries = listOf(
                Query.equal("title", "Avatar")
            )
        )
    } catch (e: AppwriteException) {
        Log.e("Appwrite", "Error: " + e.message)
    }
}
```
```graphql
query {
    databasesListDocuments(
        databaseId: "<DATABASE_ID>",
        collectionId: "<COLLECTION_ID>",
        queries: ["equal(\"title\", [\"Avatar\"])"]
    ) {
        total
        documents {
            _id
            data
        }
    }
}
```

### Upsert documents

**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)

In most use cases, you will upsert documents programmatically.

```client-web
import { Client, Databases, ID } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

const databases = new Databases(client);

const promise = databases.upsertDocument(
    '<DATABASE_ID>',
    '<COLLECTION_ID>',
    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://<REGION>.cloud.appwrite.io/v1')
        .setProject('<PROJECT_ID>');

    final databases = Databases(client);

    try {
        final document = databases.upsertDocument(
            databaseId: '<DATABASE_ID>',
            collectionId: '<COLLECTION_ID>',
            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://<REGION>.cloud.appwrite.io/v1")
        .setProject("<PROJECT_ID>")

    let databases = Databases(client)

    do {
        let document = try await databases.upsertDocument(
            databaseId: "<DATABASE_ID>",
            collectionId: "<COLLECTION_ID>",
            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://<REGION>.cloud.appwrite.io/v1")
        .setProject("<PROJECT_ID>")

    val databases = Databases(client)

    try {
        val document = databases.upsertDocument(
            databaseId = "<DATABASE_ID>",
            collectionId = "<COLLECTION_ID>",
            documentId = ID.unique(),
            data = mapOf("a" to "b"),
        )
    } catch (e: Exception) {
        Log.e("Appwrite", "Error: " + e.message)
    }
}
```
```graphql
mutation {
    databasesUpsertDocument(
        databaseId: "<DATABASE_ID>",
        collectionId: "<COLLECTION_ID>",
        documentId: "<DOCUMENT_ID>",
        data: "{}"
    ) {
        _id
        _collectionId
        _databaseId
        _createdAt
        _updatedAt
        _permissions
        data
    }
}
```

### 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

Continue learning with these related guides:

- [Queries](/docs/products/databases/queries): Learn how to filter, sort, and search your documents with various query operators.

- [Pagination](/docs/products/databases/pagination): Handle large datasets by implementing pagination in your document queries.

- [Bulk operations](/docs/products/databases/bulk-operations): Perform create, update, and delete operations on multiple documents simultaneously.

- [Timestamp overrides](/docs/products/databases/timestamp-overrides): Set custom creation and update timestamps when migrating data or backdating records.

[Learn more about bulk operations](/docs/products/databases/bulk-operations).

---

## Order

URL: https://appwrite.io/docs/products/databases/tablesdb/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

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.

```client-web
import { Client, Databases, Query } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

const databases = new Databases(client);

databases.listDocuments(
    '<DATABASE_ID>',
    '<COLLECTION_ID>',
    [
        Query.orderAsc('title'),
    ]
);
```

```client-flutter
import 'package:appwrite/appwrite.dart';

void main() async {
    final client = Client()
        .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
        .setProject('<PROJECT_ID>');

    final databases = Databases(client);

    try {
        final documents = await databases.listDocuments(
            databaseId: '<DATABASE_ID>',
            collectionId: '<COLLECTION_ID>',
            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://<REGION>.cloud.appwrite.io/v1')
        .setProject('<PROJECT_ID>');

    let databases = Databases(client)

    do {
        let documents = try await databases.listDocuments(
            databaseId: "<DATABASE_ID>",
            collectionId: "<COLLECTION_ID>",
            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://<REGION>.cloud.appwrite.io/v1')
        .setProject('<PROJECT_ID>');

    val databases = Databases(client)

    try {
        val documents = databases.listDocuments(
            databaseId = "<DATABASE_ID>",
            collectionId = "<COLLECTION_ID>",
            queries = [
                Query.orderAsc("title")
            ]
        )
    } catch (e: AppwriteException) {
        Log.e("Appwrite", e.message)
    }
}
```

```graphql
query {
    databasesListDocuments(
        databaseId: "<DATABASE_ID>",
        collectionId: "<COLLECTION_ID>"
        queries: ["orderAsc(\"title\")"]
    ) {
        total
        documents {
            _id
            data
        }
    }
}
```

### 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.

```js
// Web SDK code example for sorting based on multiple attributes
// ...

// List documents and sort based on multiple attributes
databases.listDocuments(
    '<DATABASE_ID>',
    '<COLLECTION_ID>',
    [
        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: '<DATABASE_ID>',
        collectionId: '<COLLECTION_ID>',
        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 = "<DATABASE_ID>",
        collectionId = "<COLLECTION_ID>",
        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: "<DATABASE_ID>",
        collectionId: "<COLLECTION_ID>",
        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: "<DATABASE_ID>",
        collectionId: "<COLLECTION_ID>",
        queries: ["orderAsc(\"title\")", "orderDesc(\"year\")"]
    ) {
        total
        documents {
            _id
            data
        }
    }
}
```

### Ordering by sequence

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.

```client-web
import { Client, Databases, Query } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

const databases = new Databases(client);

databases.listDocuments(
    '<DATABASE_ID>',
    '<COLLECTION_ID>',
    [
        Query.orderAsc('$sequence'),
    ]
);
```

```client-flutter
import 'package:appwrite/appwrite.dart';

void main() async {
    final client = Client()
        .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
        .setProject('<PROJECT_ID>');

    final databases = Databases(client);

    try {
        final documents = await databases.listDocuments(
            databaseId: '<DATABASE_ID>',
            collectionId: '<COLLECTION_ID>',
            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://<REGION>.cloud.appwrite.io/v1")
        .setProject("<PROJECT_ID>")

    let databases = Databases(client)

    do {
        let documents = try await databases.listDocuments(
            databaseId: "<DATABASE_ID>",
            collectionId: "<COLLECTION_ID>",
            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://<REGION>.cloud.appwrite.io/v1")
        .setProject("<PROJECT_ID>")

    val databases = Databases(client)

    try {
        val documents = databases.listDocuments(
            databaseId = "<DATABASE_ID>",
            collectionId = "<COLLECTION_ID>",
            queries = listOf(
                Query.orderAsc("\$sequence")
            )
        )
    } catch (e: AppwriteException) {
        Log.e("Appwrite", e.message)
    }
}
```

```graphql
query {
    databasesListDocuments(
        databaseId: "<DATABASE_ID>",
        collectionId: "<COLLECTION_ID>"
        queries: ["orderAsc(\"$sequence\")"]
    ) {
        total
        documents {
            _id
            data
        }
    }
}
```

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

URL: https://appwrite.io/docs/products/databases/tablesdb/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 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.

```client-web
import { Client, Databases, Query } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

const databases = new Databases(client);

// Page 1
const page1 = await databases.listDocuments(
    '<DATABASE_ID>',
    '<COLLECTION_ID>',
    [
        Query.limit(25),
        Query.offset(0)
    ]
);

// Page 2
const page2 = await databases.listDocuments(
    '<DATABASE_ID>',
    '<COLLECTION_ID>',
    [
        Query.limit(25),
        Query.offset(25)
    ]
);
```
```client-flutter
import 'package:appwrite/appwrite.dart';

void main() async {
    final client = Client()
        .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
        .setProject('<PROJECT_ID>');

    final databases = Databases(client);

    final page1 = await databases.listDocuments(
        databaseId: '<DATABASE_ID>',
        collectionId: '<COLLECTION_ID>',
        queries: [
            Query.limit(25),
            Query.offset(0)
        ]
    );

    final page2 = await databases.listDocuments(
        databaseId: '<DATABASE_ID>',
        collectionId: '<COLLECTION_ID>',
        queries: [
            Query.limit(25),
            Query.offset(25)
        ]
    );
}
```
```client-apple
import Appwrite
import AppwriteModels

func main() async throws {
    let client = Client()
        .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
        .setProject("<PROJECT_ID>")

    let databases = Databases(client)

    let page1 = try await databases.listDocuments(
        databaseId: "<DATABASE_ID>",
        collectionId: "<COLLECTION_ID>",
        queries: [
            Query.limit(25),
            Query.offset(0)
        ]
    )

    let page2 = try await databases.listDocuments(
        databaseId: "<DATABASE_ID>",
        collectionId: "<COLLECTION_ID>",
        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://<REGION>.cloud.appwrite.io/v1")
        .setProject("<PROJECT_ID>")

    val databases = Databases(client)

    val page1 = databases.listDocuments(
        databaseId = "<DATABASE_ID>",
        collectionId = "<COLLECTION_ID>",
        queries = [
            Query.limit(25),
            Query.offset(0)
        ]
    )

    val page2 = databases.listDocuments(
        databaseId = "<DATABASE_ID>",
        collectionId = "<COLLECTION_ID>",
        queries = [
            Query.limit(25),
            Query.offset(25)
        ]
    )
}
```

**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.

### 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.

```client-web
import { Databases, Query } from "appwrite";

const client = new Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>");

const databases = new Databases(client);

// Page 1
const page1 = await databases.listDocuments(
    '<DATABASE_ID>',
    '<COLLECTION_ID>',
    [
        Query.limit(25),
    ]
);

const lastId = page1.documents[page1.documents.length - 1].$id;

// Page 2
const page2 = await databases.listDocuments(
    '<DATABASE_ID>',
    '<COLLECTION_ID>',
    [
        Query.limit(25),
        Query.cursorAfter(lastId),
    ]
);
```

```client-flutter
import 'package:appwrite/appwrite.dart';

void main() async {
    final client = Client()
        .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
        .setProject('<PROJECT_ID>');

    final databases = Databases(client);

    final page1 = await databases.listDocuments(
        databaseId: '<DATABASE_ID>',
        collectionId: '<COLLECTION_ID>',
        queries: [
            Query.limit(25)
        ]
    );

    final lastId = page1.documents[page1.documents.length - 1].$id;

    final page2 = await databases.listDocuments(
        databaseId: '<DATABASE_ID>',
        collectionId: '<COLLECTION_ID>',
        queries: [
            Query.limit(25),
            Query.cursorAfter(lastId)
        ]
    );

}
```
```client-apple
import Appwrite
import AppwriteModels

func main() async throws {
    let client = Client()
      .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
      .setProject("<PROJECT_ID>")

    let databases = Databases(client)

    let page1 = try await databases.listDocuments(
        databaseId: "<DATABASE_ID>",
        collectionId: "<COLLECTION_ID>",
        queries: [
            Query.limit(25)
        ]
    )

    let lastId = page1.documents[page1.documents.count - 1].$id

    let page2 = try await databases.listDocuments(
        databaseId: "<DATABASE_ID>",
        collectionId: "<COLLECTION_ID>",
        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://<REGION>.cloud.appwrite.io/v1")
        .setProject("<PROJECT_ID>")

    val databases = Databases(client)

    val page1 = databases.listDocuments(
        databaseId = "<DATABASE_ID>",
        collectionId = "<COLLECTION_ID>",
        queries = [
            Query.limit(25)
        ]
    )

    val lastId = page1.documents[page1.documents.size - 1].$id

    val page2 = databases.listDocuments(
        databaseId = "<DATABASE_ID>",
        collectionId = "<COLLECTION_ID>",
        queries = [
            Query.limit(25),
            Query.cursorAfter(lastId)
        ]
    )
}
```

### When to use what?
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

URL: https://appwrite.io/docs/products/databases/tablesdb/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 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/security/permissions)

### 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/security/permissions)

### 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/security/permissions#examples) in our platform documentation.

---

## Queries

URL: https://appwrite.io/docs/products/databases/tablesdb/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

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

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`.

**Default pagination behavior**

By default, results are limited to the **first 25 items**.
You can change this through [pagination](/docs/products/databases/legacy/pagination).

```client-web
import { Client, Databases, Query } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

const databases = new Databases(client);

databases.listDocuments(
    '<DATABASE_ID>',
    '<COLLECTION_ID>',
    [
        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://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>')
;

const promise = databases.listDocuments(
    '<DATABASE_ID>',
    '<COLLECTION_ID>',
    [
        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://<REGION>.cloud.appwrite.io/v1')
        .setProject('<PROJECT_ID>');

    final databases = Databases(client);

    try {
        final documents = await databases.listDocuments(
            '<DATABASE_ID>',
            '<COLLECTION_ID>',
            [
                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://<REGION>.cloud.appwrite.io/v1")
        .setProject("<PROJECT_ID>")

    let databases = Databases(client)

    do {
        let documents = try await databases.listDocuments(
            databaseId: "<DATABASE_ID>",
            collectionId: "<COLLECTION_ID>",
            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://<REGION>.cloud.appwrite.io/v1')
        .setProject('<PROJECT_ID>');

    val databases = Databases(client)

    try {
        val documents = databases.listDocuments(
            databaseId = "<DATABASE_ID>",
            collectionId = "<COLLECTION_ID>",
            queries = listOf(
                Query.equal("title", listOf("Avatar", "Lord of the Rings")),
                Query.greaterThan("year", 1999)
            )
        )
    } catch (e: AppwriteException) {
        Log.e("Appwrite", e.message)
    }
}
```
```php
<?php

use Appwrite\Client;
use Appwrite\Query;
use Appwrite\Services\Databases;

$client = new Client();

$client
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>')
;

$databases = new Databases($client);

$result = $databases->listDocuments(
    '<DATABASE_ID>',
    '<COLLECTION_ID>',
    [
        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://<REGION>.cloud.appwrite.io/v1')
  .set_project('<PROJECT_ID>')
  .set_key('<YOUR_API_KEY>')
)

databases = Databases(client)

result = databases.list_documents(
    '<DATABASE_ID>',
    '<COLLECTION_ID>',
    [
        Query.equal('title', ['Avatar', 'Lord of the Rings']),
        Query.greater_than('year', 1999)
    ]
)
```
```graphql
query {
    databasesListDocuments(
        databaseId: "<DATABASE_ID>",
        collectionId: "<COLLECTION_ID>"
        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/<DATABASE_ID>/collections/<COLLECTION_ID>/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: <PROJECT_ID>
```
```rust
use appwrite::Client;
use appwrite::services::databases::Databases;
use appwrite::query::Query;
use serde_json::json;

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

    let databases = Databases::new(&client);

    let result = databases.list_documents(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
        Some(vec![
            Query::equal("title", json!(["Avatar", "Lord of the Rings"])).to_string(),
            Query::greater_than("year", 1999).to_string(),
        ]),
        None,
        None,
        None,
    ).await?;

    println!("{:?}", result);

    Ok(())
}
```

### Query operators

#### 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.

```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"]}
```
```rust
Query::select(vec!["name", "title"])
```

##### Select relationship data

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:

```client-web
const doc = await databases.getDocument(
    '<DATABASE_ID>', '<COLLECTION_ID>', '<DOCUMENT_ID>',
    [Query.select(['name', 'age'])]
);
```
```client-flutter
final doc = await databases.getDocument(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>',
    queries: [Query.select(["name", "age"])]
);
```
```python
doc = databases.get_document(
    '<DATABASE_ID>', '<COLLECTION_ID>', '<DOCUMENT_ID>',
    [Query.select(["name", "age"])]
)
```
```ruby
doc = databases.get_document(
    '<DATABASE_ID>', '<COLLECTION_ID>', '<DOCUMENT_ID>',
    [Query.select(["name", "age"])]
)
```
```server-nodejs
const doc = await databases.getDocument(
    '<DATABASE_ID>', '<COLLECTION_ID>', '<DOCUMENT_ID>',
    [Query.select(['name', 'age'])]
);
```
```php
$doc = $databases->getDocument(
    '<DATABASE_ID>', '<COLLECTION_ID>', '<DOCUMENT_ID>',
    [Query::select(["name", "age"])]
);
```
```swift
let doc = try await databases.getDocument(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    documentId: "<DOCUMENT_ID>",
    queries: [Query.select(["name", "age"])]
)
```
```http
GET /v1/databases/<DATABASE_ID>/collections/<COLLECTION_ID>/documents/<DOCUMENT_ID>?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: <PROJECT_ID>
```
```rust
let doc = databases.get_document(
    "<DATABASE_ID>",
    "<COLLECTION_ID>",
    "<DOCUMENT_ID>",
    Some(vec![
        Query::select(vec!["name", "age"]).to_string(),
    ]),
    None,
).await?;
```

###### Load all relationship data
Use the `*` wildcard to load all fields from related documents:

```client-web
const doc = await databases.getDocument(
    '<DATABASE_ID>', '<COLLECTION_ID>', '<DOCUMENT_ID>',
    [Query.select(['*', 'reviews.*'])]
);
```
```client-flutter
final doc = await databases.getDocument(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>',
    queries: [Query.select(["*", "reviews.*"])]
);
```
```python
doc = databases.get_document(
    '<DATABASE_ID>', '<COLLECTION_ID>', '<DOCUMENT_ID>',
    [Query.select(["*", "reviews.*"])]
)
```
```ruby
doc = databases.get_document(
    '<DATABASE_ID>', '<COLLECTION_ID>', '<DOCUMENT_ID>',
    [Query.select(["*", "reviews.*"])]
)
```
```server-nodejs
const doc = await databases.getDocument(
    '<DATABASE_ID>', '<COLLECTION_ID>', '<DOCUMENT_ID>',
    [Query.select(["*", "reviews.*"])]
);
```
```php
$doc = $databases->getDocument(
    '<DATABASE_ID>', '<COLLECTION_ID>', '<DOCUMENT_ID>',
    [Query::select(["*", "reviews.*"])]
);
```
```swift
let doc = try await databases.getDocument(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    documentId: "<DOCUMENT_ID>",
    queries: [Query.select(["*", "reviews.*"])]
)
```
```http
GET /v1/databases/<DATABASE_ID>/collections/<COLLECTION_ID>/documents/<DOCUMENT_ID>?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: <PROJECT_ID>
{"method":"select","values":["*","reviews.*"]}
```
```rust
let doc = databases.get_document(
    "<DATABASE_ID>",
    "<COLLECTION_ID>",
    "<DOCUMENT_ID>",
    Some(vec![
        Query::select(vec!["*", "reviews.*"]).to_string(),
    ]),
    None,
).await?;
```

###### Select specific relationship fields
For precise control, select only specific fields from related documents:

```client-web
const doc = await databases.getDocument(
    '<DATABASE_ID>', '<COLLECTION_ID>', '<DOCUMENT_ID>',
    [Query.select(['name', 'age', 'reviews.author', 'reviews.rating'])]
);
```
```client-flutter
final doc = await databases.getDocument(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>',
    queries: [Query.select(["name", "age", "reviews.author", "reviews.rating"])]
);
```
```python
doc = databases.get_document(
    '<DATABASE_ID>', '<COLLECTION_ID>', '<DOCUMENT_ID>',
    [Query.select(["name", "age", "reviews.author", "reviews.rating"])]
)
```
```ruby
doc = databases.get_document(
    '<DATABASE_ID>', '<COLLECTION_ID>', '<DOCUMENT_ID>',
    [Query.select(["name", "age", "reviews.author", "reviews.rating"])]
)
```
```server-nodejs
const doc = await databases.getDocument(
    '<DATABASE_ID>', '<COLLECTION_ID>', '<DOCUMENT_ID>',
    [Query.select(["name", "age", "reviews.author", "reviews.rating"])]
);
// Result: { name: "John", age: 30, reviews: [{ author: "...", rating: 5 }] }
```
```php
$doc = $databases->getDocument(
    '<DATABASE_ID>', '<COLLECTION_ID>', '<DOCUMENT_ID>',
    [Query::select(["name", "age", "reviews.author", "reviews.rating"])]
);
```
```swift
let doc = try await databases.getDocument(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    documentId: "<DOCUMENT_ID>",
    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"]}
```
```rust
let doc = databases.get_document(
    "<DATABASE_ID>",
    "<COLLECTION_ID>",
    "<DOCUMENT_ID>",
    Some(vec![
        Query::select(vec!["name", "age", "reviews.author", "reviews.rating"]).to_string(),
    ]),
    None,
).await?;
```

###### Load nested relationships
You can also load relationships of relationships:

```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.*"]}
```
```rust
Query::select(vec!["*", "reviews.*", "reviews.author.*"])
```

##### Use selection 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

**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.

**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).

#### Comparison operators

##### Equal

Returns document if attribute is equal to any value in the provided array.

```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"]}
```
```rust
Query::equal("title", json!(["Iron Man"]))
```

##### Not equal

Returns document if attribute is not equal to any value in the provided array.

```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"}
```
```rust
Query::not_equal("title", "Iron Man")
```

##### Less than

Returns document if attribute is less than the provided value.

```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]}
```
```rust
Query::less_than("score", 10)
```

##### Less than or equal

Returns document if attribute is less than or equal to the provided value.

```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]}
```
```rust
Query::less_than_equal("score", 10)
```

##### Greater than

Returns document if attribute is greater than the provided value.

```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]}
```
```rust
Query::greater_than("score", 10)
```

##### Greater than or equal

Returns document if attribute is greater than or equal to the provided value.

```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]}
```
```rust
Query::greater_than_equal("score", 10)
```

##### Between

Returns document if attribute value falls between the two values. The boundary values are inclusive and can be strings or numbers.

```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]}
```
```rust
Query::between("price", 5, 10)
```

#### Null checks

##### Is null

Returns documents where attribute value is null.

```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"}
```
```rust
Query::is_null("name")
```

##### Is not null

Returns documents where attribute value is **not** null.

```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"}
```
```rust
Query::is_not_null("name")
```

#### String operations

##### Starts with

Returns documents if a string attribute starts with a substring.

```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"]}
```
```rust
Query::starts_with("name", "Once upon a time")
```

##### Ends with

Returns documents if a string attribute ends with a substring.

```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."]}
```
```rust
Query::ends_with("name", "happily ever after.")
```

##### Contains

Returns documents if the array attribute contains the specified elements or if a string attribute contains the specified substring.

```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"]}
```
```rust
// For arrays
Query::contains("ingredients", json!(["apple", "banana"]))

// For strings
Query::contains("name", "Tom")
```

##### Search

Searches string attributes for provided keywords. Requires a [full-text index](/docs/products/databases/legacy/collections#indexes) on queried attributes. The search string must be at least **3 characters** to perform a search.

**Searching values with hyphens**

The hyphen (`-`) is treated as a stop character by the underlying search engine. To search for exact values that contain hyphens (for example, ticket or SKU codes like `SWT-2621-44`), wrap the value in quotes inside the search string: `Query.search(attribute, '"SWT-2621-44"')`.

```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"]}
```
```rust
Query::search("text", "key words")
```

#### Logical operators

##### AND

Returns document if it matches all of the nested sub-queries in the array passed in.

```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]}]}
```
```rust
Query::and(vec![
    Query::less_than("size", 10).to_string(),
    Query::greater_than("size", 5).to_string(),
])
```

##### OR

Returns document if it matches any of the nested sub-queries in the array passed in.

```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]}]}
```
```rust
Query::or(vec![
    Query::less_than("size", 5).to_string(),
    Query::greater_than("size", 10).to_string(),
])
```

#### Ordering

##### Order descending

Orders results in descending order by attribute. Attribute must be indexed.

```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"}
```
```rust
Query::order_desc("attribute")
```

##### Order ascending

Orders results in ascending order by attribute. Attribute must be indexed.

```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"}
```
```rust
Query::order_asc("attribute")
```

#### Pagination

##### Limit

Limits the number of results returned by the query. Used for [pagination](/docs/products/databases/legacy/pagination).

```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]}
```
```rust
Query::limit(25)
```

##### Offset

Offset the results returned by skipping some of the results. Used for [pagination](/docs/products/databases/legacy/pagination).

```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]}
```
```rust
Query::offset(0)
```

##### Cursor after

Places the cursor after the specified resource ID. Used for [pagination](/docs/products/databases/legacy/pagination).

```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"]}
```
```rust
Query::cursor_after("62a7...f620")
```

##### Cursor before

Places the cursor before the specified resource ID. Used for [pagination](/docs/products/databases/legacy/pagination).

```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"]}
```
```rust
Query::cursor_before("62a7...a600")
```

### 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:

```client-web
const results = await databases.listDocuments(
    '<DATABASE_ID>',
    '<COLLECTION_ID>',
    [
        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(
    '<DATABASE_ID>',
    '<COLLECTION_ID>',
    [
        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='<DATABASE_ID>',
    collection_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]}]}]}
```
```rust
use appwrite::Client;
use appwrite::services::databases::Databases;
use appwrite::query::Query;
use serde_json::json;

let client = Client::new()
    .set_endpoint("https://<REGION>.cloud.appwrite.io/v1")
    .set_project("<PROJECT_ID>")
    .set_key("<YOUR_API_KEY>");

let databases = Databases::new(&client);

let results = databases.list_documents(
    "<DATABASE_ID>",
    "<COLLECTION_ID>",
    Some(vec![
        Query::or(vec![
            Query::and(vec![
                Query::equal("category", json!(["books"])).to_string(),
                Query::less_than("price", 20).to_string(),
            ]).to_string(),
            Query::and(vec![
                Query::equal("category", json!(["magazines"])).to_string(),
                Query::less_than("price", 10).to_string(),
            ]).to_string(),
        ]).to_string(),
    ]),
    None,
    None,
    None,
).await?;
```

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

URL: https://appwrite.io/docs/products/databases/tablesdb/legacy/quick-start

#### 1. 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.

#### 2. 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.

#### 3. Create documents

To create a document use the `createDocument` method.

In the **Settings** menu, find your project ID and replace `<PROJECT_ID>` in the example.

Navigate to the `Oscar` database, copy the database ID, and replace `<DATABASE_ID>`.
Then, in the `My books` collection, copy the collection ID, and replace `<COLLECTION_ID>`.

```client-web
import { Client, Databases, ID } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

const databases = new Databases(client);

const promise = databases.createDocument(
    '<DATABASE_ID>',
    '<COLLECTION_ID>',
    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://<REGION>.cloud.appwrite.io/v1')
        .setProject('<PROJECT_ID>');

    final databases = Databases(client);

    try {
        final document = databases.createDocument(
            databaseId: '<DATABASE_ID>',
            collectionId: '<COLLECTION_ID>',
            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://<REGION>.cloud.appwrite.io/v1")
        .setProject("<PROJECT_ID>")

    let databases = Databases(client)

    do {
        let document = try await databases.createDocument(
            databaseId: "<DATABASE_ID>",
            collectionId: "<COLLECTION_ID>",
            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://<REGION>.cloud.appwrite.io/v1")
        .setProject("<PROJECT_ID>")

    val databases = Databases(client)

    try {
        val document = databases.createDocument(
            databaseId = "<DATABASE_ID>",
            collectionId = "<COLLECTION_ID>",
            documentId = ID.unique(),
            data = mapOf("title" to "hamlet"),
        )
    } catch (e: Exception) {
        Log.e("Appwrite", "Error: " + e.message)
    }
}
```

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"
}
```

#### 4. List documents

To read and query data from your collection, use the `listDocuments` endpoint.

Like the previous step, replace `<PROJECT_ID>`, `<DATABASE_ID>`, and`<COLLECTION_ID>` with their respective IDs.

```client-web
import { Client, Databases, Query } from "appwrite";

const client = new Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

const databases = new Databases(client);

let promise = databases.listDocuments(
    "<DATABASE_ID>",
    "<COLLECTION_ID>",
    [
        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://<REGION>.cloud.appwrite.io/v1")
        .setProject("<PROJECT_ID>")

    final databases = Databases(client);

    try {
        final documents = await databases.listDocuments(
            databaseId: '<DATABASE_ID>',
            collectionId: '<COLLECTION_ID>',
            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://<REGION>.cloud.appwrite.io/v1")
        .setProject("<PROJECT_ID>")

    let databases = Databases(client)

    do {
        let documents = try await databases.listDocuments(
            databaseId: "<DATABASE_ID>",
            collectionId: "<COLLECTION_ID>",
            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://<REGION>.cloud.appwrite.io/v1")
        .setProject("<PROJECT_ID>")

    val databases = Databases(client)

    try {
        val documents = databases.listDocuments(
            databaseId = "<DATABASE_ID>",
            collectionId = "<COLLECTION_ID>",
            queries = listOf(
                Query.equal("title", "Hamlet")
            )
        )
    } catch (e: AppwriteException) {
        Log.e("Appwrite", "Error: " + e.message)
    }
}
```

---

## Relationships

URL: https://appwrite.io/docs/products/databases/tablesdb/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.

### 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

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

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

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

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.

[Learn how to load relationships with queries](/docs/products/databases/legacy/queries#relationship-select)

### 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

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

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
You can define relationships in the Appwrite Console, or using a [Server SDK](/docs/sdks#server)

**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.

**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.

```js
const { Client, Databases } = require('node-appwrite');

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<PROJECT_ID>');               // 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://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    ->setProject('<PROJECT_ID>');               // 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://<REGION>.cloud.appwrite.io/v1')     # Your API Endpoint
    .set_project('<PROJECT_ID>'))                   # 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://<REGION>.cloud.appwrite.io/v1')# Your API Endpoint
    .set_project('<PROJECT_ID>')               # 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://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<PROJECT_ID>");               // 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://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<PROJECT_ID>');               // 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://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<PROJECT_ID>")                // 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://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<PROJECT_ID>")                // 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://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>");

var databases = new Databases(client);

await databases.CreateRelationshipAttribute(
    databaseId: "marvel",
    collectionId: "movies",
    relatedCollectionId: "reviews",
    type: "oneToMany",
    twoWay: true,
    key: "reviews",
    twoWayKey: "movie",
    onDelete: "cascade");
```

### Creating 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***.

**Nested**

You can create both the **parent** and **child** at once in a relationship by nesting data.

```js
const { Client, Databases, ID } = require('node-appwrite');

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<PROJECT_ID>');               // 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://<REGION>.cloud.appwrite.io/v1')  // Your API Endpoint
    .setProject('<PROJECT_ID>');                // 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://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<PROJECT_ID>")                // 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://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<PROJECT_ID>")                // 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!")
        )
    )
)
```

#### 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**.

**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.

```js
const { Client, Databases, ID } = require('node-appwrite');

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<PROJECT_ID>');               // Your project ID

const databases = new Databases(client);

await databases.createDocument(
    'marvel',
    'movies',
    ID.unique(),
    {
        title: 'Spiderman',
        year: 2002,
        reviews: [
            '<REVIEW_ID_1>',
            '<REVIEW_ID_2>'
        ]
    }
)
```

```dart
import 'package:appwrite/appwrite.dart';

final client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<PROJECT_ID>');               // Your project ID

final databases = Databases(client);

await databases.createDocument(
  databaseId: 'marvel',
  collectionId: 'movies',
  documentId: ID.unique(),
  data: {
    'title': 'Spiderman',
    'year': 2002,
    'reviews': [
        '<REVIEW_ID_1>',
        '<REVIEW_ID_2>'
    ]
  },
)
```

```swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<PROJECT_ID>")                // Your project ID

let databases = Database(client: client)

databases.createDocument(
    databaseId: "marvel",
    collectionId: "movies",
    documentId: ID.unique(),
    data: [
        "title": "Spiderman",
        "year": 2002,
        "reviews": [
            "<REVIEW_ID_1>",
            "<REVIEW_ID_2>"
        ]
    ]
)
```

```kotlin
import io.appwrite.Client
import io.appwrite.services.Database
import io.appwrite.ID

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<PROJECT_ID>")                // 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(
            "<REVIEW_ID_1>",
            "<REVIEW_ID_2>"
        )
    )
)
```

### Queries

You can use filter queries directly against relationship attributes using dot notation. This lets you filter documents based on the values of their related documents, such as filtering posts by an author's name or filtering orders by a product's category.

Use the format `relationshipKey.field` to reference fields on related documents.

```js
const { Client, Databases, Query } = require('node-appwrite');

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<PROJECT_ID>');               // Your project ID

const databases = new Databases(client);

await databases.listDocuments(
    'marvel',
    'movies',
    [
        Query.equal('reviews.author', ['Bob'])
    ]
);
```

```dart
import 'package:appwrite/appwrite.dart';

final client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

final databases = Databases(client);

await databases.listDocuments(
  databaseId: 'marvel',
  collectionId: 'movies',
  queries: [
    Query.equal('reviews.author', ['Bob']),
  ],
);
```

```swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

let databases = Databases(client)

databases.listDocuments(
    databaseId: "marvel",
    collectionId: "movies",
    queries: [
        Query.equal("reviews.author", value: ["Bob"])
    ]
)
```

```kotlin
import io.appwrite.Client
import io.appwrite.services.Databases
import io.appwrite.Query

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

val databases = Databases(client)

databases.listDocuments(
    databaseId = "marvel",
    collectionId = "movies",
    queries = listOf(
        Query.equal("reviews.author", listOf("Bob"))
    )
)
```

All filter queries are supported on relationship fields, including `equal`, `notEqual`, `greaterThan`, `lessThan`, `between`, `contains`, and other [comparison operators](/docs/products/databases/legacy/queries#comparison).

[Learn how to select and load relationship data](/docs/products/databases/legacy/queries#relationship-select)

### Update Relationships
Relationships can be updated by updating the relationship attribute.

```js
const { Client, Databases } = require('node-appwrite');

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<PROJECT_ID>');               // 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://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

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://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

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://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

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"
        )
    )
)
```

### Delete relationships
#### Unlink relationships, retain documents

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

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.

```js
const { Client, Databases } = require('node-appwrite');

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<PROJECT_ID>');               // 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://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

final databases = Databases(client);

await databases.deleteDocument(
  databaseId: 'marvel',
  collectionId: 'movies',
  documentId: 'spiderman'
);
```

```swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

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://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

val databases = Database(client)

databases.deleteDocument(
    databaseId = "marvel",
    collectionId = "movies",
    documentId = "spiderman"
)
```

### 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**.

```js
const { Client, Databases, ID } = require('node-appwrite');

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<PROJECT_ID>');               // 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://<REGION>.cloud.appwrite.io/v1')
    .setProject('<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!',
         '\$permissions': [
           Permission.read(Role.any())
         ]
       },
    ]
  },
);
```
```swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<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!",
                "$permissions": [
                    Permission.read(Role.any())
                ]
            ],
        ]
    ]
);
```
```kotlin
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<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!",
                "$permissions": [
                    Permission.read(Role.any())
                ]
            ],
        ]
    ]
);
```

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

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

URL: https://appwrite.io/docs/products/databases/tablesdb/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] <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. |
| `--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": "varchar",
                    "required": true,
                    "array": false,
                    "size": 255,
                    "default": null
                },
                {
                    "key": "author",
                    "type": "varchar",
                    "required": true,
                    "array": false,
                    "size": 255,
                    "default": null
                },
                {
                    "key": "release_year",
                    "type": "datetime",
                    "required": false,
                    "array": false,
                    "format": "",
                    "default": null
                },
                {
                    "key": "category",
                    "type": "varchar",
                    "required": false,
                    "array": false,
                    "elements": [
                        "fiction",
                        "nonfiction"
                    ],
                    "format": "enum",
                    "default": null
                },
                {
                    "key": "genre",
                    "type": "varchar",
                    "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:

**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;
}
```

**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
 */
```

**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 +
                '}';
    }
}
```

**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;
  }
}
```

**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,
    };
  }
}
```

**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,
)
```

**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
        )
    }
}
```

---

## Offline sync

URL: https://appwrite.io/docs/products/databases/tablesdb/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

- [RxDB](/integrations/replication-rxdb)

### 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

URL: https://appwrite.io/docs/products/databases/tablesdb/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

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:

```client-web
import { Client, TablesDB, Operator } from "appwrite";

const client = new Client()
  .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
  .setProject('<YOUR_PROJECT_ID>');

const tablesDB = new TablesDB(client);

await tablesDB.updateRow({
  databaseId: "<DATABASE_ID>",
  tableId: "<TABLE_ID>",
  rowId: "<ROW_ID>",
  data: {
    letters: Operator.arrayAppend(['c'])
  }
});
```
```server-nodejs
const sdk = require('node-appwrite');

const client = new sdk.Client();
client
  .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
  .setProject('<YOUR_PROJECT_ID>')
  .setKey('<YOUR_API_KEY>');

const tablesDB = new sdk.TablesDB(client);

const result = await tablesDB.updateRow({
  databaseId: '<DATABASE_ID>',
  tableId: '<TABLE_ID>',
  rowId: '<ROW_ID>',
  data: {
    letters: sdk.Operator.arrayAppend(['c'])
  }
});
```
```client-flutter
import 'package:appwrite/appwrite.dart';

void main() async {
  final client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<YOUR_PROJECT_ID>');

  final tablesDB = TablesDB(client);

  try {
    await tablesDB.updateRow(
        '<DATABASE_ID>',
        '<TABLE_ID>',
        '<ROW_ID>',
        {
            '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://<REGION>.cloud.appwrite.io/v1")
    .setProject("<YOUR_PROJECT_ID>")

  let tablesDB = TablesDB(client)

  do {
    _ = try await tablesDB.updateRow(
      databaseId: "<DATABASE_ID>",
      tableId: "<TABLE_ID>",
      rowId: "<ROW_ID>",
      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://<REGION>.cloud.appwrite.io/v1")
    .setProject("<YOUR_PROJECT_ID>")

  val tablesDB = TablesDB(client)

  tablesDB.updateRow(
    databaseId = "<DATABASE_ID>",
    tableId = "<TABLE_ID>",
    rowId = "<ROW_ID>",
    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://<REGION>.cloud.appwrite.io/v1"),
    appwrite.WithProject("<YOUR_PROJECT_ID>"),
    appwrite.WithKey("<YOUR_API_KEY>"),
  )

  tablesDB := appwrite.NewTablesDB(client)

  _, err := tablesDB.UpdateRow(
    "<DATABASE_ID>",
    "<TABLE_ID>",
    "<ROW_ID>",
    tablesDB.WithUpdateRowData(map[string]any{
      "letters": operator.ArrayAppend([]string{"c"}),
    }),
  )
  if err != nil {
    log.Fatal(err)
  }
}
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\TablesDB;
use Appwrite\Operator;

$client = (new Client())
  ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
  ->setProject('<YOUR_PROJECT_ID>')
  ->setKey('<YOUR_API_KEY>');

$tablesDB = new TablesDB($client);

$result = $tablesDB->updateRow(
  '<DATABASE_ID>',
  '<TABLE_ID>',
  '<ROW_ID>',
  [ '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://<REGION>.cloud.appwrite.io/v1')
  .set_project('<YOUR_PROJECT_ID>')
  .set_key('<YOUR_API_KEY>'))

tablesDB = TablesDB(client)

result = tablesDB.update_row(
    database_id='<DATABASE_ID>',
    table_id='<TABLE_ID>',
    row_id='<ROW_ID>',
    data={ 'letters': Operator.arrayAppend(['c']) }
)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

var client = new Client()
  .SetEndpoint("https://<REGION>.cloud.appwrite.io/v1")
  .SetProject("<YOUR_PROJECT_ID>")
  .SetKey("<YOUR_API_KEY>");

var tablesDB = new TablesDB(client);

await tablesDB.UpdateRow(
  databaseId: "<DATABASE_ID>",
  tableId: "<TABLE_ID>",
  rowId: "<ROW_ID>",
  data: new Dictionary<string, object>
  {
    { "letters", Operator.ArrayAppend(new[] { "c" }) }
  }
);
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new()

client
  .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
  .set_project('<YOUR_PROJECT_ID>')
  .set_key('<YOUR_API_KEY>')

tablesDB = TablesDB.new(client)

result = tablesDB.update_row(
    database_id: '<DATABASE_ID>',
    table_id: '<TABLE_ID>',
    row_id: '<ROW_ID>',
    data: { '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://<REGION>.cloud.appwrite.io/v1")
  .setProject("<YOUR_PROJECT_ID>")
  .setKey("<YOUR_API_KEY>");

TablesDB tablesDB = new TablesDB(client);

tablesDB.updateRow(
  "<DATABASE_ID>",
  "<TABLE_ID>",
  "<ROW_ID>",
  Map.of("letters", Operator.arrayAppend(List.of("c"))),
  new CoroutineCallback<>((result, error) -> {
    if (error != null) {
      error.printStackTrace();
      return;
    }
    System.out.println(result);
  })
);
```
```server-rust
use appwrite::Client;
use appwrite::services::tables_db::TablesDB;
use appwrite::operator;
use serde_json::json;

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

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

    let result = tables_db.update_row(
        "<DATABASE_ID>",
        "<TABLE_ID>",
        "<ROW_ID>",
        Some(json!({
            "letters": operator::array_append(&["c"])
        })),
        None,
        None,
    ).await?;

    Ok(())
}
```

### When to use operators

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

The following operators are available, grouped by field type. Each operator updates the given column atomically on the server.

#### Numeric

Perform arithmetic on numeric fields without reading the row first.

##### increment

Increase a numeric field by a specified value. Optionally cap the result at a maximum value.

```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)
```
```server-rust
operator::increment_by(1)
```

##### decrement

Decrease a numeric field by a specified value. Optionally cap the result at a minimum value.

```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)
```
```server-rust
operator::decrement_by(1)
```

##### multiply

Multiply a numeric field by a specified factor. Optionally cap the result at a maximum value.

```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)
```
```server-rust
operator::multiply(2)
```

##### divide

Divide a numeric field by a specified divisor. Optionally cap the result at a minimum value. Divisor cannot be zero.

```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)
```
```server-rust
operator::divide(5)
```

##### modulo

Set a numeric field to the remainder of itself divided by a specified value.

```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)
```
```server-rust
operator::modulo(3)
```

##### power

Raise a numeric field to a specified exponent. Optionally cap the result at a maximum value.

```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)
```
```server-rust
operator::power(2)
```

#### Array

Edit lists in place: append, remove, or modify array items atomically.

##### arrayAppend

Add one or more elements to the end of an array.

```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"))
```
```server-rust
operator::array_append(&["c"])
```

##### arrayPrepend

Add one or more elements to the beginning of an array.

```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"))
```
```server-rust
operator::array_prepend(&["z"])
```

##### arrayInsert

Insert an element at a specific index in an array.

```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")
```
```server-rust
operator::array_insert(1, "x")
```

##### arrayRemove

Remove a specified element from an array.

```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")
```
```server-rust
operator::array_remove("b")
```

##### arrayUnique

Remove duplicate elements from an array.

```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()
```
```server-rust
operator::array_unique()
```

##### arrayIntersect

Keep only elements that exist in both arrays.

```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"})
```
```server-rust
operator::array_intersect(&["news", "tech"])
```

##### arrayDiff

Return elements that exist in the current array but not in the provided array.

```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"})
```
```server-rust
operator::array_diff(&["old"])
```

##### arrayFilter

Filter array elements based on a condition.

```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)
```
```server-rust
operator::array_filter_with_value(operator::Condition::GreaterThan, 10)
```

#### String

Make lightweight text changes without rewriting the whole row.

##### stringConcat

Concatenate a value to a string or array field.

```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("!")
```
```server-rust
operator::string_concat("!")
```

##### stringReplace

Replace occurrences of a substring with a new string.

```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")
```
```server-rust
operator::string_replace("old", "new")
```

#### Date

Adjust time-based fields for lifecycle and scheduling logic.

##### dateAddDays

Add a specified number of days to a date field.

```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)
```
```server-rust
operator::date_add_days(7)
```

##### dateSubDays

Subtract a specified number of days from a date field.

```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)
```
```server-rust
operator::date_sub_days(3)
```

##### dateSetNow

Set a date field to the current time on the server.

```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()
```
```server-rust
operator::date_set_now()
```

#### Boolean

Toggle boolean values in place.

##### toggle

Toggle a boolean field between true and false.

```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()
```
```server-rust
operator::toggle()
```

### 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.

```client-web
import { Client, TablesDB, Operator } from "appwrite";

const client = new Client()
  .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
  .setProject('<YOUR_PROJECT_ID>');

const tablesDB = new TablesDB(client);

await tablesDB.updateRow({
  databaseId: "<DATABASE_ID>",
  tableId: "<TABLE_ID>",
  rowId: "<ROW_ID>",
  data: {
    upvotes: Operator.increment(1)
  }
});
```
```server-nodejs
const sdk = require('node-appwrite');

const client = new sdk.Client();
client
  .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
  .setProject('<YOUR_PROJECT_ID>')
  .setKey('<YOUR_API_KEY>');

const tablesDB = new sdk.TablesDB(client);

const result = await tablesDB.updateRow({
  databaseId: '<DATABASE_ID>',
  tableId: '<TABLE_ID>',
  rowId: '<ROW_ID>',
  data: {
    upvotes: sdk.Operator.increment(1)
  }
});
```
```client-flutter
import 'package:appwrite/appwrite.dart';

void main() async {
  final client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<YOUR_PROJECT_ID>');

  final tablesDB = TablesDB(client);

  try {
    await tablesDB.updateRow(
        '<DATABASE_ID>',
        '<TABLE_ID>',
        '<ROW_ID>',
        {
            '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://<REGION>.cloud.appwrite.io/v1")
    .setProject("<YOUR_PROJECT_ID>")

  let tablesDB = TablesDB(client)

  do {
    _ = try await tablesDB.updateRow(
      databaseId: "<DATABASE_ID>",
      tableId: "<TABLE_ID>",
      rowId: "<ROW_ID>",
      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://<REGION>.cloud.appwrite.io/v1")
    .setProject("<YOUR_PROJECT_ID>")

  val tablesDB = TablesDB(client)

  tablesDB.updateRow(
    databaseId = "<DATABASE_ID>",
    tableId = "<TABLE_ID>",
    rowId = "<ROW_ID>",
    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://<REGION>.cloud.appwrite.io/v1"),
    appwrite.WithProject("<YOUR_PROJECT_ID>"),
    appwrite.WithKey("<YOUR_API_KEY>"),
  )

  tablesDB := appwrite.NewTablesDB(client)

  _, err := tablesDB.UpdateRow(
    "<DATABASE_ID>",
    "<TABLE_ID>",
    "<ROW_ID>",
    tablesDB.WithUpdateRowData(map[string]any{
      "upvotes": operator.Increment(1),
    }),
  )
  if err != nil {
    log.Fatal(err)
  }
}
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\TablesDB;
use Appwrite\Operator;

$client = (new Client())
  ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
  ->setProject('<YOUR_PROJECT_ID>')
  ->setKey('<YOUR_API_KEY>');

$tablesDB = new TablesDB($client);

$result = $tablesDB->updateRow(
  '<DATABASE_ID>',
  '<TABLE_ID>',
  '<ROW_ID>',
  [ '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://<REGION>.cloud.appwrite.io/v1')
  .set_project('<YOUR_PROJECT_ID>')
  .set_key('<YOUR_API_KEY>'))

tablesDB = TablesDB(client)

result = tablesDB.update_row(
    database_id='<DATABASE_ID>',
    table_id='<TABLE_ID>',
    row_id='<ROW_ID>',
    data={ 'upvotes': Operator.increment(1) }
)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

var client = new Client()
  .SetEndpoint("https://<REGION>.cloud.appwrite.io/v1")
  .SetProject("<YOUR_PROJECT_ID>")
  .SetKey("<YOUR_API_KEY>");

var tablesDB = new TablesDB(client);

await tablesDB.UpdateRow(
  databaseId: "<DATABASE_ID>",
  tableId: "<TABLE_ID>",
  rowId: "<ROW_ID>",
  data: new Dictionary<string, object>
  {
    { "upvotes", Operator.Increment(1) }
  }
);
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new()

client
  .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
  .set_project('<YOUR_PROJECT_ID>')
  .set_key('<YOUR_API_KEY>')

tablesDB = TablesDB.new(client)

result = tablesDB.update_row(
    database_id: '<DATABASE_ID>',
    table_id: '<TABLE_ID>',
    row_id: '<ROW_ID>',
    data: { '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://<REGION>.cloud.appwrite.io/v1")
  .setProject("<YOUR_PROJECT_ID>")
  .setKey("<YOUR_API_KEY>");

TablesDB tablesDB = new TablesDB(client);

tablesDB.updateRow(
  "<DATABASE_ID>",
  "<TABLE_ID>",
  "<ROW_ID>",
  Map.of("upvotes", Operator.increment(1)),
  new CoroutineCallback<>((result, error) -> {
    if (error != null) {
      error.printStackTrace();
      return;
    }
    System.out.println(result);
  })
);
```
```server-rust
use appwrite::Client;
use appwrite::services::tables_db::TablesDB;
use appwrite::operator;
use serde_json::json;

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

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

    let result = tables_db.update_row(
        "<DATABASE_ID>",
        "<TABLE_ID>",
        "<ROW_ID>",
        Some(json!({
            "upvotes": operator::increment_by(1)
        })),
        None,
        None,
    ).await?;

    Ok(())
}
```

#### Add a book to a list

This example demonstrates using the `arrayAppend` operator to add a new book to an existing array of books.

```client-web
import { Client, TablesDB, Operator } from "appwrite";

const client = new Client()
  .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
  .setProject('<YOUR_PROJECT_ID>');

const tablesDB = new TablesDB(client);

await tablesDB.updateRow({
  databaseId: "<DATABASE_ID>",
  tableId: "<TABLE_ID>",
  rowId: "<ROW_ID>",
  data: {
    books: Operator.arrayAppend(['The Great Gatsby'])
  }
});
```
```server-nodejs
const sdk = require('node-appwrite');

const client = new sdk.Client();
client
  .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
  .setProject('<YOUR_PROJECT_ID>')
  .setKey('<YOUR_API_KEY>');

const tablesDB = new sdk.TablesDB(client);

const result = await tablesDB.updateRow({
  databaseId: '<DATABASE_ID>',
  tableId: '<TABLE_ID>',
  rowId: '<ROW_ID>',
  data: {
    books: sdk.Operator.arrayAppend(['The Great Gatsby'])
  }
});
```
```client-flutter
import 'package:appwrite/appwrite.dart';

void main() async {
  final client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<YOUR_PROJECT_ID>');

  final tablesDB = TablesDB(client);

  try {
    await tablesDB.updateRow(
        '<DATABASE_ID>',
        '<TABLE_ID>',
        '<ROW_ID>',
        {
            '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://<REGION>.cloud.appwrite.io/v1")
    .setProject("<YOUR_PROJECT_ID>")

  let tablesDB = TablesDB(client)

  do {
    _ = try await tablesDB.updateRow(
      databaseId: "<DATABASE_ID>",
      tableId: "<TABLE_ID>",
      rowId: "<ROW_ID>",
      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://<REGION>.cloud.appwrite.io/v1")
    .setProject("<YOUR_PROJECT_ID>")

  val tablesDB = TablesDB(client)

  tablesDB.updateRow(
    databaseId = "<DATABASE_ID>",
    tableId = "<TABLE_ID>",
    rowId = "<ROW_ID>",
    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://<REGION>.cloud.appwrite.io/v1"),
    appwrite.WithProject("<YOUR_PROJECT_ID>"),
    appwrite.WithKey("<YOUR_API_KEY>"),
  )

  tablesDB := appwrite.NewTablesDB(client)

  _, err := tablesDB.UpdateRow(
    "<DATABASE_ID>",
    "<TABLE_ID>",
    "<ROW_ID>",
    tablesDB.WithUpdateRowData(map[string]any{
      "books": operator.ArrayAppend([]string{"The Great Gatsby"}),
    }),
  )
  if err != nil {
    log.Fatal(err)
  }
}
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\TablesDB;
use Appwrite\Operator;

$client = (new Client())
  ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
  ->setProject('<YOUR_PROJECT_ID>')
  ->setKey('<YOUR_API_KEY>');

$tablesDB = new TablesDB($client);

$result = $tablesDB->updateRow(
  '<DATABASE_ID>',
  '<TABLE_ID>',
  '<ROW_ID>',
  [ '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://<REGION>.cloud.appwrite.io/v1')
  .set_project('<YOUR_PROJECT_ID>')
  .set_key('<YOUR_API_KEY>'))

tablesDB = TablesDB(client)

result = tablesDB.update_row(
    database_id='<DATABASE_ID>',
    table_id='<TABLE_ID>',
    row_id='<ROW_ID>',
    data={ 'books': Operator.arrayAppend(['The Great Gatsby']) }
)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

var client = new Client()
  .SetEndpoint("https://<REGION>.cloud.appwrite.io/v1")
  .SetProject("<YOUR_PROJECT_ID>")
  .SetKey("<YOUR_API_KEY>");

var tablesDB = new TablesDB(client);

await tablesDB.UpdateRow(
  databaseId: "<DATABASE_ID>",
  tableId: "<TABLE_ID>",
  rowId: "<ROW_ID>",
  data: new Dictionary<string, object>
  {
    { "books", Operator.ArrayAppend(new[] { "The Great Gatsby" }) }
  }
);
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new()

client
  .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
  .set_project('<YOUR_PROJECT_ID>')
  .set_key('<YOUR_API_KEY>')

tablesDB = TablesDB.new(client)

result = tablesDB.update_row(
    database_id: '<DATABASE_ID>',
    table_id: '<TABLE_ID>',
    row_id: '<ROW_ID>',
    data: { '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://<REGION>.cloud.appwrite.io/v1")
  .setProject("<YOUR_PROJECT_ID>")
  .setKey("<YOUR_API_KEY>");

TablesDB tablesDB = new TablesDB(client);

tablesDB.updateRow(
  "<DATABASE_ID>",
  "<TABLE_ID>",
  "<ROW_ID>",
  Map.of("books", Operator.arrayAppend(List.of("The Great Gatsby"))),
  new CoroutineCallback<>((result, error) -> {
    if (error != null) {
      error.printStackTrace();
      return;
    }
    System.out.println(result);
  })
);
```
```server-rust
use appwrite::Client;
use appwrite::services::tables_db::TablesDB;
use appwrite::operator;
use serde_json::json;

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

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

    let result = tables_db.update_row(
        "<DATABASE_ID>",
        "<TABLE_ID>",
        "<ROW_ID>",
        Some(json!({
            "books": operator::array_append(&["The Great Gatsby"])
        })),
        None,
        None,
    ).await?;

    Ok(())
}
```

#### 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.

```client-web
import { Client, TablesDB, Operator } from "appwrite";

const client = new Client()
  .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
  .setProject('<YOUR_PROJECT_ID>');

const tablesDB = new TablesDB(client);

await tablesDB.updateRow({
  databaseId: "<DATABASE_ID>",
  tableId: "<TABLE_ID>",
  rowId: "<ROW_ID>",
  data: {
    scheduledDeletion: Operator.dateAddDays(30)
  }
});
```
```server-nodejs
const sdk = require('node-appwrite');

const client = new sdk.Client();
client
  .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
  .setProject('<YOUR_PROJECT_ID>')
  .setKey('<YOUR_API_KEY>');

const tablesDB = new sdk.TablesDB(client);

const result = await tablesDB.updateRow({
  databaseId: '<DATABASE_ID>',
  tableId: '<TABLE_ID>',
  rowId: '<ROW_ID>',
  data: {
    scheduledDeletion: sdk.Operator.dateAddDays(30)
  }
});
```
```client-flutter
import 'package:appwrite/appwrite.dart';

void main() async {
  final client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<YOUR_PROJECT_ID>');

  final tablesDB = TablesDB(client);

  try {
    await tablesDB.updateRow(
        '<DATABASE_ID>',
        '<TABLE_ID>',
        '<ROW_ID>',
        {
            '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://<REGION>.cloud.appwrite.io/v1")
    .setProject("<YOUR_PROJECT_ID>")

  let tablesDB = TablesDB(client)

  do {
    _ = try await tablesDB.updateRow(
      databaseId: "<DATABASE_ID>",
      tableId: "<TABLE_ID>",
      rowId: "<ROW_ID>",
      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://<REGION>.cloud.appwrite.io/v1")
    .setProject("<YOUR_PROJECT_ID>")

  val tablesDB = TablesDB(client)

  tablesDB.updateRow(
    databaseId = "<DATABASE_ID>",
    tableId = "<TABLE_ID>",
    rowId = "<ROW_ID>",
    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://<REGION>.cloud.appwrite.io/v1"),
    appwrite.WithProject("<YOUR_PROJECT_ID>"),
    appwrite.WithKey("<YOUR_API_KEY>"),
  )

  tablesDB := appwrite.NewTablesDB(client)

  _, err := tablesDB.UpdateRow(
    "<DATABASE_ID>",
    "<TABLE_ID>",
    "<ROW_ID>",
    tablesDB.WithUpdateRowData(map[string]any{
      "scheduledDeletion": operator.DateAddDays(30),
    }),
  )
  if err != nil {
    log.Fatal(err)
  }
}
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\TablesDB;
use Appwrite\Operator;

$client = (new Client())
  ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
  ->setProject('<YOUR_PROJECT_ID>')
  ->setKey('<YOUR_API_KEY>');

$tablesDB = new TablesDB($client);

$result = $tablesDB->updateRow(
  '<DATABASE_ID>',
  '<TABLE_ID>',
  '<ROW_ID>',
  [ '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://<REGION>.cloud.appwrite.io/v1')
  .set_project('<YOUR_PROJECT_ID>')
  .set_key('<YOUR_API_KEY>'))

tablesDB = TablesDB(client)

result = tablesDB.update_row(
    database_id='<DATABASE_ID>',
    table_id='<TABLE_ID>',
    row_id='<ROW_ID>',
    data={ 'scheduledDeletion': Operator.dateAddDays(30) }
)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

var client = new Client()
  .SetEndpoint("https://<REGION>.cloud.appwrite.io/v1")
  .SetProject("<YOUR_PROJECT_ID>")
  .SetKey("<YOUR_API_KEY>");

var tablesDB = new TablesDB(client);

await tablesDB.UpdateRow(
  databaseId: "<DATABASE_ID>",
  tableId: "<TABLE_ID>",
  rowId: "<ROW_ID>",
  data: new Dictionary<string, object>
  {
    { "scheduledDeletion", Operator.DateAddDays(30) }
  }
);
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new()

client
  .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
  .set_project('<YOUR_PROJECT_ID>')
  .set_key('<YOUR_API_KEY>')

tablesDB = TablesDB.new(client)

result = tablesDB.update_row(
    database_id: '<DATABASE_ID>',
    table_id: '<TABLE_ID>',
    row_id: '<ROW_ID>',
    data: { '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://<REGION>.cloud.appwrite.io/v1")
  .setProject("<YOUR_PROJECT_ID>")
  .setKey("<YOUR_API_KEY>");

TablesDB tablesDB = new TablesDB(client);

tablesDB.updateRow(
  "<DATABASE_ID>",
  "<TABLE_ID>",
  "<ROW_ID>",
  Map.of("scheduledDeletion", Operator.dateAddDays(30)),
  new CoroutineCallback<>((result, error) -> {
    if (error != null) {
      error.printStackTrace();
      return;
    }
    System.out.println(result);
  })
);
```
```server-rust
use appwrite::Client;
use appwrite::services::tables_db::TablesDB;
use appwrite::operator;
use serde_json::json;

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

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

    let result = tables_db.update_row(
        "<DATABASE_ID>",
        "<TABLE_ID>",
        "<ROW_ID>",
        Some(json!({
            "scheduledDeletion": operator::date_add_days(30)
        })),
        None,
        None,
    ).await?;

    Ok(())
}
```

#### Update a single row in a transaction

This example demonstrates combining multiple operators (`increment` and `dateSetNow`) in a single transaction to ensure atomic updates.

```client-web
import { Client, TablesDB, Operator } from "appwrite";

const client = new Client()
  .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
  .setProject('<YOUR_PROJECT_ID>');

const tablesDB = new TablesDB(client);

// Create a transaction
const tx = await tablesDB.createTransaction();

// Update row with operators inside the transaction
await tablesDB.updateRow({
  databaseId: "<DATABASE_ID>",
  tableId: "<TABLE_ID>",
  rowId: "<ROW_ID>",
  data: {
    upvotes: Operator.increment(1),
    lastModified: Operator.dateSetNow()
  },
  transactionId: tx.$id
});

// Commit the transaction
await tablesDB.updateTransaction({
  transactionId: tx.$id,
  commit: true
});
```
```server-nodejs
const sdk = require('node-appwrite');

const client = new sdk.Client();
client
  .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
  .setProject('<YOUR_PROJECT_ID>')
  .setKey('<YOUR_API_KEY>');

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: '<DATABASE_ID>',
  tableId: '<TABLE_ID>',
  rowId: '<ROW_ID>',
  data: {
    upvotes: sdk.Operator.increment(1),
    lastModified: sdk.Operator.dateSetNow()
  },
  transactionId: tx.$id
});

// Commit the transaction
await tablesDB.updateTransaction({
  transactionId: tx.$id,
  commit: true
});
```
```client-flutter
import 'package:appwrite/appwrite.dart';

void main() async {
  final client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<YOUR_PROJECT_ID>');

  final tablesDB = TablesDB(client);

  try {
    // Create a transaction
    final tx = await tablesDB.createTransaction();

    // Update row with operators inside the transaction
    await tablesDB.updateRow(
        '<DATABASE_ID>',
        '<TABLE_ID>',
        '<ROW_ID>',
        {
            '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://<REGION>.cloud.appwrite.io/v1")
    .setProject("<YOUR_PROJECT_ID>")

  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: "<DATABASE_ID>",
      tableId: "<TABLE_ID>",
      rowId: "<ROW_ID>",
      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://<REGION>.cloud.appwrite.io/v1")
    .setProject("<YOUR_PROJECT_ID>")

  val tablesDB = TablesDB(client)

  // Create a transaction
  val tx = tablesDB.createTransaction()

  // Update row with operators inside the transaction
  tablesDB.updateRow(
    databaseId = "<DATABASE_ID>",
    tableId = "<TABLE_ID>",
    rowId = "<ROW_ID>",
    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://<REGION>.cloud.appwrite.io/v1"),
    appwrite.WithProject("<YOUR_PROJECT_ID>"),
    appwrite.WithKey("<YOUR_API_KEY>"),
  )

  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(
    "<DATABASE_ID>",
    "<TABLE_ID>",
    "<ROW_ID>",
    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
<?php

use Appwrite\Client;
use Appwrite\Services\TablesDB;
use Appwrite\Operator;

$client = (new Client())
  ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
  ->setProject('<YOUR_PROJECT_ID>')
  ->setKey('<YOUR_API_KEY>');

$tablesDB = new TablesDB($client);

// Create a transaction
$tx = $tablesDB->createTransaction();

// Update row with operators inside the transaction
$result = $tablesDB->updateRow(
  '<DATABASE_ID>',
  '<TABLE_ID>',
  '<ROW_ID>',
  [
    '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://<REGION>.cloud.appwrite.io/v1')
  .set_project('<YOUR_PROJECT_ID>')
  .set_key('<YOUR_API_KEY>'))

tablesDB = TablesDB(client)

### Create a transaction
tx = tablesDB.create_transaction()

### Update row with operators inside the transaction
result = tablesDB.update_row(
    database_id='<DATABASE_ID>',
    table_id='<TABLE_ID>',
    row_id='<ROW_ID>',
    data={
        'upvotes': Operator.increment(1),
        'lastModified': Operator.dateSetNow()
    },
    transaction_id=tx.id
)

### Commit the transaction
tablesDB.update_transaction(transaction_id=tx.id, commit=True)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

var client = new Client()
  .SetEndpoint("https://<REGION>.cloud.appwrite.io/v1")
  .SetProject("<YOUR_PROJECT_ID>")
  .SetKey("<YOUR_API_KEY>");

var tablesDB = new TablesDB(client);

// Create a transaction
var tx = await tablesDB.CreateTransaction();

// Update row with operators inside the transaction
await tablesDB.UpdateRow(
  databaseId: "<DATABASE_ID>",
  tableId: "<TABLE_ID>",
  rowId: "<ROW_ID>",
  data: new Dictionary<string, object>
  {
    { "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://<REGION>.cloud.appwrite.io/v1')
  .set_project('<YOUR_PROJECT_ID>')
  .set_key('<YOUR_API_KEY>')

tablesDB = TablesDB.new(client)

### Create a transaction
tx = tablesDB.create_transaction

### Update row with operators inside the transaction
result = tablesDB.update_row(
    database_id: '<DATABASE_ID>',
    table_id: '<TABLE_ID>',
    row_id: '<ROW_ID>',
    data: {
        'upvotes' => Operator.increment(1),
        'lastModified' => Operator.dateSetNow()
    },
    transaction_id: tx['$id']
)

### Commit the transaction
tablesDB.update_transaction(transaction_id: tx['$id'], commit: true)
```
```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://<REGION>.cloud.appwrite.io/v1")
  .setProject("<YOUR_PROJECT_ID>")
  .setKey("<YOUR_API_KEY>");

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(
    "<DATABASE_ID>",
    "<TABLE_ID>",
    "<ROW_ID>",
    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");
        })
      );
    })
  );
}));
```
```server-rust
use appwrite::Client;
use appwrite::services::tables_db::TablesDB;
use appwrite::operator;
use serde_json::json;

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

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

    // Create a transaction
    let tx = tables_db.create_transaction(None).await?;

    // Update row with operators inside the transaction
    let result = tables_db.update_row(
        "<DATABASE_ID>",
        "<TABLE_ID>",
        "<ROW_ID>",
        Some(json!({
            "upvotes": operator::increment_by(1),
            "lastModified": operator::date_set_now()
        })),
        None,
        Some(&tx.id),
    ).await?;

    // Commit the transaction
    tables_db.update_transaction(&tx.id, Some(true), None).await?;

    Ok(())
}
```

#### 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.

```client-web
import { Client, TablesDB, Operator } from "appwrite";

const client = new Client()
  .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
  .setProject('<YOUR_PROJECT_ID>');

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: '<DATABASE_ID>',
      tableId: '<USERS_TABLE_ID>',
      rowId: '<USER_ID>',
      data: {
        lastActivity: Operator.dateSetNow()
      }
    },
    {
      action: 'update',
      databaseId: '<DATABASE_ID>',
      tableId: '<TRANSACTIONS_TABLE_ID>',
      rowId: '<TRANSACTION_ID>',
      data: {
        amount: Operator.increment(10),
        events: Operator.arrayAppend(['credit_added'])
      }
    }
  ]
});

// Commit the transaction
await tablesDB.updateTransaction({
  transactionId: tx.$id,
  commit: true
});
```
```server-nodejs
const sdk = require('node-appwrite');

const client = new sdk.Client();
client
  .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
  .setProject('<YOUR_PROJECT_ID>')
  .setKey('<YOUR_API_KEY>');

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: '<DATABASE_ID>',
      tableId: '<USERS_TABLE_ID>',
      rowId: '<USER_ID>',
      data: {
        lastActivity: sdk.Operator.dateSetNow()
      }
    },
    {
      action: 'update',
      databaseId: '<DATABASE_ID>',
      tableId: '<TRANSACTIONS_TABLE_ID>',
      rowId: '<TRANSACTION_ID>',
      data: {
        amount: sdk.Operator.increment(10),
        events: sdk.Operator.arrayAppend(['credit_added'])
      }
    }
  ]
});

// Commit the transaction
await tablesDB.updateTransaction({
  transactionId: tx.$id,
  commit: true
});
```
```client-flutter
import 'package:appwrite/appwrite.dart';

void main() async {
  final client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<YOUR_PROJECT_ID>');

  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': '<DATABASE_ID>',
          'tableId': '<USERS_TABLE_ID>',
          'rowId': '<USER_ID>',
          'data': {
            'lastActivity': Operator.dateSetNow()
          }
        },
        {
          'action': 'update',
          'databaseId': '<DATABASE_ID>',
          'tableId': '<TRANSACTIONS_TABLE_ID>',
          'rowId': '<TRANSACTION_ID>',
          '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://<REGION>.cloud.appwrite.io/v1")
    .setProject("<YOUR_PROJECT_ID>")

  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": "<DATABASE_ID>",
          "tableId": "<USERS_TABLE_ID>",
          "rowId": "<USER_ID>",
          "data": [
            "lastActivity": Operator.dateSetNow()
          ]
        ],
        [
          "action": "update",
          "databaseId": "<DATABASE_ID>",
          "tableId": "<TRANSACTIONS_TABLE_ID>",
          "rowId": "<TRANSACTION_ID>",
          "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://<REGION>.cloud.appwrite.io/v1")
    .setProject("<YOUR_PROJECT_ID>")

  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 "<DATABASE_ID>",
        "tableId" to "<USERS_TABLE_ID>",
        "rowId" to "<USER_ID>",
        "data" to mapOf(
          "lastActivity" to Operator.dateSetNow()
        )
      ),
      mapOf(
        "action" to "update",
        "databaseId" to "<DATABASE_ID>",
        "tableId" to "<TRANSACTIONS_TABLE_ID>",
        "rowId" to "<TRANSACTION_ID>",
        "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://<REGION>.cloud.appwrite.io/v1"),
    appwrite.WithProject("<YOUR_PROJECT_ID>"),
    appwrite.WithKey("<YOUR_API_KEY>"),
  )

  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": "<DATABASE_ID>",
        "tableId": "<USERS_TABLE_ID>",
        "rowId": "<USER_ID>",
        "data": map[string]any{
          "lastActivity": operator.DateSetNow(),
        },
      },
      {
        "action": "update",
        "databaseId": "<DATABASE_ID>",
        "tableId": "<TRANSACTIONS_TABLE_ID>",
        "rowId": "<TRANSACTION_ID>",
        "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
<?php

use Appwrite\Client;
use Appwrite\Services\TablesDB;
use Appwrite\Operator;

$client = (new Client())
  ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
  ->setProject('<YOUR_PROJECT_ID>')
  ->setKey('<YOUR_API_KEY>');

$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' => '<DATABASE_ID>',
      'tableId' => '<USERS_TABLE_ID>',
      'rowId' => '<USER_ID>',
      'data' => [
        'lastActivity' => Operator::dateSetNow()
      ]
    ],
    [
      'action' => 'update',
      'databaseId' => '<DATABASE_ID>',
      'tableId' => '<TRANSACTIONS_TABLE_ID>',
      'rowId' => '<TRANSACTION_ID>',
      '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://<REGION>.cloud.appwrite.io/v1')
  .set_project('<YOUR_PROJECT_ID>')
  .set_key('<YOUR_API_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': '<DATABASE_ID>',
      'tableId': '<USERS_TABLE_ID>',
      'rowId': '<USER_ID>',
      'data': {
        'lastActivity': Operator.dateSetNow()
      }
    },
    {
      'action': 'update',
      'databaseId': '<DATABASE_ID>',
      'tableId': '<TRANSACTIONS_TABLE_ID>',
      'rowId': '<TRANSACTION_ID>',
      'data': {
        'amount': Operator.increment(10),
        'events': Operator.arrayAppend(['credit_added'])
      }
    }
  ]
)

### Commit the transaction
tablesDB.update_transaction(transaction_id=tx.id, commit=True)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

var client = new Client()
  .SetEndpoint("https://<REGION>.cloud.appwrite.io/v1")
  .SetProject("<YOUR_PROJECT_ID>")
  .SetKey("<YOUR_API_KEY>");

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<Dictionary<string, object>>
  {
    new Dictionary<string, object>
    {
      { "action", "update" },
      { "databaseId", "<DATABASE_ID>" },
      { "tableId", "<USERS_TABLE_ID>" },
      { "rowId", "<USER_ID>" },
      { "data", new Dictionary<string, object>
        {
          { "lastActivity", Operator.DateSetNow() }
        }
      }
    },
    new Dictionary<string, object>
    {
      { "action", "update" },
      { "databaseId", "<DATABASE_ID>" },
      { "tableId", "<TRANSACTIONS_TABLE_ID>" },
      { "rowId", "<TRANSACTION_ID>" },
      { "data", new Dictionary<string, object>
        {
          { "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://<REGION>.cloud.appwrite.io/v1')
  .set_project('<YOUR_PROJECT_ID>')
  .set_key('<YOUR_API_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' => '<DATABASE_ID>',
      'tableId' => '<USERS_TABLE_ID>',
      'rowId' => '<USER_ID>',
      'data' => {
        'lastActivity' => Operator.dateSetNow()
      }
    },
    {
      'action' => 'update',
      'databaseId' => '<DATABASE_ID>',
      'tableId' => '<TRANSACTIONS_TABLE_ID>',
      'rowId' => '<TRANSACTION_ID>',
      'data' => {
        'amount' => Operator.increment(10),
        'events' => Operator.arrayAppend(['credit_added'])
      }
    }
  ]
)

### Commit the transaction
tablesDB.update_transaction(transaction_id: tx['$id'], commit: true)
```
```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://<REGION>.cloud.appwrite.io/v1")
  .setProject("<YOUR_PROJECT_ID>")
  .setKey("<YOUR_API_KEY>");

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<Map<String, Object>> operations = Arrays.asList(
    Map.of(
      "action", "update",
      "databaseId", "<DATABASE_ID>",
      "tableId", "<USERS_TABLE_ID>",
      "rowId", "<USER_ID>",
      "data", Map.of(
        "lastActivity", Operator.dateSetNow()
      )
    ),
    Map.of(
      "action", "update",
      "databaseId", "<DATABASE_ID>",
      "tableId", "<TRANSACTIONS_TABLE_ID>",
      "rowId", "<TRANSACTION_ID>",
      "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");
        })
      );
    })
  );
}));
```
```server-rust
use appwrite::Client;
use appwrite::services::tables_db::TablesDB;
use appwrite::operator;
use serde_json::json;

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

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

    // Create a transaction
    let tx = tables_db.create_transaction(None).await?;

    // Stage multiple operations at once using createOperations
    tables_db.create_operations(
        &tx.id,
        Some(vec![
            json!({
                "action": "update",
                "databaseId": "<DATABASE_ID>",
                "tableId": "<USERS_TABLE_ID>",
                "rowId": "<USER_ID>",
                "data": {
                    "lastActivity": operator::date_set_now()
                }
            }),
            json!({
                "action": "update",
                "databaseId": "<DATABASE_ID>",
                "tableId": "<TRANSACTIONS_TABLE_ID>",
                "rowId": "<TRANSACTION_ID>",
                "data": {
                    "amount": operator::increment_by(10),
                    "events": operator::array_append(&["credit_added"])
                }
            }),
        ]),
    ).await?;

    // Commit the transaction
    tables_db.update_transaction(&tx.id, Some(true), None).await?;

    Ok(())
}
```

---

## Order

URL: https://appwrite.io/docs/products/databases/tablesdb/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

When querying using the [listRows](/docs/references/cloud/client-web/tablesDB#listRows) endpoint,
you can specify the order of the rows returned using the `Query.orderAsc()` and `Query.orderDesc()` query methods.

```client-web
import { Client, Query, TablesDB } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

const tablesDB = new TablesDB(client);

tablesDB.listRows({
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    queries: [
        Query.orderAsc('title'),
    ]
});
```

```client-flutter
import 'package:appwrite/appwrite.dart';

void main() async {
    final client = Client()
        .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
        .setProject('<PROJECT_ID>');

    final tablesDB = TablesDB(client);

    try {
        final rows = await tablesDB.listRows(
            databaseId: '<DATABASE_ID>',
            tableId: '<TABLE_ID>',
            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://<REGION>.cloud.appwrite.io/v1')
        .setProject('<PROJECT_ID>');

    let tablesDB = TablesDB(client)

    do {
        let rows = try await tablesDB.listRows(
            databaseId: "<DATABASE_ID>",
            tableId: "<TABLE_ID>",
            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://<REGION>.cloud.appwrite.io/v1')
        .setProject('<PROJECT_ID>');

    val tablesDB = TablesDB(client)

    try {
        val rows = tablesDB.listRows(
            databaseId = "<DATABASE_ID>",
            tableId = "<TABLE_ID>",
            queries = [
                Query.orderAsc("title")
            ]
        )
    } catch (e: AppwriteException) {
        Log.e("Appwrite", e.message)
    }
}
```

```graphql
query {
    tablesListRows(
        databaseId: "<DATABASE_ID>",
        tableId: "<TABLE_ID>"
        queries: ["orderAsc(\"title\")"]
    ) {
        total
        rows {
            _id
            data
        }
    }
}
```

### 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.

```js
// Web SDK code example for sorting based on multiple columns
// ...

// List rows and sort based on multiple columns
tablesDB.listRows({
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    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: '<DATABASE_ID>',
        tableId: '<TABLE_ID>',
        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 = "<DATABASE_ID>",
        tableId = "<TABLE_ID>",
        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: "<DATABASE_ID>",
        tableId: "<TABLE_ID>",
        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: "<DATABASE_ID>",
        tableId: "<TABLE_ID>",
        queries: ["orderAsc(\"title\")", "orderDesc(\"year\")"]
    ) {
        total
        rows {
            _id
            data
        }
    }
}
```

### Ordering by sequence

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.

```client-web
import { Client, Query, TablesDB } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

const tablesDB = new TablesDB(client);

tablesDB.listRows({
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    queries: [
        Query.orderAsc('$sequence'),
    ]
});
```

```client-flutter
import 'package:appwrite/appwrite.dart';

void main() async {
    final client = Client()
        .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
        .setProject('<PROJECT_ID>');

    final tablesDB = TablesDB(client);

    try {
        final rows = await tablesDB.listRows(
            databaseId: '<DATABASE_ID>',
            tableId: '<TABLE_ID>',
            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://<REGION>.cloud.appwrite.io/v1")
        .setProject("<PROJECT_ID>")

    let tablesDB = TablesDB(client)

    do {
        let rows = try await tablesDB.listRows(
            databaseId: "<DATABASE_ID>",
            tableId: "<TABLE_ID>",
            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://<REGION>.cloud.appwrite.io/v1")
        .setProject("<PROJECT_ID>")

    val tablesDB = TablesDB(client)

    try {
        val rows = tablesDB.listRows(
            databaseId = "<DATABASE_ID>",
            tableId = "<TABLE_ID>",
            queries = listOf(
                Query.orderAsc("\$sequence")
            )
        )
    } catch (e: AppwriteException) {
        Log.e("Appwrite", e.message)
    }
}
```

```graphql
query {
    tablesListRows(
        databaseId: "<DATABASE_ID>",
        tableId: "<TABLE_ID>"
        queries: ["orderAsc(\"$sequence\")"]
    ) {
        total
        rows {
            _id
            data
        }
    }
}
```

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

URL: https://appwrite.io/docs/products/databases/tablesdb/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 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.

```client-web
import { Client, Query, TablesDB } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

const tablesDB = new TablesDB(client);

// Page 1
const page1 = await tablesDB.listRows({
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    queries: [
        Query.limit(25),
        Query.offset(0)
    ]
});

// Page 2
const page2 = await tablesDB.listRows({
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    queries: [
        Query.limit(25),
        Query.offset(25)
    ]
});
```
```client-flutter
import 'package:appwrite/appwrite.dart';

void main() async {
    final client = Client()
        .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
        .setProject('<PROJECT_ID>');

    final tablesDB = TablesDB(client);

    final page1 = await tablesDB.listRows(
        databaseId: '<DATABASE_ID>',
        tableId: '<TABLE_ID>',
        queries: [
            Query.limit(25),
            Query.offset(0)
        ]
    );

    final page2 = await tablesDB.listRows(
        databaseId: '<DATABASE_ID>',
        tableId: '<TABLE_ID>',
        queries: [
            Query.limit(25),
            Query.offset(25)
        ]
    );
}
```
```client-apple
import Appwrite
import AppwriteModels

func main() async throws {
    let client = Client()
        .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
        .setProject("<PROJECT_ID>")

    let tablesDB = TablesDB(client)

    let page1 = try await tablesDB.listRows(
        databaseId: "<DATABASE_ID>",
        tableId: "<TABLE_ID>",
        queries: [
            Query.limit(25),
            Query.offset(0)
        ]
    )

    let page2 = try await tablesDB.listRows(
        databaseId: "<DATABASE_ID>",
        tableId: "<TABLE_ID>",
        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://<REGION>.cloud.appwrite.io/v1")
        .setProject("<PROJECT_ID>")

    val tablesDB = TablesDB(client)

    val page1 = tablesDB.listRows(
        databaseId = "<DATABASE_ID>",
        tableId = "<TABLE_ID>",
        queries = [
            Query.limit(25),
            Query.offset(0)
        ]
    )

    val page2 = tablesDB.listRows(
        databaseId = "<DATABASE_ID>",
        tableId = "<TABLE_ID>",
        queries = [
            Query.limit(25),
            Query.offset(25)
        ]
    )
}
```

**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.

### 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.

```client-web
import { Client, Query, TablesDB } from "appwrite";

const client = new Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>");

const tablesDB = new TablesDB(client);

// Page 1
const page1 = await tablesDB.listRows({
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    queries: [
        Query.limit(25),
    ]
});

const lastId = page1.rows[page1.rows.length - 1].$id;

// Page 2
const page2 = await tablesDB.listRows({
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    queries: [
        Query.limit(25),
        Query.cursorAfter(lastId),
    ]
});
```

```client-flutter
import 'package:appwrite/appwrite.dart';

void main() async {
    final client = Client()
        .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
        .setProject('<PROJECT_ID>');

    final tablesDB = TablesDB(client);

    final page1 = await tablesDB.listRows(
        databaseId: '<DATABASE_ID>',
        tableId: '<TABLE_ID>',
        queries: [
            Query.limit(25)
        ]
    );

    final lastId = page1.rows[page1.rows.length - 1].$id;

    final page2 = await tablesDB.listRows(
        databaseId: '<DATABASE_ID>',
        tableId: '<TABLE_ID>',
        queries: [
            Query.limit(25),
            Query.cursorAfter(lastId)
        ]
    );

}
```
```client-apple
import Appwrite
import AppwriteModels

func main() async throws {
    let client = Client()
      .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
      .setProject("<PROJECT_ID>")

    let tablesDB = TablesDB(client)

    let page1 = try await tablesDB.listRows(
        databaseId: "<DATABASE_ID>",
        tableId: "<TABLE_ID>",
        queries: [
            Query.limit(25)
        ]
    )

    let lastId = page1.rows[page1.rows.count - 1].$id

    let page2 = try await tablesDB.listRows(
        databaseId: "<DATABASE_ID>",
        tableId: "<TABLE_ID>",
        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://<REGION>.cloud.appwrite.io/v1")
        .setProject("<PROJECT_ID>")

    val tablesDB = TablesDB(client)

    val page1 = tablesDB.listRows(
        databaseId = "<DATABASE_ID>",
        tableId = "<TABLE_ID>",
        queries = [
            Query.limit(25)
        ]
    )

    val lastId = page1.rows[page1.rows.size - 1].$id

    val page2 = tablesDB.listRows(
        databaseId = "<DATABASE_ID>",
        tableId = "<TABLE_ID>",
        queries = [
            Query.limit(25),
            Query.cursorAfter(lastId)
        ]
    )
}
```

### When to use what?
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

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”.

```client-web
import { Client, Query, TablesDB } from "appwrite";

const client = new Client()
  .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
  .setProject('<PROJECT_ID>');

const tablesDB = new TablesDB(client);

const page = await tablesDB.listRows({
  databaseId: '<DATABASE_ID>',
  tableId: '<TABLE_ID>',
  queries: [
    Query.limit(25)
  ],
  total: false // Skip computing total count
});
```
```server-nodejs
const sdk = require('node-appwrite');

const client = new sdk.Client()
  .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
  .setProject('<PROJECT_ID>')
  .setKey('<YOUR_API_KEY>');

const tablesDB = new sdk.TablesDB(client);

const page = await tablesDB.listRows({
  databaseId: '<DATABASE_ID>',
  tableId: '<TABLE_ID>',
  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://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

tables_db = TablesDB(client)

page = tables_db.list_rows(
    database_id='<DATABASE_ID>',
    table_id='<TABLE_ID>',
    queries=[
        Query.limit(25)
    ],
    total=False  # Skip computing total count
)
```
```server-ruby
require 'appwrite'

client = Appwrite::Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

tables_db = Appwrite::TablesDB.new(client)

page = tables_db.list_rows(
    database_id: '<DATABASE_ID>',
    table_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://<REGION>.cloud.appwrite.io/v1')
  .setProject('<PROJECT_ID>')
  .setKey('<YOUR_API_KEY>');

const tablesDB = new TablesDB(client);

const page = await tablesDB.listRows({
  databaseId: '<DATABASE_ID>',
  tableId: '<TABLE_ID>',
  queries: [
    Query.limit(25)
  ],
  total: false // Skip computing total count
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Query;
use Appwrite\Services\TablesDB;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$tablesDB = new TablesDB($client);

$page = $tablesDB->listRows(
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    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://<REGION>.cloud.appwrite.io/v1")
    client.SetProject("<PROJECT_ID>")
    client.SetKey("<YOUR_API_KEY>")

    tablesDB := appwrite.NewTablesDB(client)

    page, err := tablesDB.ListRows(
        "<DATABASE_ID>",
        "<TABLE_ID>",
        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://<REGION>.cloud.appwrite.io/v1")
        .setProject("<PROJECT_ID>")
        .setKey("<YOUR_API_KEY>")

    let tablesDB = TablesDB(client)

    let page = try await tablesDB.listRows(
        databaseId: "<DATABASE_ID>",
        tableId: "<TABLE_ID>",
        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://<REGION>.cloud.appwrite.io/v1")
        .setProject("<PROJECT_ID>")
        .setKey("<YOUR_API_KEY>")

    val tablesDB = TablesDB(client)

    val page = tablesDB.listRows(
        databaseId = "<DATABASE_ID>",
        tableId = "<TABLE_ID>",
        queries = listOf(
            Query.limit(25)
        ),
        total = false // Skip computing total count
    )
}
```
```server-rust
use appwrite::Client;
use appwrite::services::tables_db::TablesDB;
use appwrite::query::Query;

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

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

    let page = tables_db.list_rows(
        "<DATABASE_ID>",
        "<TABLE_ID>",
        Some(vec![
            Query::limit(25).to_string(),
        ]),
        None,           // transaction_id
        Some(false),    // total - Skip computing total count
        None,           // ttl
    ).await?;

    println!("{:?}", page);
    Ok(())
}
```
```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://<REGION>.cloud.appwrite.io/v1")
            .setProject("<PROJECT_ID>")
            .setKey("<YOUR_API_KEY>");

        TablesDB tablesDB = new TablesDB(client);

        RowList page = tablesDB.listRows(
            "<DATABASE_ID>",
            "<TABLE_ID>",
            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://<REGION>.cloud.appwrite.io/v1')
      .setProject('<PROJECT_ID>');

  final tablesDB = TablesDB(client);

  final page = await tablesDB.listRows(
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    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://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

  let tablesDB = TablesDB(client)

  let page = try await tablesDB.listRows(
    databaseId: "<DATABASE_ID>",
    tableId: "<TABLE_ID>",
    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://<REGION>.cloud.appwrite.io/v1")
        .setProject("<PROJECT_ID>")

    val tablesDB = TablesDB(client)

    val page = tablesDB.listRows(
        databaseId = "<DATABASE_ID>",
        tableId = "<TABLE_ID>",
        queries = listOf(
            Query.limit(25)
        ),
        total = false // Skip computing total count
    )
}
```
```graphql
query {
    tablesListRows(
        databaseId: "<DATABASE_ID>",
        tableId: "<TABLE_ID>",
        queries: ["limit(25)"],
        total: false
    ) {
        total
        rows {
            _id
            data
        }
    }
}
```
```http
GET /v1/tablesdb/<DATABASE_ID>/tables/<TABLE_ID>/rows?total=false HTTP/1.1
Content-Type: application/json
X-Appwrite-Project: <PROJECT_ID>
```
```json
{
  "total": 0,
  "rows": [
    { "_id": "...", "data": { /* ... */ } }
  ]
}
```

### Cache list responses

You can cache list responses by passing a `ttl` (time-to-live) value in seconds. Subsequent identical requests return the cached result until the TTL expires. The cache is permission-aware, so users with different roles never see each other's cached data.

Set `ttl` between `1` and `86400` (24 hours). The default is `0` (caching disabled). The response includes an `X-Appwrite-Cache` header with value `hit` or `miss`. Combine with `total=false` for the best performance on paginated queries over large tables.

```client-web
import { Client, Query, TablesDB } from "appwrite";

const client = new Client()
  .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
  .setProject('<PROJECT_ID>');

const tablesDB = new TablesDB(client);

const page = await tablesDB.listRows({
  databaseId: '<DATABASE_ID>',
  tableId: '<TABLE_ID>',
  queries: [
    Query.limit(25)
  ],
  ttl: 60 // Cache for 60 seconds
});
```
```server-nodejs
const sdk = require('node-appwrite');

const client = new sdk.Client()
  .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
  .setProject('<PROJECT_ID>')
  .setKey('<YOUR_API_KEY>');

const tablesDB = new sdk.TablesDB(client);

const page = await tablesDB.listRows({
  databaseId: '<DATABASE_ID>',
  tableId: '<TABLE_ID>',
  queries: [
    sdk.Query.limit(25)
  ],
  ttl: 60 // Cache for 60 seconds
});
```
```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://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

tables_db = TablesDB(client)

page = tables_db.list_rows(
    database_id='<DATABASE_ID>',
    table_id='<TABLE_ID>',
    queries=[
        Query.limit(25)
    ],
    ttl=60  # Cache for 60 seconds
)
```
```server-ruby
require 'appwrite'

client = Appwrite::Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

tables_db = Appwrite::TablesDB.new(client)

page = tables_db.list_rows(
    database_id: '<DATABASE_ID>',
    table_id: '<TABLE_ID>',
    queries: [
        Appwrite::Query.limit(25)
    ],
    ttl: 60  # Cache for 60 seconds
)
```
```server-deno
import { Client, Query, TablesDB } from "https://deno.land/x/appwrite/mod.ts";

const client = new Client()
  .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
  .setProject('<PROJECT_ID>')
  .setKey('<YOUR_API_KEY>');

const tablesDB = new TablesDB(client);

const page = await tablesDB.listRows({
  databaseId: '<DATABASE_ID>',
  tableId: '<TABLE_ID>',
  queries: [
    Query.limit(25)
  ],
  ttl: 60 // Cache for 60 seconds
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Query;
use Appwrite\Services\TablesDB;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$tablesDB = new TablesDB($client);

$page = $tablesDB->listRows(
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    queries: [
        Query::limit(25)
    ],
    ttl: 60 // Cache for 60 seconds
);
```
```server-go
package main

import (
    "fmt"
    "github.com/appwrite/sdk-for-go/client"
    "github.com/appwrite/sdk-for-go/tablesdb"
    "github.com/appwrite/sdk-for-go/query"
)

func main() {
    clt := client.New(
        client.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"),
        client.WithProject("<PROJECT_ID>"),
        client.WithKey("<YOUR_API_KEY>"),
    )

    tablesDB := tablesdb.New(clt)

    page, err := tablesDB.ListRows(
        "<DATABASE_ID>",
        "<TABLE_ID>",
        tablesDB.WithListRowsQueries([]string{
            query.Limit(25),
        }),
        tablesDB.WithListRowsTtl(60), // Cache for 60 seconds
    )

    if err != nil {
        fmt.Println(err)
    }
    _ = page
}
```
```server-swift
import Appwrite
import AppwriteModels

func main() async throws {
    let client = Client()
        .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
        .setProject("<PROJECT_ID>")
        .setKey("<YOUR_API_KEY>")

    let tablesDB = TablesDB(client)

    let page = try await tablesDB.listRows(
        databaseId: "<DATABASE_ID>",
        tableId: "<TABLE_ID>",
        queries: [
            Query.limit(25)
        ],
        ttl: 60 // Cache for 60 seconds
    )
}
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.Query
import io.appwrite.services.TablesDB

suspend fun main() {
    val client = Client(applicationContext)
        .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
        .setProject("<PROJECT_ID>")
        .setKey("<YOUR_API_KEY>")

    val tablesDB = TablesDB(client)

    val page = tablesDB.listRows(
        databaseId = "<DATABASE_ID>",
        tableId = "<TABLE_ID>",
        queries = listOf(
            Query.limit(25)
        ),
        ttl = 60 // Cache for 60 seconds
    )
}
```
```server-rust
use appwrite::Client;
use appwrite::services::tables_db::TablesDB;
use appwrite::query::Query;

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

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

    let page = tables_db.list_rows(
        "<DATABASE_ID>",
        "<TABLE_ID>",
        Some(vec![
            Query::limit(25).to_string(),
        ]),
        None,        // transaction_id
        None,        // total
        Some(60),    // ttl - Cache for 60 seconds
    ).await?;

    println!("{:?}", page);
    Ok(())
}
```
```server-java
import io.appwrite.Client;
import io.appwrite.Query;
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("<YOUR_API_KEY>");

TablesDB tablesDB = new TablesDB(client);

tablesDB.listRows(
    "<DATABASE_ID>",
    "<TABLE_ID>",
    List.of(Query.limit(25)),
    null, // transactionId
    null, // total
    60, // ttl - Cache for 60 seconds
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }
        System.out.println(result);
    })
);
```
```client-flutter
import 'package:appwrite/appwrite.dart';

void main() async {
  final client = Client()
      .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
      .setProject('<PROJECT_ID>');

  final tablesDB = TablesDB(client);

  final page = await tablesDB.listRows(
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    queries: [
      Query.limit(25)
    ],
    ttl: 60, // Cache for 60 seconds
  );
}
```
```client-apple
import Appwrite
import AppwriteModels

func main() async throws {
  let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

  let tablesDB = TablesDB(client)

  let page = try await tablesDB.listRows(
    databaseId: "<DATABASE_ID>",
    tableId: "<TABLE_ID>",
    queries: [
      Query.limit(25)
    ],
    ttl: 60 // Cache for 60 seconds
  )
}
```
```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://<REGION>.cloud.appwrite.io/v1")
        .setProject("<PROJECT_ID>")

    val tablesDB = TablesDB(client)

    val page = tablesDB.listRows(
        databaseId = "<DATABASE_ID>",
        tableId = "<TABLE_ID>",
        queries = listOf(
            Query.limit(25)
        ),
        ttl = 60 // Cache for 60 seconds
    )
}
```
```graphql
query {
    tablesListRows(
        databaseId: "<DATABASE_ID>",
        tableId: "<TABLE_ID>",
        queries: ["limit(25)"],
        ttl: 60
    ) {
        total
        rows {
            _id
            data
        }
    }
}
```
```http
GET /v1/tablesdb/<DATABASE_ID>/tables/<TABLE_ID>/rows?ttl=60 HTTP/1.1
Content-Type: application/json
X-Appwrite-Project: <PROJECT_ID>
```

Row writes do **not** invalidate the cache, so cached responses may contain stale data until the TTL expires. Schema changes invalidate cached entries automatically. To force an immediate purge, see [Rows: Purge cache](/docs/products/databases/rows#purge-cache).

---

## Database permissions

URL: https://appwrite.io/docs/products/databases/tablesdb/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 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/security/permissions)

### 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/security/permissions)

### 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/security/permissions#examples) in our platform documentation.

---

## Queries

URL: https://appwrite.io/docs/products/databases/tablesdb/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

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

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`.

**Default pagination behavior**

By default, results are limited to the **first 25 items**.
You can change this through [pagination](/docs/products/databases/pagination).

```client-web
import { Client, Query, TablesDB } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

const tablesDB = new TablesDB(client);

tablesDB.listRows({
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    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://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>')
;

const promise = tablesDB.listRows({
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    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://<REGION>.cloud.appwrite.io/v1')
        .setProject('<PROJECT_ID>');

    final tablesDB = TablesDB(client);

    try {
        final rows = await tablesDB.listRows(
            '<DATABASE_ID>',
            '<TABLE_ID>',
            [
                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://<REGION>.cloud.appwrite.io/v1")
        .setProject("<PROJECT_ID>")

    let tablesDB = TablesDB(client)

    do {
        let rows = try await tablesDB.listRows(
            databaseId: "<DATABASE_ID>",
            tableId: "<TABLE_ID>",
            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://<REGION>.cloud.appwrite.io/v1')
        .setProject('<PROJECT_ID>');

    val tablesDB = TablesDB(client)

    try {
        val rows = tablesDB.listRows(
            databaseId = "<DATABASE_ID>",
            tableId = "<TABLE_ID>",
            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://<REGION>.cloud.appwrite.io/v1"),
        appwrite.WithProject("<PROJECT_ID>"),
        appwrite.WithKey("<API_KEY>"),
    )

    tablesDB := appwrite.NewTablesDB(client)

    rows, err := tablesDB.ListRows(
        "<DATABASE_ID>",
        "<TABLE_ID>",
        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-rust
use appwrite::Client;
use appwrite::services::tables_db::TablesDB;
use appwrite::query::Query;
use serde_json::Value;

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

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

    let rows = tables_db.list_rows(
        "<DATABASE_ID>",
        "<TABLE_ID>",
        Some(vec![
            Query::equal("title", Value::Array(vec![
                Value::String("Avatar".to_string()),
                Value::String("Lord of the Rings".to_string()),
            ])).to_string(),
            Query::greater_than("year", 1999).to_string(),
        ]),
        None,
        None,
        None,
    ).await?;

    println!("{:?}", rows);
    Ok(())
}
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Query;
use Appwrite\Services\TablesDB;

$client = new Client();

$client
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>')
;

$tablesDB = new TablesDB($client);

$result = $tables->listRows(
    '<DATABASE_ID>',
    '<TABLE_ID>',
    [
        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://<REGION>.cloud.appwrite.io/v1')
  .set_project('<PROJECT_ID>')
  .set_key('<YOUR_API_KEY>')
)

tablesDB = TablesDB(client)

result = tablesDB.list_rows(
    database_id='<DATABASE_ID>',
    table_id='<TABLE_ID>',
    queries=[
        Query.equal('title', ['Avatar', 'Lord of the Rings']),
        Query.greater_than('year', 1999)
    ]
)
```
```graphql
query {
    tablesListRows(
        databaseId: "<DATABASE_ID>",
        tableId: "<TABLE_ID>"
        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/<DATABASE_ID>/tables/<TABLE_ID>/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: <PROJECT_ID>
```

### Query operators

#### 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.

```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"})
```
```server-rust
Query::select(vec!["name", "title"]).to_string()
```
```http
{"method":"select","values":["name","title"]}
```

##### Select relationship data

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:

```client-web
const row = await tablesDB.getRow({
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    rowId: '<ROW_ID>',
    queries: [Query.select(['name', 'age'])]
});
```
```client-flutter
final row = await tablesDB.getRow(
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    rowId: '<ROW_ID>',
    queries: [Query.select(["name", "age"])]
);
```
```server-python
row = tablesDB.get_row(
    database_id='<DATABASE_ID>',
    table_id='<TABLE_ID>',
    row_id='<ROW_ID>',
    queries=[Query.select(["name", "age"])]
)
```
```server-ruby
row = tablesDB.get_row(
    database_id: '<DATABASE_ID>',
    table_id: '<TABLE_ID>',
    row_id: '<ROW_ID>',
    queries: [Query.select(["name", "age"])]
)
```
```server-nodejs
const row = await tablesDB.getRow({
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    rowId: '<ROW_ID>',
    queries: [Query.select(['name', 'age'])]
});
```
```server-php
$row = $tablesDB->getRow(
    '<DATABASE_ID>', '<TABLE_ID>', '<ROW_ID>',
    [Query::select(["name", "age"])]
);
```
```server-rust
let row = tables_db.get_row(
    "<DATABASE_ID>",
    "<TABLE_ID>",
    "<ROW_ID>",
    Some(vec![Query::select(vec!["name", "age"]).to_string()]),
    None,
).await?;
```
```client-apple
let row = try await tablesDB.getRow(
    databaseId: "<DATABASE_ID>",
    tableId: "<TABLE_ID>",
    rowId: "<ROW_ID>",
    queries: [Query.select(["name", "age"])]
)
```
```http
GET /v1/tablesdb/<DATABASE_ID>/tables/<TABLE_ID>/rows/<ROW_ID>?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: <PROJECT_ID>
```

###### Load all relationship data
Use the `*` wildcard to load all fields from related rows:

```client-web
const row = await tablesDB.getRow({
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    rowId: '<ROW_ID>',
    queries: [Query.select(['*', 'reviews.*'])]
});
```
```client-flutter
final row = await tablesDB.getRow(
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    rowId: '<ROW_ID>',
    queries: [Query.select(["*", "reviews.*"])]
);
```
```server-python
row = tablesDB.get_row(
    database_id='<DATABASE_ID>',
    table_id='<TABLE_ID>',
    row_id='<ROW_ID>',
    queries=[Query.select(["*", "reviews.*"])]
)
```
```server-ruby
row = tablesDB.get_row(
    database_id: '<DATABASE_ID>',
    table_id: '<TABLE_ID>',
    row_id: '<ROW_ID>',
    queries: [Query.select(["*", "reviews.*"])]
)
```
```server-nodejs
const row = await tablesDB.getRow({
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    rowId: '<ROW_ID>',
    queries: [Query.select(["*", "reviews.*"])]
});
```
```server-php
$row = $tablesDB->getRow(
    '<DATABASE_ID>', '<TABLE_ID>', '<ROW_ID>',
    [Query::select(["*", "reviews.*"])]
);
```
```server-rust
let row = tables_db.get_row(
    "<DATABASE_ID>",
    "<TABLE_ID>",
    "<ROW_ID>",
    Some(vec![Query::select(vec!["*", "reviews.*"]).to_string()]),
    None,
).await?;
```
```client-apple
let row = try await tablesDB.getRow(
    databaseId: "<DATABASE_ID>",
    tableId: "<TABLE_ID>",
    rowId: "<ROW_ID>",
    queries: [Query.select(["*", "reviews.*"])]
)
```
```http
GET /v1/tablesdb/<DATABASE_ID>/tables/<TABLE_ID>/rows/<ROW_ID>?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: <PROJECT_ID>
{"method":"select","values":["*","reviews.*"]}
```

###### Select specific relationship fields
For precise control, select only specific fields from related rows:

```client-web
const row = await tablesDB.getRow({
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    rowId: '<ROW_ID>',
    queries: [Query.select(['name', 'age', 'reviews.author', 'reviews.rating'])]
});
```
```client-flutter
final row = await tablesDB.getRow(
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    rowId: '<ROW_ID>',
    queries: [Query.select(["name", "age", "reviews.author", "reviews.rating"])]
);
```
```server-python
row = tablesDB.get_row(
    database_id='<DATABASE_ID>',
    table_id='<TABLE_ID>',
    row_id='<ROW_ID>',
    queries=[Query.select(["name", "age", "reviews.author", "reviews.rating"])]
)
```
```server-ruby
row = tablesDB.get_row(
    database_id: '<DATABASE_ID>',
    table_id: '<TABLE_ID>',
    row_id: '<ROW_ID>',
    queries: [Query.select(["name", "age", "reviews.author", "reviews.rating"])]
)
```
```server-nodejs
const row = await tablesDB.getRow({
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    rowId: '<ROW_ID>',
    queries: [Query.select(["name", "age", "reviews.author", "reviews.rating"])]
});
// Result: { name: "John", age: 30, reviews: [{ author: "...", rating: 5 }] }
```
```server-php
$row = $tablesDB->getRow(
    '<DATABASE_ID>', '<TABLE_ID>', '<ROW_ID>',
    [Query::select(["name", "age", "reviews.author", "reviews.rating"])]
);
```
```server-rust
let row = tables_db.get_row(
    "<DATABASE_ID>",
    "<TABLE_ID>",
    "<ROW_ID>",
    Some(vec![Query::select(vec!["name", "age", "reviews.author", "reviews.rating"]).to_string()]),
    None,
).await?;
```
```client-apple
let row = try await tablesDB.getRow(
    databaseId: "<DATABASE_ID>",
    tableId: "<TABLE_ID>",
    rowId: "<ROW_ID>",
    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"]}
```

###### Load nested relationships
You can also load relationships of relationships:

```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.*"])
```
```server-rust
Query::select(vec!["*", "reviews.*", "reviews.author.*"]).to_string()
```
```client-apple
Query.select(["*", "reviews.*", "reviews.author.*"])
```
```http
{"method":"select","values":["*","reviews.*","reviews.author.*"]}
```

##### Use selection 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

**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.

**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).

#### Comparison operators

##### Equal

Returns row if column is equal to any value in the provided array. Also supported for spatial types.

```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"})
```
```server-rust
Query::equal("title", Value::Array(vec![Value::String("Iron Man".to_string())])).to_string()
```
```http
{"method":"equal","column":"title","values":["Iron Man"]}
```

##### Not equal

Returns row if column is not equal to any value in the provided array. Also supported for spatial types.

```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")
```
```server-rust
Query::not_equal("title", "Iron Man").to_string()
```
```http
{"method":"notEqual","column":"title","values":"Iron Man"}
```

##### Less than

Returns row if column is less than the provided value.

```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)
```
```server-rust
Query::less_than("score", 10).to_string()
```
```http
{"method":"lessThan","column":"score","values":[10]}
```

##### Less than or equal

Returns row if column is less than or equal to the provided value.

```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)
```
```server-rust
Query::less_than_equal("score", 10).to_string()
```
```http
{"method":"lessThanEqual","column":"score","values":[10]}
```

##### Greater than

Returns row if column is greater than the provided value.

```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)
```
```server-rust
Query::greater_than("score", 10).to_string()
```
```http
{"method":"greaterThan","column":"score","values":[10]}
```

##### Greater than or equal

Returns row if column is greater than or equal to the provided value.

```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)
```
```server-rust
Query::greater_than_equal("score", 10).to_string()
```
```http
{"method":"greaterThanEqual","column":"score","values":[10]}
```

##### Between

Returns row if column value falls between the two values. The boundary values are inclusive and can be strings or numbers.

```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)
```
```server-rust
Query::between("price", 5, 10).to_string()
```
```http
{"method":"between","column":"price","values":[5,10]}
```

##### 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.

```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)
```
```server-rust
Query::not_between("price", 5, 10).to_string()
```
```http
{"method":"notBetween","column":"price","values":[5,10]}
```

#### Null checks

##### Is null

Returns rows where column value is null.

```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")
```
```server-rust
Query::is_null("name").to_string()
```
```http
{"method":"isNull","column":"name"}
```

##### Is not null

Returns rows where column value is **not** null.

```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")
```
```server-rust
Query::is_not_null("name").to_string()
```
```http
{"method":"isNotNull","column":"name"}
```

#### Text operations

##### Starts with

Returns rows if a text column starts with a substring.

```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")
```
```server-rust
Query::starts_with("name", "Once upon a time").to_string()
```
```http
{"method":"startsWith","column":"name","values":["Once upon a time"]}
```

##### Not starts with

Returns rows if a text column does not start with a substring.

```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")
```
```server-rust
Query::not_starts_with("name", "Once upon a time").to_string()
```
```http
{"method":"notStartsWith","column":"name","values":["Once upon a time"]}
```

##### Ends with

Returns rows if a text column ends with a substring.

```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.")
```
```server-rust
Query::ends_with("name", "happily ever after.").to_string()
```
```http
{"method":"endsWith","column":"name","values":["happily ever after."]}
```

##### Not ends with

Returns rows if a text column does not end with a substring.

```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.")
```
```server-rust
Query::not_ends_with("name", "happily ever after.").to_string()
```
```http
{"method":"notEndsWith","column":"name","values":["happily ever after."]}
```

##### Contains

Returns rows if the array column contains the specified elements or if a text column contains the specified substring. Also supported for spatial types.

```client-web
// For arrays
Query.contains("ingredients", ['apple', 'banana'])

// For text columns
Query.contains("name", "Tom")
```
```client-flutter
// For arrays
Query.contains("ingredients", ['apple', 'banana'])

// For text columns
Query.contains("name", "Tom")
```
```server-python
### For arrays
Query.contains("ingredients", ['apple', 'banana'])

### For text columns
Query.contains("name", "Tom")
```
```server-ruby
### For arrays
Query.contains("ingredients", ['apple', 'banana'])

### For text columns
Query.contains("name", "Tom")
```
```server-deno
// For arrays
Query.contains("ingredients", ['apple', 'banana'])

// For text columns
Query.contains("name", "Tom")
```
```server-php
// For arrays
Query::contains("ingredients", ['apple', 'banana'])

// For text columns
Query::contains("name", "Tom")
```
```client-apple
// For arrays
Query.contains("ingredients", value: ["apple", "banana"])

// For text columns
Query.contains("name", value: "Tom")
```server-go
// For arrays
query.Contains("ingredients", []string{"apple", "banana"})

// For text columns
query.Contains("name", "Tom")
```
```server-rust
// For arrays
Query::contains("ingredients", Value::Array(vec![
    Value::String("apple".to_string()),
    Value::String("banana".to_string()),
])).to_string()

// For text columns
Query::contains("name", "Tom").to_string()
```
```http
### For arrays
{"method":"contains","column":"ingredients","values":["apple","banana"]}

### For text columns
{"method":"contains","column":"name","values":["Tom"]}
```

##### Not contains

Returns rows if the array column does not contain the specified
elements, or if a text column does not contain the specified
substring. Also supported for spatial types.

```client-web
// For arrays
Query.notContains("ingredients", ['apple', 'banana'])

// For text columns
Query.notContains("name", "Tom")
```
```client-flutter
// For arrays
Query.notContains("ingredients", ['apple', 'banana'])

// For text columns
Query.notContains("name", "Tom")
```
```client-react-native
// For arrays
Query.notContains("ingredients", ['apple', 'banana'])

// For text columns
Query.notContains("name", "Tom")
```
```client-apple
// For arrays
Query.notContains("ingredients", value: ['apple', 'banana'])

// For text columns
Query.notContains("name", value: "Tom")
```
```client-android-kotlin
// For arrays
Query.notContains("ingredients", ['apple', 'banana'])

// For text columns
Query.notContains("name", "Tom")
```
```client-android-java
// For arrays
Query.notContains("ingredients", Arrays.asList("apple", "banana"))

// For text columns
Query.notContains("name", "Tom")
```
```server-python
### For arrays
Query.not_contains("ingredients", ['apple', 'banana'])

### For text columns
Query.not_contains("name", "Tom")
```
```server-ruby
### For arrays
Query.not_contains("ingredients", ['apple', 'banana'])

### For text columns
Query.not_contains("name", "Tom")
```
```server-deno
// For arrays
Query.notContains("ingredients", ['apple', 'banana'])

// For text columns
Query.notContains("name", "Tom")
```
```server-nodejs
// For arrays
Query.notContains("ingredients", ['apple', 'banana'])

// For text columns
Query.notContains("name", "Tom")
```
```server-php
// For arrays
Query::notContains("ingredients", ['apple', 'banana'])

// For text columns
Query::notContains("name", "Tom")
```
```server-dotnet
// For arrays
Query.NotContains("ingredients", new List<string> { "apple", "banana" })

// For text columns
Query.NotContains("name", "Tom")
```
```server-go
// For arrays
query.NotContains("ingredients", []string{"apple", "banana"})

// For text columns
query.NotContains("name", "Tom")
```server-dart
// For arrays
Query.notContains("ingredients", ['apple', 'banana'])

// For text columns
Query.notContains("name", "Tom")
```
```server-swift
// For arrays
Query.notContains("ingredients", value: ['apple', 'banana'])

// For text columns
Query.notContains("name", value: "Tom")
```
```server-kotlin
// For arrays
Query.notContains("ingredients", listOf("apple", "banana"))

// For text columns
Query.notContains("name", "Tom")
```
```server-rust
// For arrays
Query::not_contains("ingredients", Value::Array(vec![
    Value::String("apple".to_string()),
    Value::String("banana".to_string()),
])).to_string()

// For text columns
Query::not_contains("name", "Tom").to_string()
```
```http
### For arrays
{"method":"notContains","column":"ingredients","values":["apple","banana"]}

### For text columns
{"method":"notContains","column":"name","values":["Tom"]}
```

##### Search

Searches text columns for provided keywords. Requires a [full-text index](/docs/products/databases/tables#indexes) on queried columns. The search string must be at least **3 characters** to perform a search.

**Searching values with hyphens**

The hyphen (`-`) is treated as a stop character by the underlying search engine. To search for exact values that contain hyphens (for example, ticket or SKU codes like `SWT-2621-44`), wrap the value in quotes inside the search string: `Query.search(column, '"SWT-2621-44"')`.

```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")
```
```server-rust
Query::search("text", "key words").to_string()
```
```http
{"method":"search","column":"text","values":["key words"]}
```

##### Not search

Returns rows if a text column does not match the full-text search
query. Requires a [full-text index](/docs/products/databases/tables#indexes)
on queried columns. The search string must be at least **3 characters** to perform a search.

**Searching values with hyphens**

The hyphen (`-`) is treated as a stop character by the underlying search engine. To exclude exact values that contain hyphens (for example, ticket or SKU codes like `SWT-2621-44`), wrap the value in quotes inside the search string: `Query.notSearch(column, '"SWT-2621-44"')`.

```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")
```
```server-rust
Query::not_search("text", "key words").to_string()
```
```http
{"method":"notSearch","column":"text","values":["key words"]}
```

#### Logical operators

##### AND

Returns row if it matches all of the nested sub-queries in the array passed in.

```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),
})
```
```server-rust
Query::and(vec![
    Query::less_than("size", 10).to_string(),
    Query::greater_than("size", 5).to_string(),
]).to_string()
```
```http
{"method":"and","values":[{"method":"lessThan","column":"size","values":[10]},{"method":"greaterThan","column":"size","values":[5]}]}
```

##### OR

Returns row if it matches any of the nested sub-queries in the array passed in.

```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),
})
```
```server-rust
Query::or(vec![
    Query::less_than("size", 5).to_string(),
    Query::greater_than("size", 10).to_string(),
]).to_string()
```
```http
{"method":"or","values":[{"method":"lessThan","column":"size","values":[5]},{"method":"greaterThan","column":"size","values":[10]}]}
```

#### Ordering

##### Order descending

Orders results in descending order by column. Column must be indexed.

```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("column")
```
```server-rust
Query::order_desc("column").to_string()
```
```http
{"method":"orderDesc","column":"column"}
```

##### Order ascending

Orders results in ascending order by column. Column must be indexed.

```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("column")
```
```server-rust
Query::order_asc("column").to_string()
```
```http
{"method":"orderAsc","column":"column"}
```

##### Order random

Orders results in random order.

```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()
```
```server-rust
Query::order_random().to_string()
```
```http
{"method":"orderRandom"}
```

#### Pagination

##### Limit

Limits the number of results returned by the query. Used for [pagination](/docs/products/databases/pagination).

```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)
```
```server-rust
Query::limit(25).to_string()
```
```http
{"method":"limit","values":[25]}
```

##### Offset

Offset the results returned by skipping some of the results. Used for [pagination](/docs/products/databases/pagination).

```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)
```
```server-rust
Query::offset(0).to_string()
```
```http
{"method":"offset","values":[0]}
```

##### Cursor after

Places the cursor after the specified resource ID. Used for [pagination](/docs/products/databases/pagination).

```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")
```
```server-rust
Query::cursor_after("62a7...f620").to_string()
```
```http
{"method":"cursorAfter","values":["62a7...f620"]}
```

##### Cursor before

Places the cursor before the specified resource ID. Used for [pagination](/docs/products/databases/pagination).

```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")
```
```server-rust
Query::cursor_before("62a7...a600").to_string()
```
```http
{"method":"cursorBefore","values":["62a7...a600"]}
```

### 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

Returns rows created before the given date.

```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")
```
```server-rust
Query::created_before("2025-01-01T00:00:00Z").to_string()
```
```http
{"method":"createdBefore","values":["2025-01-01T00:00:00Z"]}
```

##### Created after

Returns rows created after the given date.

```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")
```
```server-rust
Query::created_after("2025-01-01T00:00:00Z").to_string()
```
```http
{"method":"createdAfter","values":["2025-01-01T00:00:00Z"]}
```

##### Updated before

Returns rows updated before the given date.

```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")
```
```server-rust
Query::updated_before("2025-01-01T00:00:00Z").to_string()
```
```http
{"method":"updatedBefore","values":["2025-01-01T00:00:00Z"]}
```

##### Updated after

Returns rows updated after the given date.

```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")
```
```server-rust
Query::updated_after("2025-01-01T00:00:00Z").to_string()
```
```http
{"method":"updatedAfter","values":["2025-01-01T00:00:00Z"]}
```

### Geo queries and spatial operations

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).

**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.

#### Distance equal

Returns rows where the spatial column is exactly the specified distance from a point.

```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<double> { -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)
```
```server-rust
Query::distance_equal(
    "location",
    Value::Array(vec![Value::from(-73.9851), Value::from(40.7589)]),
    200,
    true,
).to_string()
```
```http
{"method":"distanceEqual","column":"location","values":[[-73.9851, 40.7589], 200]}
```

#### Distance not equal

Returns rows where the spatial column is not exactly the specified distance from a point.

```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<double> { -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)
```
```server-rust
Query::distance_not_equal(
    "location",
    Value::Array(vec![Value::from(-73.9851), Value::from(40.7589)]),
    200,
    true,
).to_string()
```
```http
{"method":"distanceNotEqual","column":"location","values":[[-73.9851, 40.7589], 200]}
```

#### Distance greater than

Returns rows where the spatial column is more than the specified distance from a point.

```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<double> { -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)
```
```server-rust
Query::distance_greater_than(
    "location",
    Value::Array(vec![Value::from(-73.9851), Value::from(40.7589)]),
    200,
    true,
).to_string()
```
```http
{"method":"distanceGreaterThan","column":"location","values":[[-73.9851, 40.7589], 200]}
```

#### Distance less than

Returns rows where the spatial column is less than the specified distance from a point.

```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<double> { -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)
```
```server-rust
Query::distance_less_than(
    "location",
    Value::Array(vec![Value::from(-73.9851), Value::from(40.7589)]),
    200,
    true,
).to_string()
```
```http
{"method":"distanceLessThan","column":"location","values":[[-73.9851, 40.7589], 200]}
```

#### Intersects

Returns rows where the spatial column intersects with the provided geometry.

```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<List<double>>
{
    new List<double> { -73.9851, 40.7589 },
    new List<double> { -73.9776, 40.7614 },
    new List<double> { -73.9733, 40.7505 },
    new List<double> { -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)))
```
```server-rust
Query::intersects("area", serde_json::json!([
    [-73.9851, 40.7589],
    [-73.9776, 40.7614],
    [-73.9733, 40.7505],
    [-73.9851, 40.7589]
])).to_string()
```
```http
{"method":"intersects","column":"area","values":[[[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]]}
```

#### Not intersects

Returns rows where the spatial column does not intersect with the provided geometry.

```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<List<double>>
{
    new List<double> { -73.9851, 40.7589 },
    new List<double> { -73.9776, 40.7614 },
    new List<double> { -73.9733, 40.7505 },
    new List<double> { -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)))
```
```server-rust
Query::not_intersects("area", serde_json::json!([
    [-73.9851, 40.7589],
    [-73.9776, 40.7614],
    [-73.9733, 40.7505],
    [-73.9851, 40.7589]
])).to_string()
```
```http
{"method":"notIntersects","column":"area","values":[[[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]]}
```

#### Overlaps

Returns rows where the spatial column overlaps with the provided geometry.

```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<List<double>>
{
    new List<double> { -73.9851, 40.7589 },
    new List<double> { -73.9776, 40.7614 },
    new List<double> { -73.9733, 40.7505 },
    new List<double> { -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)))
```
```server-rust
Query::overlaps("zone", serde_json::json!([
    [-73.9851, 40.7589],
    [-73.9776, 40.7614],
    [-73.9733, 40.7505],
    [-73.9851, 40.7589]
])).to_string()
```
```http
{"method":"overlaps","column":"zone","values":[[[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]]}
```

#### Not overlaps

Returns rows where the spatial column does not overlap with the provided geometry.

```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<List<double>>
{
    new List<double> { -73.9851, 40.7589 },
    new List<double> { -73.9776, 40.7614 },
    new List<double> { -73.9733, 40.7505 },
    new List<double> { -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)))
```
```server-rust
Query::not_overlaps("zone", serde_json::json!([
    [-73.9851, 40.7589],
    [-73.9776, 40.7614],
    [-73.9733, 40.7505],
    [-73.9851, 40.7589]
])).to_string()
```
```http
{"method":"notOverlaps","column":"zone","values":[[[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]]}
```

#### Touches

Returns rows where the spatial column touches the provided geometry.

```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<List<double>>
{
    new List<double> { -73.9851, 40.7589 },
    new List<double> { -73.9776, 40.7614 },
    new List<double> { -73.9733, 40.7505 },
    new List<double> { -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)))
```
```server-rust
Query::touches("boundary", serde_json::json!([
    [-73.9851, 40.7589],
    [-73.9776, 40.7614],
    [-73.9733, 40.7505],
    [-73.9851, 40.7589]
])).to_string()
```
```http
{"method":"touches","column":"boundary","values":[[[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]]}
```

#### Not touches

Returns rows where the spatial column does not touch the provided geometry.

```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<List<double>>
{
    new List<double> { -73.9851, 40.7589 },
    new List<double> { -73.9776, 40.7614 },
    new List<double> { -73.9733, 40.7505 },
    new List<double> { -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)))
```
```server-rust
Query::not_touches("boundary", serde_json::json!([
    [-73.9851, 40.7589],
    [-73.9776, 40.7614],
    [-73.9733, 40.7505],
    [-73.9851, 40.7589]
])).to_string()
```
```http
{"method":"notTouches","column":"boundary","values":[[[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]]}
```

#### Crosses

Returns rows where the spatial column crosses the provided geometry.

```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<List<double>>
{
    new List<double> { -73.9851, 40.7589 },
    new List<double> { -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)))
```
```server-rust
Query::crosses("route", serde_json::json!([
    [-73.9851, 40.7589],
    [-73.9776, 40.7614]
])).to_string()
```
```http
{"method":"crosses","column":"route","values":[[[-73.9851, 40.7589], [-73.9776, 40.7614]]]}
```

#### Not crosses

Returns rows where the spatial column does not cross the provided geometry.

```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<List<double>>
{
    new List<double> { -73.9851, 40.7589 },
    new List<double> { -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)))
```
```server-rust
Query::not_crosses("route", serde_json::json!([
    [-73.9851, 40.7589],
    [-73.9776, 40.7614]
])).to_string()
```
```http
{"method":"notCrosses","column":"route","values":[[[-73.9851, 40.7589], [-73.9776, 40.7614]]]}
```

### 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:

```client-web
const results = await tablesDB.listRows({
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    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(
    '<DATABASE_ID>',
    '<TABLE_ID>',
    [
        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='<DATABASE_ID>',
    table_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(
    "<DATABASE_ID>",
    "<TABLE_ID>",
    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)
}
```
```server-rust
let rows = tables_db.list_rows(
    "<DATABASE_ID>",
    "<TABLE_ID>",
    Some(vec![
        Query::or(vec![
            Query::and(vec![
                Query::equal("category", Value::Array(vec![Value::String("books".to_string())])).to_string(),
                Query::less_than("price", 20).to_string(),
            ]).to_string(),
            Query::and(vec![
                Query::equal("category", Value::Array(vec![Value::String("magazines".to_string())])).to_string(),
                Query::less_than("price", 10).to_string(),
            ]).to_string(),
        ]).to_string(),
    ]),
    None,
    None,
    None,
).await?;
```
```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]}]}]}
```

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

URL: https://appwrite.io/docs/products/databases/tablesdb/quick-start

#### 1. 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.

#### 2. 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 **Text**.
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.

#### 3. Create rows

To create a row use the `createRow` method.

In the **Settings** menu, find your project ID and replace `<PROJECT_ID>` in the example.

Navigate to the `Oscar` database, copy the database ID, and replace `<DATABASE_ID>`.
Then, in the `My books` table, copy the table ID, and replace `<TABLE_ID>`.

```client-web
import { Client, ID, TablesDB } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

const tablesDB = new TablesDB(client);

const promise = tablesDB.createRow({
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    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://<REGION>.cloud.appwrite.io/v1')
        .setProject('<PROJECT_ID>');

    final tablesDB = TablesDB(client);

    try {
        final row = tablesDB.createRow(
            databaseId: '<DATABASE_ID>',
            tableId: '<TABLE_ID>',
            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://<REGION>.cloud.appwrite.io/v1")
        .setProject("<PROJECT_ID>")

    let tablesDB = TablesDB(client)

    do {
        let row = try await tablesDB.createRow(
            databaseId: "<DATABASE_ID>",
            tableId: "<TABLE_ID>",
            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://<REGION>.cloud.appwrite.io/v1")
        .setProject("<PROJECT_ID>")

    val tablesDB = TablesDB(client)

    try {
        val row = tablesDB.createRow(
            databaseId = "<DATABASE_ID>",
            tableId = "<TABLE_ID>",
            rowId = ID.unique(),
            data = mapOf("title" to "hamlet"),
        )
    } catch (e: Exception) {
        Log.e("Appwrite", "Error: " + e.message)
    }
}
```

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"
}
```

#### 4. List rows

To read and query data from your table, use the `listRows` endpoint.

Like the previous step, replace `<PROJECT_ID>`, `<DATABASE_ID>`, and `<TABLE_ID>` with their respective IDs.

```client-web
import { Client, Query, TablesDB } from "appwrite";

const client = new Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

const tablesDB = new TablesDB(client);

const promise = tablesDB.listRows({
    databaseId: "<DATABASE_ID>",
    tableId: "<TABLE_ID>",
    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://<REGION>.cloud.appwrite.io/v1")
        .setProject("<PROJECT_ID>")

    final tablesDB = TablesDB(client);

    try {
        final rows = await tablesDB.listRows(
            databaseId: '<DATABASE_ID>',
            tableId: '<TABLE_ID>',
            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://<REGION>.cloud.appwrite.io/v1")
        .setProject("<PROJECT_ID>")

    let tablesDB = TablesDB(client)

    do {
        let rows = try await tablesDB.listRows(
            databaseId: "<DATABASE_ID>",
            tableId: "<TABLE_ID>",
            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://<REGION>.cloud.appwrite.io/v1")
        .setProject("<PROJECT_ID>")

    val tablesDB = TablesDB(client)

    try {
        val rows = tablesDB.listRows(
            databaseId = "<DATABASE_ID>",
            tableId = "<TABLE_ID>",
            queries = listOf(
                Query.equal("title", "Hamlet")
            )
        )
    } catch (e: AppwriteException) {
        Log.e("Appwrite", "Error: " + e.message)
    }
}
```

#### 5. 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:

```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 = "<DATABASE_ID>",
        tableId = "<TABLE_ID>",
        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: "<DATABASE_ID>",
        tableId: "<TABLE_ID>",
        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<Book>({
        databaseId: '<DATABASE_ID>',
        tableId: '<TABLE_ID>'
    });

    books.rows.forEach(book => {
        console.log(`Book: ${book.title} by ${book.author}`);
    });
} catch (error) {
    console.log(error);
}
```

**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` to generate models for your tables.

##### Model methods

Models returned by native SDKs include helpful methods for data manipulation:

**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)
```

**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)
```

---

## Relationships

URL: https://appwrite.io/docs/products/databases/tablesdb/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.

### 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

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

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.

### Opt-in 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.

[Learn how to load relationships with queries](/docs/products/databases/queries#relationship-select)

### 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

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

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
You can define relationships in the Appwrite Console, or using a [Server SDK](/docs/sdks#server)

**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.

**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.

```js
const { Client, TablesDB } = require('node-appwrite');

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<PROJECT_ID>');               // Your project ID

const tablesDB = new 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
});
```

```php
use \Appwrite\Client;
use \Appwrite\Services\TablesDB;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    ->setProject('<PROJECT_ID>');               // 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://<REGION>.cloud.appwrite.io/v1')     # Your API Endpoint
    .set_project('<PROJECT_ID>'))                   # 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://<REGION>.cloud.appwrite.io/v1')# Your API Endpoint
    .set_project('<PROJECT_ID>')               # 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://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<PROJECT_ID>");               // Your project ID

const tablesDB = new TablesDB(client);

tablesDB.createRelationshipColumn({
    databaseId: "marvel",
    tableId: "movies",
    relatedTableId: "reviews",
    type: "oneToMany",
    twoWay: true,
    key: "reviews",
    twoWayKey: "movie",
    onDelete: "cascade"
});
```

```dart
import 'package:appwrite/appwrite.dart';

final client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<PROJECT_ID>');               // 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://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<PROJECT_ID>")                // 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://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<PROJECT_ID>")                // 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://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>");

var tablesDB = new TablesDB(client);

await tablesDB.CreateRelationshipColumn(
    databaseId: "marvel",
    tableId: "movies",
    relatedTableId: "reviews",
    type: "oneToMany",
    twoWay: true,
    key: "reviews",
    twoWayKey: "movie",
    onDelete: "cascade");
```

### Creating 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***.

**Nested**

You can create both the **parent** and **child** at once in a relationship by nesting data.

```js
const { Client, ID, TablesDB } = require('node-appwrite');

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<PROJECT_ID>');               // 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://<REGION>.cloud.appwrite.io/v1')  // Your API Endpoint
    .setProject('<PROJECT_ID>');                // 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://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<PROJECT_ID>")                // 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://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<PROJECT_ID>")                // 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!")
        )
    )
)
```

#### 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**.

**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.

```js
const { Client, ID, TablesDB } = require('node-appwrite');

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<PROJECT_ID>');               // Your project ID

const tablesDB = new TablesDB(client);

await tablesDB.createRow({
    databaseId: 'marvel',
    tableId: 'movies',
    rowId: ID.unique(),
    data: {
        title: 'Spiderman',
        year: 2002,
        reviews: [
            '<REVIEW_ID_1>',
            '<REVIEW_ID_2>'
        ]
    }
});
```

```dart
import 'package:appwrite/appwrite.dart';

final client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint    
    .setProject('<PROJECT_ID>');               // Your project ID  

final tablesDB = TablesDB(client);

await tablesDB.createRow(
  databaseId: 'marvel',
  tableId: 'movies',
  rowId: ID.unique(),
  data: {
    'title': 'Spiderman',
    'year': 2002,
    'reviews': [
        '<REVIEW_ID_1>',
        '<REVIEW_ID_2>'
    ]
  },
)
```

```swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint        
    .setProject("<PROJECT_ID>")                // Your project ID      

let tablesDB = TablesDB(client: client)

tablesDB.createRow(
    databaseId: "marvel",
    tableId: "movies",
    rowId: ID.unique(),
    data: [
        "title": "Spiderman",
        "year": 2002,
        "reviews": [
            "<REVIEW_ID_1>",
            "<REVIEW_ID_2>"
        ]
    ]
)
```

```kotlin
import io.appwrite.Client
import io.appwrite.services.TablesDB
import io.appwrite.ID

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint    
    .setProject("<PROJECT_ID>")                // 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(
            "<REVIEW_ID_1>",
            "<REVIEW_ID_2>"
        )
    )
)
```

### Queries

You can use filter queries directly against relationship columns using dot notation. This lets you filter rows based on the values of their related rows, such as filtering posts by an author's name or filtering orders by a product's category.

Use the format `relationshipKey.field` to reference fields on related rows.

```js
const { Client, TablesDB, Query } = require('node-appwrite');

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<PROJECT_ID>');               // Your project ID

const tablesDB = new TablesDB(client);

await tablesDB.listRows({
    databaseId: 'marvel',
    tableId: 'movies',
    queries: [
        Query.equal('reviews.author', ['Bob'])
    ],
});
```

```dart
import 'package:appwrite/appwrite.dart';

final client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

final tablesDB = TablesDB(client);

await tablesDB.listRows(
  databaseId: 'marvel',
  tableId: 'movies',
  queries: [
    Query.equal('reviews.author', ['Bob']),
  ],
);
```

```swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

let tablesDB = TablesDB(client)

tablesDB.listRows(
    databaseId: "marvel",
    tableId: "movies",
    queries: [
        Query.equal("reviews.author", value: ["Bob"])
    ]
)
```

```kotlin
import io.appwrite.Client
import io.appwrite.services.TablesDB
import io.appwrite.Query

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

val tablesDB = TablesDB(client)

tablesDB.listRows(
    databaseId = "marvel",
    tableId = "movies",
    queries = listOf(
        Query.equal("reviews.author", listOf("Bob"))
    )
)
```

All filter queries are supported on relationship fields, including `equal`, `notEqual`, `greaterThan`, `lessThan`, `between`, `contains`, and other [comparison operators](/docs/products/databases/queries#comparison).

[Learn how to select and load relationship data](/docs/products/databases/queries#relationship-select)

### Update relationships
Relationships can be updated by updating the relationship column.

```js
const { Client, TablesDB } = require('node-appwrite');

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<PROJECT_ID>');               // Your project ID

const tablesDB = new TablesDB(client);

await tablesDB.updateRow({
    databaseId: 'marvel',
    tableId: 'movies',
    rowId: 'spiderman',
    data: {
        title: 'Spiderman',
        year: 2002,
        reviews: [
            'review4',
            'review5'
        ]
    }
});
```

```dart
import 'package:appwrite/appwrite.dart';

final client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

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://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

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://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

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"
        )
    )
)
```

### Delete relationships
#### Unlink relationships, retain rows

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

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.

```js
const { Client, TablesDB } = require('node-appwrite');

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<PROJECT_ID>');               // Your project ID

const tablesDB = new TablesDB(client);

await tablesDB.deleteRow({
    databaseId: 'marvel',
    tableId: 'movies',
    rowId: 'spiderman'
});
```

```dart
import 'package:appwrite/appwrite.dart';

final client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

final tablesDB = TablesDB(client);

await tablesDB.deleteRow(
  databaseId: 'marvel',
  tableId: 'movies',
  rowId: 'spiderman'
);
```

```swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

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://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

val tablesDB = TablesDB(client)

tablesDB.deleteRow(
    databaseId = "marvel",
    tableId = "movies",
    rowId = "spiderman"
)
```

### 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**.

```js
const { Client, ID, TablesDB } = require('node-appwrite');

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<PROJECT_ID>');               // 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!',
                $permissions: [
                    Permission.read(Role.any())
                ]
            },
        ]
    }
});
```
```dart
import 'package:appwrite/appwrite.dart';

final client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<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!',
         '\$permissions': [
           Permission.read(Role.any())
         ]
       },
    ]
  },
);
```
```swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<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!",
                "$permissions": [
                    Permission.read(Role.any())
                ]
            ],
        ]
    ]
);
```
```kotlin
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<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!",
                "$permissions": [
                    Permission.read(Role.any())
                ]
            ],
        ]
    ]
);
```

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

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

URL: https://appwrite.io/docs/products/databases/tablesdb/rows

Each piece of data or information in Appwrite Databases is a row.
Rows have a structure defined by the parent table.

### Create rows

**Permissions required**

You must grant _create_ permissions to users at the _table level_ before users can create rows.
[Learn more about permissions](#permissions)

In most use cases, you will create rows programmatically.

```client-web
import { Client, ID, TablesDB } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

const tablesDB = new TablesDB(client);

const promise = tablesDB.createRow({
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    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://<REGION>.cloud.appwrite.io/v1')
        .setProject('<PROJECT_ID>');

    final tablesDB = TablesDB(client);

    try {
        final row = tablesDB.createRow(
            databaseId: '<DATABASE_ID>',
            tableId: '<TABLE_ID>',
            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://<REGION>.cloud.appwrite.io/v1")
        .setProject("<PROJECT_ID>")

    let tablesDB = TablesDB(client)

    do {
        let row = try await tablesDB.createRow(
            databaseId: "<DATABASE_ID>",
            tableId: "<TABLE_ID>",
            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://<REGION>.cloud.appwrite.io/v1")
        .setProject("<PROJECT_ID>")

    val tablesDB = TablesDB(client)

    try {
        val row = tablesDB.createRow(
            databaseId = "<DATABASE_ID>",
            tableId = "<TABLE_ID>",
            rowId = ID.unique(),
            data = mapOf("a" to "b"),
        )
    } catch (e: AppwriteException) {
        Log.e("Appwrite", "Error: " + e.message)
    }
}
```
```graphql
mutation {
    tablesCreateRow(
        databaseId: "<DATABASE_ID>",
        tableId: "<TABLE_ID>",
        rowId: "<ROW_ID>",
        data: "{}"
    ) {
        _id
        _tableId
        _databaseId
        _createdAt
        _updatedAt
        _permissions
        data
    }
}
```

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.

### Get row

**Permissions required**

You must grant _read_ permissions to users at the _table level_ or _row level_ before users can read rows.
[Learn more about permissions](#permissions)

A single row can be retrieved by its ID using the [Get row](/docs/references/cloud/client-web/tablesDB#getRow) endpoint.

Pass `queries` to shape the response, for example `Query.select()` to return only the columns you need or to
[load relationship data](/docs/products/databases/tablesdb/relationships#performance-loading).

```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 promise = tablesDB.getRow({
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    rowId: '<ROW_ID>'
});

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://<REGION>.cloud.appwrite.io/v1')
        .setProject('<PROJECT_ID>');

    final tablesDB = TablesDB(client);

    try {
        final row = await tablesDB.getRow(
            databaseId: '<DATABASE_ID>',
            tableId: '<TABLE_ID>',
            rowId: '<ROW_ID>'
        );
    } on AppwriteException catch(e) {
        print(e);
    }
}
```
```client-apple
import Appwrite
import AppwriteModels

func main() async throws {
    let client = Client()
        .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
        .setProject("<PROJECT_ID>")

    let tablesDB = TablesDB(client)

    do {
        let row = try await tablesDB.getRow(
            databaseId: "<DATABASE_ID>",
            tableId: "<TABLE_ID>",
            rowId: "<ROW_ID>"
        )
    } 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://<REGION>.cloud.appwrite.io/v1")
        .setProject("<PROJECT_ID>")

    val tablesDB = TablesDB(client)

    try {
        val row = tablesDB.getRow(
            databaseId = "<DATABASE_ID>",
            tableId = "<TABLE_ID>",
            rowId = "<ROW_ID>",
        )
    } catch (e: AppwriteException) {
        Log.e("Appwrite", "Error: " + e.message)
    }
}
```
```graphql
query {
    tablesDBGetRow(
        databaseId: "<DATABASE_ID>",
        tableId: "<TABLE_ID>",
        rowId: "<ROW_ID>"
    ) {
        _id
        _tableId
        _databaseId
        _createdAt
        _updatedAt
        _permissions
        data
    }
}
```

### List rows

**Permissions required**

You must grant _read_ permissions to users at the _table level_ before users can read rows.
[Learn more about permissions](#permissions)

Rows can be retrieved using the [List rows](/docs/references/cloud/client-web/tablesDB#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).

**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).

```client-web
import { Client, Query, TablesDB } from "appwrite";

const client = new Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")

const tablesDB = new TablesDB(client);

let promise = tablesDB.listRows({
    databaseId: "<DATABASE_ID>",
    tableId: "<TABLE_ID>",
    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://<REGION>.cloud.appwrite.io/v1")
        .setProject("<PROJECT_ID>")

    final tablesDB = TablesDB(client);

    try {
        final rows = await tablesDB.listRows(
            databaseId: '<DATABASE_ID>',
            tableId: '<TABLE_ID>',
            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://<REGION>.cloud.appwrite.io/v1")
        .setProject("<PROJECT_ID>")

    let tablesDB = TablesDB(client)

    do {
        let rows = try await tablesDB.listRows(
            databaseId: "<DATABASE_ID>",
            tableId: "<TABLE_ID>",
            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://<REGION>.cloud.appwrite.io/v1")
        .setProject("<PROJECT_ID>")

    val tablesDB = TablesDB(client)

    try {
        val rows = tablesDB.listRows(
            databaseId = "<DATABASE_ID>",
            tableId = "<TABLE_ID>",
            queries = listOf(
                Query.equal("title", "Avatar")
            )
        )
    } catch (e: AppwriteException) {
        Log.e("Appwrite", "Error: " + e.message)
    }
}
```
```graphql
query {
    tablesListRows(
        databaseId: "<DATABASE_ID>",
        tableId: "<TABLE_ID>",
        queries: ["equal(\"title\", [\"Avatar\"])"]
    ) {
        total
        rows {
            _id
            data
        }
    }
}
```

### Cache list responses

You can cache list responses by passing a `ttl` (time-to-live) value in seconds to `listRows`. Subsequent identical requests return the cached result until the TTL expires. The cache is permission-aware, so users with different roles never see each other's cached data.

Set `ttl` between `1` and `86400` (24 hours). The default is `0` (caching disabled). The response includes an `X-Appwrite-Cache` header with value `hit` or `miss`.

```client-web
import { Client, Query, TablesDB } from "appwrite";

const client = new Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>");

const tablesDB = new TablesDB(client);

const rows = await tablesDB.listRows({
    databaseId: "<DATABASE_ID>",
    tableId: "<TABLE_ID>",
    queries: [
        Query.equal('title', 'Avatar')
    ],
    ttl: 60 // Cache for 60 seconds
});
```
```server-nodejs
const sdk = require('node-appwrite');

const client = new sdk.Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const tablesDB = new sdk.TablesDB(client);

const rows = await tablesDB.listRows({
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    queries: [
        sdk.Query.equal('title', 'Avatar')
    ],
    ttl: 60 // Cache for 60 seconds
});
```
```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://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

tables_db = TablesDB(client)

rows = tables_db.list_rows(
    database_id='<DATABASE_ID>',
    table_id='<TABLE_ID>',
    queries=[
        Query.equal('title', 'Avatar')
    ],
    ttl=60  # Cache for 60 seconds
)
```
```server-ruby
require 'appwrite'

client = Appwrite::Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

tables_db = Appwrite::TablesDB.new(client)

rows = tables_db.list_rows(
    database_id: '<DATABASE_ID>',
    table_id: '<TABLE_ID>',
    queries: [
        Appwrite::Query.equal('title', 'Avatar')
    ],
    ttl: 60  # Cache for 60 seconds
)
```
```server-deno
import { Client, Query, TablesDB } from "https://deno.land/x/appwrite/mod.ts";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const tablesDB = new TablesDB(client);

const rows = await tablesDB.listRows({
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    queries: [
        Query.equal('title', 'Avatar')
    ],
    ttl: 60 // Cache for 60 seconds
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Query;
use Appwrite\Services\TablesDB;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$tablesDB = new TablesDB($client);

$rows = $tablesDB->listRows(
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    queries: [
        Query::equal('title', ['Avatar'])
    ],
    ttl: 60 // Cache for 60 seconds
);
```
```server-go
package main

import (
    "fmt"
    "github.com/appwrite/sdk-for-go/client"
    "github.com/appwrite/sdk-for-go/tablesdb"
    "github.com/appwrite/sdk-for-go/query"
)

func main() {
    clt := client.New(
        client.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"),
        client.WithProject("<PROJECT_ID>"),
        client.WithKey("<YOUR_API_KEY>"),
    )

    tablesDB := tablesdb.New(clt)

    rows, err := tablesDB.ListRows(
        "<DATABASE_ID>",
        "<TABLE_ID>",
        tablesDB.WithListRowsQueries([]string{
            query.Equal("title", []interface{}{"Avatar"}),
        }),
        tablesDB.WithListRowsTtl(60), // Cache for 60 seconds
    )

    if err != nil {
        fmt.Println(err)
    }
    _ = rows
}
```
```server-swift
import Appwrite
import AppwriteModels

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let tablesDB = TablesDB(client)

let rows = try await tablesDB.listRows(
    databaseId: "<DATABASE_ID>",
    tableId: "<TABLE_ID>",
    queries: [
        Query.equal("title", value: "Avatar")
    ],
    ttl: 60 // Cache for 60 seconds
)
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.Query
import io.appwrite.services.TablesDB

suspend fun main() {
    val client = Client()
        .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
        .setProject("<PROJECT_ID>")
        .setKey("<YOUR_API_KEY>")

    val tablesDB = TablesDB(client)

    val rows = tablesDB.listRows(
        databaseId = "<DATABASE_ID>",
        tableId = "<TABLE_ID>",
        queries = listOf(
            Query.equal("title", "Avatar")
        ),
        ttl = 60 // Cache for 60 seconds
    )
}
```
```server-rust
use appwrite::Client;
use appwrite::services::TablesDB;

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

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

    let rows = tables_db.list_rows(
        "<DATABASE_ID>",
        "<TABLE_ID>",
        Some(vec![
            "equal(\"title\", [\"Avatar\"])".to_string(),
        ]),
        None,     // transaction_id
        None,     // total
        Some(60), // ttl - Cache for 60 seconds
    ).await?;

    let _ = rows;
    Ok(())
}
```
```server-dotnet
using Appwrite;
using Appwrite.Models;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

TablesDB tablesDB = new TablesDB(client);

RowList rows = await tablesDB.ListRows(
    databaseId: "<DATABASE_ID>",
    tableId: "<TABLE_ID>",
    queries: new List<string> {
        Query.Equal("title", new List<object> { "Avatar" })
    },
    ttl: 60 // Cache for 60 seconds
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

TablesDB tablesDB = TablesDB(client);

RowList rows = await tablesDB.listRows(
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    queries: [
        Query.equal('title', 'Avatar')
    ],
    ttl: 60, // Cache for 60 seconds
);
```
```server-java
import io.appwrite.Client;
import io.appwrite.Query;
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("<YOUR_API_KEY>");

TablesDB tablesDB = new TablesDB(client);

tablesDB.listRows(
    "<DATABASE_ID>",
    "<TABLE_ID>",
    List.of(Query.equal("title", List.of("Avatar"))),
    null, // transactionId
    null, // total
    60, // ttl - Cache for 60 seconds
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }
        System.out.println(result);
    })
);
```
```client-flutter
import 'package:appwrite/appwrite.dart';

void main() async {
    final client = Client()
        .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
        .setProject("<PROJECT_ID>");

    final tablesDB = TablesDB(client);

    final rows = await tablesDB.listRows(
        databaseId: '<DATABASE_ID>',
        tableId: '<TABLE_ID>',
        queries: [
            Query.equal('title', 'Avatar')
        ],
        ttl: 60, // Cache for 60 seconds
    );
}
```
```client-apple
import Appwrite
import AppwriteModels

func main() async throws {
    let client = Client()
        .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
        .setProject("<PROJECT_ID>")

    let tablesDB = TablesDB(client)

    let rows = try await tablesDB.listRows(
        databaseId: "<DATABASE_ID>",
        tableId: "<TABLE_ID>",
        queries: [
            Query.equal("title", value: "Avatar")
        ],
        ttl: 60 // Cache for 60 seconds
    )
}
```
```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://<REGION>.cloud.appwrite.io/v1")
        .setProject("<PROJECT_ID>")

    val tablesDB = TablesDB(client)

    val rows = tablesDB.listRows(
        databaseId = "<DATABASE_ID>",
        tableId = "<TABLE_ID>",
        queries = listOf(
            Query.equal("title", "Avatar")
        ),
        ttl = 60 // Cache for 60 seconds
    )
}
```
```graphql
query {
    tablesListRows(
        databaseId: "<DATABASE_ID>",
        tableId: "<TABLE_ID>",
        queries: ["equal(\"title\", [\"Avatar\"])"],
        ttl: 60
    ) {
        total
        rows {
            _id
            data
        }
    }
}
```

#### Purge cache

Row writes do **not** invalidate the cache, so cached responses may contain stale data until the TTL expires. Schema changes (adding or removing columns and indexes) invalidate cached entries automatically.

To force an immediate cache purge, call `updateTable` with `purge` set to `true` using a [Server SDK](/docs/sdks#server).

```server-nodejs
const sdk = require('node-appwrite');

const client = new sdk.Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const tablesDB = new sdk.TablesDB(client);

await tablesDB.updateTable({
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    purge: true
});
```
```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('<YOUR_API_KEY>')

tables_db = TablesDB(client)

tables_db.update_table(
    database_id='<DATABASE_ID>',
    table_id='<TABLE_ID>',
    purge=True
)
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\TablesDB;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$tablesDB = new TablesDB($client);

$tablesDB->updateTable(
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    purge: true
);
```
```server-ruby
require 'appwrite'

client = Appwrite::Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

tables_db = Appwrite::TablesDB.new(client)

tables_db.update_table(
    database_id: '<DATABASE_ID>',
    table_id: '<TABLE_ID>',
    purge: true
)
```
```server-deno
import { Client, TablesDB } from "https://deno.land/x/appwrite/mod.ts";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const tablesDB = new TablesDB(client);

await tablesDB.updateTable({
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    purge: true
});
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

final client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

final tablesDB = TablesDB(client);

await tablesDB.updateTable(
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    purge: true,
);
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let tablesDB = TablesDB(client)

let _ = try await tablesDB.updateTable(
    databaseId: "<DATABASE_ID>",
    tableId: "<TABLE_ID>",
    purge: true
)
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.TablesDB

suspend fun main() {
    val client = Client(applicationContext)
        .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
        .setProject("<PROJECT_ID>")
        .setKey("<YOUR_API_KEY>")

    val tablesDB = TablesDB(client)

    tablesDB.updateTable(
        databaseId = "<DATABASE_ID>",
        tableId = "<TABLE_ID>",
        purge = true
    )
}
```
```server-go
package main

import (
    "github.com/appwrite/sdk-for-go/client"
    "github.com/appwrite/sdk-for-go/tablesdb"
)

func main() {
    clt := client.New(
        client.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"),
        client.WithProject("<PROJECT_ID>"),
        client.WithKey("<YOUR_API_KEY>"),
    )

    tablesDB := tablesdb.New(clt)

    tablesDB.UpdateTable(
        "<DATABASE_ID>",
        "<TABLE_ID>",
        tablesDB.WithUpdateTablePurge(true),
    )
}
```
```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("<YOUR_API_KEY>");

TablesDB tablesDB = new TablesDB(client);

tablesDB.updateTable(
    "<DATABASE_ID>",
    "<TABLE_ID>",
    null, // name
    null, // permissions
    null, // rowSecurity
    null, // enabled
    true, // purge
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }
        System.out.println(result);
    })
);
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

var client = new Client()
    .SetEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

var tablesDB = new TablesDB(client);

await tablesDB.UpdateTable(
    databaseId: "<DATABASE_ID>",
    tableId: "<TABLE_ID>",
    purge: true
);
```
```server-rust
use appwrite::Client;
use appwrite::services::tables_db::TablesDB;

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

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

    tables_db.update_table(
        "<DATABASE_ID>",
        "<TABLE_ID>",
        None,       // name
        None,       // permissions
        None,       // row_security
        None,       // enabled
        Some(true), // purge
    ).await?;

    Ok(())
}
```

### Update row

**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)

In most use cases, you will update rows programmatically.

```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 promise = tablesDB.updateRow({
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    rowId: '<ROW_ID>',
    data: { 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://<REGION>.cloud.appwrite.io/v1')
        .setProject('<PROJECT_ID>');

    final tablesDB = TablesDB(client);

    try {
        final row = await tablesDB.updateRow(
            databaseId: '<DATABASE_ID>',
            tableId: '<TABLE_ID>',
            rowId: '<ROW_ID>',
            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://<REGION>.cloud.appwrite.io/v1")
        .setProject("<PROJECT_ID>")

    let tablesDB = TablesDB(client)

    do {
        let row = try await tablesDB.updateRow(
            databaseId: "<DATABASE_ID>",
            tableId: "<TABLE_ID>",
            rowId: "<ROW_ID>",
            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://<REGION>.cloud.appwrite.io/v1")
        .setProject("<PROJECT_ID>")

    val tablesDB = TablesDB(client)

    try {
        val row = tablesDB.updateRow(
            databaseId = "<DATABASE_ID>",
            tableId = "<TABLE_ID>",
            rowId = "<ROW_ID>",
            data = mapOf("title" to "Updated Title"),
        )
    } catch (e: AppwriteException) {
        Log.e("Appwrite", "Error: " + e.message)
    }
}
```
```graphql
mutation {
    tablesDBUpdateRow(
        databaseId: "<DATABASE_ID>",
        tableId: "<TABLE_ID>",
        rowId: "<ROW_ID>",
        data: "{\"title\": \"Updated Title\"}"
    ) {
        _id
        _tableId
        _databaseId
        _createdAt
        _updatedAt
        _permissions
        data
    }
}
```

### 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.

**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)

```client-web
import { Client, ID, TablesDB } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

const tablesDB = new TablesDB(client);

const promise = tablesDB.upsertRow({
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    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://<REGION>.cloud.appwrite.io/v1')
        .setProject('<PROJECT_ID>');

    final tablesDB = TablesDB(client);

    try {
        final row = tablesDB.upsertRow(
            databaseId: '<DATABASE_ID>',
            tableId: '<TABLE_ID>',
            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://<REGION>.cloud.appwrite.io/v1")
        .setProject("<PROJECT_ID>")

    let tablesDB = TablesDB(client)

    do {
        let row = try await tablesDB.upsertRow(
            databaseId: "<DATABASE_ID>",
            tableId: "<TABLE_ID>",
            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://<REGION>.cloud.appwrite.io/v1")
        .setProject("<PROJECT_ID>")

    val tablesDB = TablesDB(client)

    try {
        val row = tablesDB.upsertRow(
            databaseId = "<DATABASE_ID>",
            tableId = "<TABLE_ID>",
            rowId = ID.unique(),
            data = mapOf("a" to "b"),
        )
    } catch (e: AppwriteException) {
        Log.e("Appwrite", "Error: " + e.message)
    }
}
```
```graphql
mutation {
    tablesUpsertRow(
        databaseId: "<DATABASE_ID>",
        tableId: "<TABLE_ID>",
        rowId: "<ROW_ID>",
        data: "{}"
    ) {
        _id
        _tableId
        _databaseId
        _createdAt
        _updatedAt
        _permissions
        data
    }
}
```

### Type safety with models

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:

**Kotlin/Java**

```kotlin
data class Book(
    val title: String,
    val author: String,
    val publishedYear: Int? = null,
    val genre: List<String>? = null,
    val isAvailable: Boolean = true
)
```

**Swift**

```swift
struct Book: Codable {
    let title: String
    let author: String
    let publishedYear: Int?
    let genre: [String]?
    let isAvailable: Bool
}
```

**Web/Node**

```typescript
interface Book {
    title: string;
    author: string;
    publishedYear?: number;
    genre?: string[];
    isAvailable: boolean;
}
```

#### Using type-safe operations

Use the `nestedType` parameter for full type safety in native SDKs, or generics in web SDKs:

```client-android-kotlin
val tablesDB = TablesDB(client)

try {
    // Create with type safety
    val newBook = tablesDB.createRow(
        databaseId = "<DATABASE_ID>",
        tableId = "<TABLE_ID>",
        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 = "<DATABASE_ID>",
        tableId = "<TABLE_ID>",
        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: "<DATABASE_ID>",
        tableId: "<TABLE_ID>",
        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: "<DATABASE_ID>",
        tableId: "<TABLE_ID>",
        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<Book>({
        databaseId: '<DATABASE_ID>',
        tableId: '<TABLE_ID>',
        rowId: ID.unique(),
        data: {
            title: "The Great Gatsby",
            author: "F. Scott Fitzgerald",
            isAvailable: true
        }
    });

    // List with generics
    const books = await tablesDB.listRows<Book>({
        databaseId: '<DATABASE_ID>',
        tableId: '<TABLE_ID>'
    });

    // 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);
}
```

#### Model methods

Models returned by native SDKs include helpful utility methods:

**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)
```

**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)
}
```

**Generate types automatically**

You can automatically generate model definitions for your tables using the [Appwrite CLI](/docs/products/databases/type-generation). Run `appwrite types` to generate types based on your database schema.

### 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

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).

```client-web
// Create row inside a transaction
await tablesDB.createRow({
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    rowId: '<ROW_ID>',
    data: { title: 'Draft' },
    transactionId: '<TRANSACTION_ID>'
});

// Update row inside a transaction
await tablesDB.updateRow({
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    rowId: '<ROW_ID>',
    data: { title: 'Published' },
    transactionId: '<TRANSACTION_ID>'
});
```
```client-flutter
// Create row inside a transaction
await tablesDB.createRow(
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    rowId: '<ROW_ID>',
    data: { 'title': 'Draft' },
    transactionId: '<TRANSACTION_ID>'
);

// Update row inside a transaction
await tablesDB.updateRow(
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    rowId: '<ROW_ID>',
    data: { 'title': 'Published' },
    transactionId: '<TRANSACTION_ID>'
);
```
```client-apple
// Create row inside a transaction
let _ = try await tablesDB.createRow(
    databaseId: "<DATABASE_ID>",
    tableId: "<TABLE_ID>",
    rowId: "<ROW_ID>",
    data: ["title": "Draft"],
    transactionId: "<TRANSACTION_ID>"
)

// Update row inside a transaction
let _2 = try await tablesDB.updateRow(
    databaseId: "<DATABASE_ID>",
    tableId: "<TABLE_ID>",
    rowId: "<ROW_ID>",
    data: ["title": "Published"],
    transactionId: "<TRANSACTION_ID>"
)
```
```client-android-kotlin
// Create row inside a transaction
val _ = tablesDB.createRow(
    databaseId = "<DATABASE_ID>",
    tableId = "<TABLE_ID>",
    rowId = "<ROW_ID>",
    data = mapOf("title" to "Draft"),
    transactionId = "<TRANSACTION_ID>"
)

// Update row inside a transaction
val _2 = tablesDB.updateRow(
    databaseId = "<DATABASE_ID>",
    tableId = "<TABLE_ID>",
    rowId = "<ROW_ID>",
    data = mapOf("title" to "Published"),
    transactionId = "<TRANSACTION_ID>"
)
```
```client-android-java
// Create row inside a transaction (asynchronous)
Map<String, Object> data = new HashMap<>();
data.put("title", "Draft");

tablesDB.createRow(
    "<DATABASE_ID>",
    "<TABLE_ID>",
    "<ROW_ID>",
    data,
    "<TRANSACTION_ID>",
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return null;
        }
        System.out.println(result);
        return null;
    })
);

// Update row inside a transaction (asynchronous)
Map<String, Object> update = new HashMap<>();
update.put("title", "Published");

tablesDB.updateRow(
    "<DATABASE_ID>",
    "<TABLE_ID>",
    "<ROW_ID>",
    update,
    "<TRANSACTION_ID>",
    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: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    rowId: '<ROW_ID>',
    data: { title: 'Draft' },
    transactionId: '<TRANSACTION_ID>'
});

// Update row inside a transaction
await tablesDB.updateRow({
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    rowId: '<ROW_ID>',
    data: { title: 'Published' },
    transactionId: '<TRANSACTION_ID>'
});
```
```server-nodejs
// Delete row inside a transaction
await tablesDB.deleteRow({
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    rowId: '<ROW_ID>',
    transactionId: '<TRANSACTION_ID>'
});
```
```server-deno
// Delete row inside a transaction
await tablesDB.deleteRow({
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    rowId: '<ROW_ID>',
    transactionId: '<TRANSACTION_ID>'
});
```
```server-python
### Delete row inside a transaction
tablesDB.delete_row(
    database_id = '<DATABASE_ID>',
    table_id = '<TABLE_ID>',
    row_id = '<ROW_ID>',
    transaction_id = '<TRANSACTION_ID>'
)
```
```server-php
// Delete row inside a transaction
$tablesDB->deleteRow(
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    rowId: '<ROW_ID>',
    transactionId: '<TRANSACTION_ID>'
);
```
```server-ruby
### Delete row inside a transaction
tablesDB.delete_row(
    database_id: '<DATABASE_ID>',
    table_id: '<TABLE_ID>',
    row_id: '<ROW_ID>',
    transaction_id: '<TRANSACTION_ID>'
)
```
```server-dotnet
// Delete row inside a transaction
await tablesDB.DeleteRow(
    databaseId: "<DATABASE_ID>",
    tableId: "<TABLE_ID>",
    rowId: "<ROW_ID>",
    transactionId: "<TRANSACTION_ID>"
);
```
```server-dart
// Delete row inside a transaction
await tablesDB.deleteRow(
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    rowId: '<ROW_ID>',
    transactionId: '<TRANSACTION_ID>'
);
```
```server-swift
// Delete row inside a transaction
let _ = try await tablesDB.deleteRow(
    databaseId: "<DATABASE_ID>",
    tableId: "<TABLE_ID>",
    rowId: "<ROW_ID>",
    transactionId: "<TRANSACTION_ID>"
)
```
```server-kotlin
// Delete row inside a transaction
val _ = tablesDB.deleteRow(
    databaseId = "<DATABASE_ID>",
    tableId = "<TABLE_ID>",
    rowId = "<ROW_ID>",
    transactionId = "<TRANSACTION_ID>"
)
```
```server-java
// Delete row inside a transaction (asynchronous)
tablesDB.deleteRow(
    "<DATABASE_ID>",
    "<TABLE_ID>",
    "<ROW_ID>",
    "<TRANSACTION_ID>",
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return null;
        }
        System.out.println(result);
        return null;
    })
);
```

```server-rust
// Delete row inside a transaction
use appwrite::Client;
use appwrite::services::tables_db::TablesDB;

let client = Client::new()
    .set_endpoint("https://<REGION>.cloud.appwrite.io/v1")
    .set_project("<PROJECT_ID>")
    .set_key("<API_KEY>");

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

tables_db.delete_row(
    "<DATABASE_ID>",
    "<TABLE_ID>",
    "<ROW_ID>",
    Some("<TRANSACTION_ID>"),
).await?;
```

### Next steps

Continue learning with these related guides:

- [Queries](/docs/products/databases/queries): Learn how to filter, sort, and search your rows with various query operators.

- [Pagination](/docs/products/databases/pagination): Handle large datasets by implementing pagination in your row queries.

- [Bulk operations](/docs/products/databases/bulk-operations): Perform create, update, and delete operations on multiple rows simultaneously.

- [Timestamp overrides](/docs/products/databases/timestamp-overrides): Set custom creation and update timestamps when migrating data or backdating records.

---

## Tables

URL: https://appwrite.io/docs/products/databases/tablesdb/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
You can create tables using the Appwrite Console, a [Server SDK](/docs/sdks#server), or using the [CLI](/docs/tooling/command-line/installation).

**Console**

You can create a table by heading to the **Databases** page, navigate to a [database](/docs/products/databases/databases), and click **Create table**.

**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/partners/project/api-keys).

```server-nodejs
const sdk = require('node-appwrite');

// Init SDK
const client = new sdk.Client();

const tablesDB = new sdk.TablesDB(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 = tablesDB.createTable({
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    name: '<NAME>',
    columns: [
        {
            key: 'email',
            type: 'email',
            required: true
        },
        {
            key: 'name',
            type: 'varchar',
            size: 255,
            required: true
        },
        {
            key: 'bio',
            type: 'text',
            required: false
        },
        {
            key: 'content',
            type: 'mediumtext',
            required: false
        },
        {
            key: 'data',
            type: 'longtext',
            required: false
        },
        {
            key: 'age',
            type: 'integer',
            required: false
        },
        {
            key: 'score',
            type: 'float',
            required: false
        },
        {
            key: 'total_views',
            type: 'bigint',
            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: '<RELATED_TABLE_ID>',
            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://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<PROJECT_ID>') // Your project ID
    .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key
;

let promise = tablesDB.createTable({
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    name: '<NAME>',
    columns: [
        {
            key: 'email',
            type: 'email',
            required: true
        },
        {
            key: 'name',
            type: 'varchar',
            size: 255,
            required: true
        },
        {
            key: 'bio',
            type: 'text',
            required: false
        },
        {
            key: 'content',
            type: 'mediumtext',
            required: false
        },
        {
            key: 'data',
            type: 'longtext',
            required: false
        },
        {
            key: 'age',
            type: 'integer',
            required: false
        },
        {
            key: 'score',
            type: 'float',
            required: false
        },
        {
            key: 'total_views',
            type: 'bigint',
            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: '<RELATED_TABLE_ID>',
            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
<?php

use Appwrite\Client;
use Appwrite\Services\TablesDB;

$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
;

$tablesDB = new TablesDB($client);

$result = $tablesDB->createTable(
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    name: '<NAME>',
    columns: [
        [
            'key' => 'email',
            'type' => 'email',
            'required' => true
        ],
        [
            'key' => 'name',
            'type' => 'varchar',
            'size' => 255,
            'required' => true
        ],
        [
            'key' => 'bio',
            'type' => 'text',
            'required' => false
        ],
        [
            'key' => 'content',
            'type' => 'mediumtext',
            'required' => false
        ],
        [
            'key' => 'data',
            'type' => 'longtext',
            'required' => false
        ],
        [
            'key' => 'age',
            'type' => 'integer',
            'required' => false
        ],
        [
            'key' => 'score',
            'type' => 'float',
            'required' => false
        ],
        [
            'key' => 'total_views',
            'type' => 'bigint',
            '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' => '<RELATED_TABLE_ID>',
            '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://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint
  .set_project('<PROJECT_ID>') # Your project ID
  .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key
)

tablesDB = TablesDB(client)

result = tablesDB.create_table(
    database_id='<DATABASE_ID>',
    table_id='<TABLE_ID>',
    name='<NAME>',
    columns=[
        {
            'key': 'email',
            'type': 'email',
            'required': True
        },
        {
            'key': 'name',
            'type': 'varchar',
            'size': 255,
            'required': True
        },
        {
            'key': 'bio',
            'type': 'text',
            'required': False
        },
        {
            'key': 'content',
            'type': 'mediumtext',
            'required': False
        },
        {
            'key': 'data',
            'type': 'longtext',
            'required': False
        },
        {
            'key': 'age',
            'type': 'integer',
            'required': False
        },
        {
            'key': 'score',
            'type': 'float',
            'required': False
        },
        {
            'key': 'total_views',
            'type': 'bigint',
            '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': '<RELATED_TABLE_ID>',
            '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://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint
    .set_project('<PROJECT_ID>') # Your project ID
    .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key

tablesDB = TablesDB.new(client)

response = tablesDB.create_table(
    database_id: '<DATABASE_ID>',
    table_id: '<TABLE_ID>',
    name: '<NAME>',
    columns: [
        {
            key: 'email',
            type: 'email',
            required: true
        },
        {
            key: 'name',
            type: 'varchar',
            size: 255,
            required: true
        },
        {
            key: 'bio',
            type: 'text',
            required: false
        },
        {
            key: 'content',
            type: 'mediumtext',
            required: false
        },
        {
            key: 'data',
            type: 'longtext',
            required: false
        },
        {
            key: 'age',
            type: 'integer',
            required: false
        },
        {
            key: 'score',
            type: 'float',
            required: false
        },
        {
            key: 'total_views',
            type: 'bigint',
            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: '<RELATED_TABLE_ID>',
            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://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .SetProject("<PROJECT_ID>") // Your project ID
    .SetKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key

var tablesDB = new TablesDB(client);

Table result = await tablesDB.CreateTable(
    databaseId: "<DATABASE_ID>",
    tableId: "<TABLE_ID>",
    name: "<NAME>",
    columns: new List<Dictionary<string, object>>
    {
        new Dictionary<string, object>
        {
            { "key", "email" },
            { "type", "email" },
            { "required", true }
        },
        new Dictionary<string, object>
        {
            { "key", "name" },
            { "type", "varchar" },
            { "size", 255 },
            { "required", true }
        },
        new Dictionary<string, object>
        {
            { "key", "bio" },
            { "type", "text" },
            { "required", false }
        },
        new Dictionary<string, object>
        {
            { "key", "content" },
            { "type", "mediumtext" },
            { "required", false }
        },
        new Dictionary<string, object>
        {
            { "key", "data" },
            { "type", "longtext" },
            { "required", false }
        },
        new Dictionary<string, object>
        {
            { "key", "age" },
            { "type", "integer" },
            { "required", false }
        },
        new Dictionary<string, object>
        {
            { "key", "score" },
            { "type", "float" },
            { "required", false }
        },
        new Dictionary<string, object>
        {
            { "key", "total_views" },
            { "type", "bigint" },
            { "required", false }
        },
        new Dictionary<string, object>
        {
            { "key", "is_active" },
            { "type", "boolean" },
            { "required", true }
        },
        new Dictionary<string, object>
        {
            { "key", "created_at" },
            { "type", "datetime" },
            { "required", false }
        },
        new Dictionary<string, object>
        {
            { "key", "status" },
            { "type", "enum" },
            { "elements", new List<string> { "draft", "published", "archived" } },
            { "required", true }
        },
        new Dictionary<string, object>
        {
            { "key", "ip_address" },
            { "type", "ip" },
            { "required", false }
        },
        new Dictionary<string, object>
        {
            { "key", "website" },
            { "type", "url" },
            { "required", false }
        },
        new Dictionary<string, object>
        {
            { "key", "location" },
            { "type", "point" },
            { "required", false }
        },
        new Dictionary<string, object>
        {
            { "key", "path" },
            { "type", "line" },
            { "required", false }
        },
        new Dictionary<string, object>
        {
            { "key", "area" },
            { "type", "polygon" },
            { "required", false }
        },
        new Dictionary<string, object>
        {
            { "key", "related_items" },
            { "type", "relationship" },
            { "relatedTableId", "<RELATED_TABLE_ID>" },
            { "relationType", "manyToMany" },
            { "twoWay", true },
            { "twoWayKey", "items" },
            { "onDelete", "cascade" },
            { "required", false }
        }
    },
    indexes: new List<Dictionary<string, object>>
    {
        new Dictionary<string, object>
        {
            { "key", "idx_email" },
            { "type", "unique" },
            { "attributes", new List<string> { "email" } }
        },
        new Dictionary<string, object>
        {
            { "key", "idx_name" },
            { "type", "key" },
            { "attributes", new List<string> { "name" } }
        },
        new Dictionary<string, object>
        {
            { "key", "idx_name_fulltext" },
            { "type", "fulltext" },
            { "attributes", new List<string> { "name" } }
        }
    });
```
```dart
import 'package:dart_appwrite/dart_appwrite.dart';

void main() { // Init SDK
  Client client = Client();
  Databases tablesDB = TablesDB(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 = tablesDB.createTable(
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    name: '<NAME>',
    columns: [
        {
            'key': 'email',
            'type': 'email',
            'required': true
        },
        {
            'key': 'name',
            'type': 'varchar',
            'size': 255,
            'required': true
        },
        {
            'key': 'bio',
            'type': 'text',
            'required': false
        },
        {
            'key': 'content',
            'type': 'mediumtext',
            'required': false
        },
        {
            'key': 'data',
            'type': 'longtext',
            'required': false
        },
        {
            'key': 'age',
            'type': 'integer',
            'required': false
        },
        {
            'key': 'score',
            'type': 'float',
            'required': false
        },
        {
            'key': 'total_views',
            'type': 'bigint',
            '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': '<RELATED_TABLE_ID>',
            '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://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<PROJECT_ID>") // Your project ID
    .setKey("919c2d18fb5d4...a2ae413da83346ad2") // Your secret API key

val tablesDB = TablesDB(client)

val response = tablesDB.createTable(
    databaseId = "<DATABASE_ID>",
    tableId = "<TABLE_ID>",
    name = "<NAME>",
    columns = listOf(
        mapOf(
            "key" to "email",
            "type" to "email",
            "required" to true
        ),
        mapOf(
            "key" to "name",
            "type" to "varchar",
            "size" to 255,
            "required" to true
        ),
        mapOf(
            "key" to "bio",
            "type" to "text",
            "required" to false
        ),
        mapOf(
            "key" to "content",
            "type" to "mediumtext",
            "required" to false
        ),
        mapOf(
            "key" to "data",
            "type" to "longtext",
            "required" to false
        ),
        mapOf(
            "key" to "age",
            "type" to "integer",
            "required" to false
        ),
        mapOf(
            "key" to "score",
            "type" to "float",
            "required" to false
        ),
        mapOf(
            "key" to "total_views",
            "type" to "bigint",
            "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 "<RELATED_TABLE_ID>",
            "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://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .setProject("<PROJECT_ID>") // Your project ID
    .setKey("919c2d18fb5d4...a2ae413da83346ad2"); // Your secret API key

Databases tablesDB = new TablesDB(client);

List<Map<String, Object>> columns = Arrays.asList(
    new HashMap<String, Object>() {{
        put("key", "email");
        put("type", "email");
        put("required", true);
    }},
    new HashMap<String, Object>() {{
        put("key", "name");
        put("type", "varchar");
        put("size", 255);
        put("required", true);
    }},
    new HashMap<String, Object>() {{
        put("key", "bio");
        put("type", "text");
        put("required", false);
    }},
    new HashMap<String, Object>() {{
        put("key", "content");
        put("type", "mediumtext");
        put("required", false);
    }},
    new HashMap<String, Object>() {{
        put("key", "data");
        put("type", "longtext");
        put("required", false);
    }},
    new HashMap<String, Object>() {{
        put("key", "age");
        put("type", "integer");
        put("required", false);
    }},
    new HashMap<String, Object>() {{
        put("key", "score");
        put("type", "float");
        put("required", false);
    }},
    new HashMap<String, Object>() {{
        put("key", "total_views");
        put("type", "bigint");
        put("required", false);
    }},
    new HashMap<String, Object>() {{
        put("key", "is_active");
        put("type", "boolean");
        put("required", true);
    }},
    new HashMap<String, Object>() {{
        put("key", "created_at");
        put("type", "datetime");
        put("required", false);
    }},
    new HashMap<String, Object>() {{
        put("key", "status");
        put("type", "enum");
        put("elements", Arrays.asList("draft", "published", "archived"));
        put("required", true);
    }},
    new HashMap<String, Object>() {{
        put("key", "ip_address");
        put("type", "ip");
        put("required", false);
    }},
    new HashMap<String, Object>() {{
        put("key", "website");
        put("type", "url");
        put("required", false);
    }},
    new HashMap<String, Object>() {{
        put("key", "location");
        put("type", "point");
        put("required", false);
    }},
    new HashMap<String, Object>() {{
        put("key", "path");
        put("type", "line");
        put("required", false);
    }},
    new HashMap<String, Object>() {{
        put("key", "area");
        put("type", "polygon");
        put("required", false);
    }},
    new HashMap<String, Object>() {{
        put("key", "related_items");
        put("type", "relationship");
        put("relatedTableId", "<RELATED_TABLE_ID>");
        put("relationType", "manyToMany");
        put("twoWay", true);
        put("twoWayKey", "items");
        put("onDelete", "cascade");
        put("required", false);
    }}
);

List<Map<String, Object>> indexes = Arrays.asList(
    new HashMap<String, Object>() {{
        put("key", "idx_email");
        put("type", "unique");
        put("attributes", Arrays.asList("email"));
    }},
    new HashMap<String, Object>() {{
        put("key", "idx_name");
        put("type", "key");
        put("attributes", Arrays.asList("name"));
    }},
    new HashMap<String, Object>() {{
        put("key", "idx_name_fulltext");
        put("type", "fulltext");
        put("attributes", Arrays.asList("name"));
    }}
);

tablesDB.createTable(
    "<DATABASE_ID>",
    "<TABLE_ID>",
    "<NAME>",
    columns,
    indexes,
    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 tablesDB = TablesDB(client)

let table = try await tablesDB.createTable(
    databaseId: "<DATABASE_ID>",
    tableId: "<TABLE_ID>",
    name: "<NAME>",
    columns: [
        [
            "key": "email",
            "type": "email",
            "required": true
        ],
        [
            "key": "name",
            "type": "varchar",
            "size": 255,
            "required": true
        ],
        [
            "key": "bio",
            "type": "text",
            "required": false
        ],
        [
            "key": "content",
            "type": "mediumtext",
            "required": false
        ],
        [
            "key": "data",
            "type": "longtext",
            "required": false
        ],
        [
            "key": "age",
            "type": "integer",
            "required": false
        ],
        [
            "key": "score",
            "type": "float",
            "required": false
        ],
        [
            "key": "total_views",
            "type": "bigint",
            "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": "<RELATED_TABLE_ID>",
            "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"]
        ]
    ]
)
```
```server-rust
use appwrite::Client;
use appwrite::services::tables_db::TablesDB;
use serde_json::json;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let 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

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

    let table = tables_db.create_table(
        "<DATABASE_ID>",
        "<TABLE_ID>",
        "<NAME>",
        None,       // permissions
        None,       // row_security
        None,       // enabled
        Some(vec![
            json!({
                "key": "email",
                "type": "email",
                "required": true
            }),
            json!({
                "key": "name",
                "type": "varchar",
                "size": 255,
                "required": true
            }),
            json!({
                "key": "bio",
                "type": "text",
                "required": false
            }),
            json!({
                "key": "content",
                "type": "mediumtext",
                "required": false
            }),
            json!({
                "key": "data",
                "type": "longtext",
                "required": false
            }),
            json!({
                "key": "age",
                "type": "integer",
                "required": false
            }),
            json!({
                "key": "score",
                "type": "float",
                "required": false
            }),
            json!({
                "key": "total_views",
                "type": "bigint",
                "required": false
            }),
            json!({
                "key": "is_active",
                "type": "boolean",
                "required": true
            }),
            json!({
                "key": "created_at",
                "type": "datetime",
                "required": false
            }),
            json!({
                "key": "status",
                "type": "enum",
                "elements": ["draft", "published", "archived"],
                "required": true
            }),
            json!({
                "key": "ip_address",
                "type": "ip",
                "required": false
            }),
            json!({
                "key": "website",
                "type": "url",
                "required": false
            }),
            json!({
                "key": "location",
                "type": "point",
                "required": false
            }),
            json!({
                "key": "path",
                "type": "line",
                "required": false
            }),
            json!({
                "key": "area",
                "type": "polygon",
                "required": false
            }),
            json!({
                "key": "related_items",
                "type": "relationship",
                "relatedTableId": "<RELATED_TABLE_ID>",
                "relationType": "manyToMany",
                "twoWay": true,
                "twoWayKey": "items",
                "onDelete": "cascade",
                "required": false
            }),
        ]),
        Some(vec![
            json!({
                "key": "idx_email",
                "type": "unique",
                "attributes": ["email"]
            }),
            json!({
                "key": "idx_name",
                "type": "key",
                "attributes": ["name"]
            }),
            json!({
                "key": "idx_name_fulltext",
                "type": "fulltext",
                "attributes": ["name"]
            }),
        ]),
    ).await?;

    println!("{:?}", table);
    Ok(())
}
```

You can also configure **permissions** in the `createTable` method. Learn more about the `createTable` method in the [API references](/docs/references).

**CLI**

**Before proceeding**

Ensure you [**install**](/docs/tooling/command-line/installation#getting-started) the CLI, [**log in**](/docs/tooling/command-line/installation#login) to your Appwrite account, and [**initialize**](/docs/tooling/command-line/installation#initialization) your Appwrite project.

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.

[Learn more about the CLI tables commands](/docs/tooling/command-line/tables#commands)

**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).

### 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
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 |
|--------------|------------------------------------------------------------------|
| `varchar` | Short text column. Fully indexable if size < 768. Maximum 16,383 characters. |
| `text` | Text column. Prefix indexing only. Maximum 16,383 characters. |
| `mediumtext` | Text column. Prefix indexing only. Maximum 4,194,303 characters. |
| `longtext` | Text column. Prefix indexing only. Maximum 1,073,741,823 characters. |
| `integer` | Integer column. 32-bit signed, range -2,147,483,648 to 2,147,483,647. |
| `bigint` | Big integer column. 64-bit signed, range -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807. Ideal for timestamps, large counters, or IDs that exceed 32-bit limits. |
| `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) |
| `string` | **Deprecated.** Use `varchar`, `text`, `mediumtext`, or `longtext` instead. |

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 text columns), and whether it is required or not after creation.

You can increase a varchar 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

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 text 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

URL: https://appwrite.io/docs/products/databases/tablesdb/timestamp-overrides

When creating or updating rows, 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.

**Server SDKs required**

To manually set `$createdAt` and `$updatedAt`, you must use a **server SDK** with an **API key**. These columns can be passed inside the `data` parameter on any of the create, update, or upsert routes (single or bulk).

### Setting custom timestamps

You can override a row's timestamps by providing ISO 8601 strings (for example, `2025-08-10T12:34:56.000Z`) in the `data` payload. If these columns are not provided, Appwrite will set them automatically.

Custom timestamps work with all row operations: create, update, upsert, and their bulk variants.

#### Single row operations

When working with individual rows, you can set custom timestamps during create, update, and upsert operations.

##### Create with custom timestamps

```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);

await tablesDB.createRow({
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    rowId: sdk.ID.unique(),
    data: {
        '$createdAt': new Date('2025-08-10T12:34:56.000Z').toISOString(),
        '$updatedAt': new Date('2025-08-10T12:34:56.000Z').toISOString(),
        // ...your columns
    }
});
```
```server-php
use Appwrite\Client;
use Appwrite\ID;
use Appwrite\Services\TablesDB;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<YOUR_PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$tablesDB = new TablesDB($client);

$tablesDB->createRow(
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    rowId: '<ROW_ID>',
    [
        '$createdAt' => (new DateTime('<CUSTOM_DATE>'))->format(DATE_ATOM),
        '$updatedAt' => (new DateTime('<CUSTOM_DATE>'))->format(DATE_ATOM),
        // ...your columns
    ]
);
```
```server-swift
import Appwrite
import Foundation

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<YOUR_PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let tablesDB = TablesDB(client)

let isoFormatter = ISO8601DateFormatter()
isoFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
let customDate = isoFormatter.date(from: "<CUSTOM_DATE>") ?? Date()
let createdAt = isoFormatter.string(from: customDate)
let updatedAt = isoFormatter.string(from: customDate)

do {
    let created = try await tablesDB.createRow(
        databaseId: "<DATABASE_ID>",
        tableId: "<TABLE_ID>",
        rowId: "<ROW_ID>",
        data: [
            "$createdAt": createdAt,
            "$updatedAt": updatedAt,
            // ...your columns
        ]
    )
    print("Created:", created)
} catch {
    print("Create error:", error)
}
```
```server-python
from appwrite.client import Client
from appwrite.services.tables_db import TablesDB
from appwrite.id import ID
from datetime import datetime, timezone

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<API_KEY>')

tablesDB = TablesDB(client)

iso = datetime(2025, 8, 10, 12, 34, 56, tzinfo=timezone.utc).isoformat()

tablesDB.create_row(
        database_id='<DATABASE_ID>',
        table_id='<TABLE_ID>',
        row_id=ID.unique(),
        data={
                '$createdAt': iso,
                '$updatedAt': iso,
                # ...your columns
        }
)
```
```server-ruby
require 'appwrite'
require 'time'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<YOUR_PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

tablesDB = TablesDB.new(client)

custom_date = Time.parse('2025-08-10T12:34:56.000Z').iso8601

tablesDB.create_row(
    database_id: '<DATABASE_ID>',
    table_id: '<TABLE_ID>',
    row_id: ID.unique(),
    data: {
        '$createdAt' => custom_date,
        '$updatedAt' => custom_date,
        # ...your columns
    }
)
```
```server-dotnet
using Appwrite;
using Appwrite.Models;
using Appwrite.Services;

Client client = new Client()
    .SetEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<YOUR_PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

TablesDB tablesDB = new TablesDB(client);

string customDate = DateTimeOffset.Parse("2025-08-10T12:34:56.000Z").ToString("O");

await tablesDB.CreateRow(
    databaseId: "<DATABASE_ID>",
    tableId: "<TABLE_ID>",
    rowId: ID.Unique(),
    data: new Dictionary<string, object>
    {
        ["$createdAt"] = customDate,
        ["$updatedAt"] = customDate,
        // ...your columns
    }
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<YOUR_PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

TablesDB tablesDB = TablesDB(client);

String customDate = DateTime.parse('2025-08-10T12:34:56.000Z').toIso8601String();

await tablesDB.createRow(
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    rowId: ID.unique(),
    data: {
        '\$createdAt': customDate,
        '\$updatedAt': customDate,
        // ...your columns
    },
);
```
```rust
use appwrite::Client;
use appwrite::services::tables_db::TablesDB;
use appwrite::id::ID;
use serde_json::json;

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

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

    let result = tablesDB.create_row(
        "<DATABASE_ID>",
        "<TABLE_ID>",
        &ID::unique(),
        json!({
            "$createdAt": "2025-08-10T12:34:56.000Z",
            "$updatedAt": "2025-08-10T12:34:56.000Z"
            // ...your columns
        }),
        None,
        None,
    ).await?;

    println!("Created: {:?}", result);
    Ok(())
}
```

##### Update with custom timestamps

When updating rows, you can also set a custom `$updatedAt` timestamp:

```server-nodejs
await tablesDB.updateRow({
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    rowId: '<ROW_ID>',
    data: {
        '$updatedAt': new Date('2025-08-10T12:34:56.000Z').toISOString(),
        // ...your columns
    }
});
```
```server-php
$tablesDB->updateRow(
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    rowId: '<ROW_ID>',
    [
        '$updatedAt' => (new DateTime('<CUSTOM_DATE>'))->format(DATE_ATOM),
        // ...your columns
    ]
);
```
```server-python
from datetime import datetime, timezone

tablesDB.update_row(
    database_id='<DATABASE_ID>',
    table_id='<TABLE_ID>',
    row_id='<ROW_ID>',
    data={
        '$updatedAt': datetime(2025, 8, 10, 12, 34, 56, tzinfo=timezone.utc).isoformat(),
        # ...your columns
    }
)
```
```server-swift
import Appwrite
import Foundation

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<YOUR_PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let tablesDB = TablesDB(client)

let isoFormatter = ISO8601DateFormatter()
isoFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
let updatedAt = isoFormatter.string(from: isoFormatter.date(from: "<CUSTOM_DATE>") ?? Date())

do {
    let updated = try await tablesDB.updateRow(
        databaseId: "<DATABASE_ID>",
        tableId: "<TABLE_ID>",
        rowId: "<ROW_ID>",
        data: [
            "$updatedAt": updatedAt,
            // ...your columns
        ]
    )
    print("Updated:", updated)
} catch {
    print("Update error:", error)
}
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<YOUR_PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

tablesDB = TablesDB.new(client)

custom_date = Time.parse('<CUSTOM_DATE>').iso8601

tablesDB.update_row(
  database_id: '<DATABASE_ID>',
  table_id: '<TABLE_ID>',
  row_id: '<ROW_ID>',
  data: {
    '$updatedAt' => custom_date,
    # ...your columns
  }
)
```
```server-dotnet
using Appwrite;
using Appwrite.Models;
using Appwrite.Services;

Client client = new Client()
    .SetEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<YOUR_PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

TablesDB tablesDB = new TablesDB(client);

string customDate = DateTimeOffset.Parse("<CUSTOM_DATE>").ToString("O");

await tablesDB.UpdateRow(
    databaseId: "<DATABASE_ID>",
    tableId: "<TABLE_ID>",
    rowId: "<ROW_ID>",
    data: new Dictionary<string, object>
    {
        ["$updatedAt"] = customDate,
        // ...your columns
    }
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<YOUR_PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

TablesDB tablesDB = TablesDB(client);

String customDate = DateTime.parse('<CUSTOM_DATE>').toIso8601String();

await tablesDB.updateRow(
  databaseId: '<DATABASE_ID>',
  tableId: '<TABLE_ID>',
  rowId: '<ROW_ID>',
  data: {
    '\$updatedAt': customDate,
    // ...your columns
  },
);
```
```rust
use appwrite::Client;
use appwrite::services::tables_db::TablesDB;
use serde_json::json;

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

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

    let result = tablesDB.update_row(
        "<DATABASE_ID>",
        "<TABLE_ID>",
        "<ROW_ID>",
        Some(json!({
            "$updatedAt": "2025-08-10T12:34:56.000Z"
            // ...your columns
        })),
        None,
        None,
    ).await?;

    println!("Updated: {:?}", result);
    Ok(())
}
```

#### Bulk operations

Custom timestamps also work with bulk operations, allowing you to set different timestamps for each row in the batch:

##### Bulk create

```server-nodejs
await tablesDB.createRows({
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    rows: [
        {
            '$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 columns
        },
        {
            '$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 columns
        }
    ]
});
```
```server-python
tablesDB.create_rows(
        database_id='<DATABASE_ID>',
        table_id='<TABLE_ID>',
        rows=[
            {
                '$id': ID.unique(),
                '$createdAt': datetime(2024, 1, 1, tzinfo=timezone.utc).isoformat(),
                '$updatedAt': datetime(2024, 1, 1, tzinfo=timezone.utc).isoformat(),
                # ...your columns
            },
            {
                '$id': ID.unique(),
                '$createdAt': datetime(2024, 2, 1, tzinfo=timezone.utc).isoformat(),
                '$updatedAt': datetime(2024, 2, 1, tzinfo=timezone.utc).isoformat(),
                # ...your columns
            }
        ]
)
```
```server-php
use Appwrite\Client;
use Appwrite\ID;
use Appwrite\Services\TablesDB;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<YOUR_PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$tablesDB = new TablesDB($client);

$tablesDB->createRows(
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    rows: [
        [
            '$id' => ID::unique(),
            '$createdAt' => (new DateTime('<CUSTOM_DATE>'))->format(DATE_ATOM),
            '$updatedAt' => (new DateTime('<CUSTOM_DATE>'))->format(DATE_ATOM),
            // ...your columns
        ],
        [
            '$id' => ID::unique(),
            '$createdAt' => (new DateTime('<CUSTOM_DATE>'))->format(DATE_ATOM),
            '$updatedAt' => (new DateTime('<CUSTOM_DATE>'))->format(DATE_ATOM),
            // ...your columns
        ],
    ]
);
```
```server-swift
import Appwrite
import Foundation

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<YOUR_PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let tablesDB = TablesDB(client)

let isoFormatter = ISO8601DateFormatter()
isoFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]

let first = isoFormatter.string(from: isoFormatter.date(from: "<CUSTOM_DATE>") ?? Date())
let second = isoFormatter.string(from: isoFormatter.date(from: "<CUSTOM_DATE>") ?? Date())

do {
    let bulkCreated = try await tablesDB.createRows(
        databaseId: "<DATABASE_ID>",
        tableId: "<TABLE_ID>",
        rows: [
            [
                "$id": ID.unique(),
                "$createdAt": first,
                "$updatedAt": first,
                // ...your columns
            ],
            [
                "$id": ID.unique(),
                "$createdAt": second,
                "$updatedAt": second,
                // ...your columns
            ]
        ]
    )
    print("Bulk create:", bulkCreated)
} catch {
    print("Bulk create error:", error)
}
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<YOUR_PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

tablesDB = TablesDB.new(client)

first = Time.parse('<CUSTOM_DATE>').iso8601
second = Time.parse('<CUSTOM_DATE>').iso8601

tablesDB.create_rows(
  database_id: '<DATABASE_ID>',
  table_id: '<TABLE_ID>',
  rows: [
    {
      '$id' => ID.unique(),
      '$createdAt' => first,
      '$updatedAt' => first,
      # ...your columns
    },
    {
      '$id' => ID.unique(),
      '$createdAt' => second,
      '$updatedAt' => second,
      # ...your columns
    }
  ]
)
```
```server-dotnet
using Appwrite;
using Appwrite.Models;
using Appwrite.Services;

Client client = new Client()
    .SetEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<YOUR_PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

TablesDB tablesDB = new TablesDB(client);

string first = DateTimeOffset.Parse("<CUSTOM_DATE>").ToString("O");
string second = DateTimeOffset.Parse("<CUSTOM_DATE>").ToString("O");

await tablesDB.CreateRows(
    databaseId: "<DATABASE_ID>",
    tableId: "<TABLE_ID>",
    rows: new List<object>
    {
        new Dictionary<string, object>
        {
            ["$id"] = ID.Unique(),
            ["$createdAt"] = first,
            ["$updatedAt"] = first,
            // ...your columns
        },
        new Dictionary<string, object>
        {
            ["$id"] = ID.Unique(),
            ["$createdAt"] = second,
            ["$updatedAt"] = second,
            // ...your columns
        }
    }
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<YOUR_PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

TablesDB tablesDB = TablesDB(client);

String first = DateTime.parse('<CUSTOM_DATE>').toIso8601String();
String second = DateTime.parse('<CUSTOM_DATE>').toIso8601String();

await tablesDB.createRows(
  databaseId: '<DATABASE_ID>',
  tableId: '<TABLE_ID>',
  rows: [
    {
      '\$id': ID.unique(),
      '\$createdAt': first,
      '\$updatedAt': first,
      // ...your columns
    },
    {
      '\$id': ID.unique(),
      '\$createdAt': second,
      '\$updatedAt': second,
      // ...your columns
    }
  ],
);
```
```rust
use appwrite::Client;
use appwrite::services::tables_db::TablesDB;
use appwrite::id::ID;
use serde_json::json;

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

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

    let result = tablesDB.create_rows(
        "<DATABASE_ID>",
        "<TABLE_ID>",
        vec![
            json!({
                "$id": ID::unique(),
                "$createdAt": "2024-01-01T00:00:00.000Z",
                "$updatedAt": "2024-01-01T00:00:00.000Z"
                // ...your columns
            }),
            json!({
                "$id": ID::unique(),
                "$createdAt": "2024-02-01T00:00:00.000Z",
                "$updatedAt": "2024-02-01T00:00:00.000Z"
                // ...your columns
            }),
        ],
        None,
    ).await?;

    println!("Bulk create: {:?}", result);
    Ok(())
}
```

##### Bulk upsert

```server-nodejs
await tablesDB.upsertRows({
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    rows: [
        {
            '$id': '<ROW_ID_OR_NEW_ID>',
            '$createdAt': new Date('2024-01-01T00:00:00.000Z').toISOString(),
            '$updatedAt': new Date('2025-01-01T00:00:00.000Z').toISOString(),
            // ...your columns
        }
    ]
});
```
```server-python
tablesDB.upsert_rows(
    database_id='<DATABASE_ID>',
    table_id='<TABLE_ID>',
    rows=[
        {
            '$id': '<ROW_ID_OR_NEW_ID>',
            '$createdAt': datetime(2024, 1, 1, tzinfo=timezone.utc).isoformat(),
            '$updatedAt': datetime(2025, 1, 1, tzinfo=timezone.utc).isoformat(),
            # ...your columns
        }
    ]
)
```
```server-php
use Appwrite\Client;
use Appwrite\ID;
use Appwrite\Services\TablesDB;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<YOUR_PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$tablesDB = new TablesDB($client);

$tablesDB->upsertRows(
    databaseId: '<DATABASE_ID>',
    tableId: '<TABLE_ID>',
    rows: [
        [
            '$id' => '<ROW_ID_OR_NEW_ID>',
            '$createdAt' => (new DateTime('<CUSTOM_DATE>'))->format(DATE_ATOM),
            '$updatedAt' => (new DateTime('<CUSTOM_DATE>'))->format(DATE_ATOM),
            // ...your columns
        ],
    ]
);
```
```server-swift
import Appwrite
import Foundation

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<YOUR_PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let tablesDB = TablesDB(client)

let isoFormatter = ISO8601DateFormatter()
isoFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
let createdAt = isoFormatter.string(from: isoFormatter.date(from: "<CUSTOM_DATE>") ?? Date())
let updatedAt = isoFormatter.string(from: isoFormatter.date(from: "<CUSTOM_DATE>") ?? Date())

do {
    let bulkUpserted = try await tablesDB.upsertRows(
        databaseId: "<DATABASE_ID>",
        tableId: "<TABLE_ID>",
        rows: [
            [
                "$id": "<ROW_ID_OR_NEW_ID>",
                "$createdAt": createdAt,
                "$updatedAt": updatedAt,
                // ...your columns
            ]
        ]
    )
    print("Bulk upsert:", bulkUpserted)
} catch {
    print("Bulk upsert error:", error)
}
```
```server-ruby
require 'appwrite'
require 'time'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<YOUR_PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

tablesDB = TablesDB.new(client)

custom_date = Time.parse('<CUSTOM_DATE>').iso8601

tablesDB.upsert_rows(
  database_id: '<DATABASE_ID>',
  table_id: '<TABLE_ID>',
  rows: [
    {
      '$id' => '<ROW_ID_OR_NEW_ID>',
      '$createdAt' => custom_date,
      '$updatedAt' => custom_date,
      # ...your columns
    }
  ]
)
```
```server-dotnet
using Appwrite;
using Appwrite.Models;
using Appwrite.Services;

Client client = new Client()
    .SetEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<YOUR_PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

TablesDB tablesDB = new TablesDB(client);

string createdAt = DateTimeOffset.Parse("<CUSTOM_DATE>").ToString("O");
string updatedAt = DateTimeOffset.Parse("<CUSTOM_DATE>").ToString("O");

await tablesDB.UpsertRows(
    databaseId: "<DATABASE_ID>",
    tableId: "<TABLE_ID>",
    rows: new List<object>
    {
        new Dictionary<string, object>
        {
            ["$id"] = "<ROW_ID_OR_NEW_ID>",
            ["$createdAt"] = createdAt,
            ["$updatedAt"] = updatedAt,
            // ...your columns
        }
    }
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<YOUR_PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

TablesDB tablesDB = TablesDB(client);

String createdAt = DateTime.parse('<CUSTOM_DATE>').toIso8601String();
String updatedAt = DateTime.parse('<CUSTOM_DATE>').toIso8601String();

await tablesDB.upsertRows(
  databaseId: '<DATABASE_ID>',
  tableId: '<TABLE_ID>',
  rows: [
    {
      '\$id': '<ROW_ID_OR_NEW_ID>',
      '\$createdAt': createdAt,
      '\$updatedAt': updatedAt,
      // ...your columns
    }
  ],
);
```
```rust
use appwrite::Client;
use appwrite::services::tables_db::TablesDB;
use serde_json::json;

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

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

    let result = tablesDB.upsert_rows(
        "<DATABASE_ID>",
        "<TABLE_ID>",
        vec![
            json!({
                "$id": "<ROW_ID_OR_NEW_ID>",
                "$createdAt": "2024-01-01T00:00:00.000Z",
                "$updatedAt": "2025-01-01T00:00:00.000Z"
                // ...your columns
            }),
        ],
        None,
    ).await?;

    println!("Bulk upsert: {:?}", result);
    Ok(())
}
```

### Common use cases

Custom timestamps are particularly useful in several scenarios:

#### Data migration
When migrating existing data from another system, you can preserve the original
creation and modification times:

```server-nodejs
await tablesDB.createRow({
    databaseId: '<DATABASE_ID>',
    tableId: 'blog_posts',
    rowId: sdk.ID.unique(),
    data: {
    '$createdAt': '<ORIGINAL_CREATED_AT_ISO>',
    '$updatedAt': '<LAST_MODIFIED_ISO>',
    title: '<TITLE>',
    content: '<CONTENT>'
  }
})
```
```server-php
$tablesDB->createRow(
  databaseId: '<DATABASE_ID>',
  tableId: 'blog_posts',
  rowId: ID::unique(),
  [
    '$createdAt' => '<ORIGINAL_CREATED_AT_ISO>',
    '$updatedAt' => '<LAST_MODIFIED_ISO>',
    'title' => '<TITLE>',
    'content' => '<CONTENT>'
  ]
);
```
```server-swift
let _ = try await tablesDB.createRow(
  databaseId: "<DATABASE_ID>",
  tableId: "blog_posts",
  rowId: ID.unique(),
  data: [
    "$createdAt": "<ORIGINAL_CREATED_AT_ISO>",
    "$updatedAt": "<LAST_MODIFIED_ISO>",
    "title": "<TITLE>",
    "content": "<CONTENT>"
  ]
)
```
```server-python
tablesDB.create_row(
  database_id='<DATABASE_ID>',
  table_id='blog_posts',
  row_id=ID.unique(),
  data={
    '$createdAt': '<ORIGINAL_CREATED_AT_ISO>',
    '$updatedAt': '<LAST_MODIFIED_ISO>',
    'title': '<TITLE>',
    'content': '<CONTENT>'
  }
)
```
```server-ruby
tablesDB.create_row(
  database_id: '<DATABASE_ID>',
  table_id: 'blog_posts',
  row_id: ID.unique(),
  data: {
    '$createdAt' => '<ORIGINAL_CREATED_AT_ISO>',
    '$updatedAt' => '<LAST_MODIFIED_ISO>',
    'title' => '<TITLE>',
    'content' => '<CONTENT>'
  }
)
```
```server-dotnet
await tablesDB.CreateRow(
  databaseId: "<DATABASE_ID>",
  tableId: "blog_posts",
  rowId: ID.Unique(),
  data: new Dictionary<string, object>
  {
    ["$createdAt"] = "<ORIGINAL_CREATED_AT_ISO>",
    ["$updatedAt"] = "<LAST_MODIFIED_ISO>",
    ["title"] = "<TITLE>",
    ["content"] = "<CONTENT>"
  }
);
```
```server-dart
await tablesDB.createRow(
  databaseId: '<DATABASE_ID>',
  tableId: 'blog_posts',
  rowId: ID.unique(),
  data: {
    '\$createdAt': '<ORIGINAL_CREATED_AT_ISO>',
    '\$updatedAt': '<LAST_MODIFIED_ISO>',
    'title': '<TITLE>',
    'content': '<CONTENT>'
  },
);
```
```rust
use appwrite::Client;
use appwrite::services::tables_db::TablesDB;
use appwrite::id::ID;
use serde_json::json;

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

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

    let result = tablesDB.create_row(
        "<DATABASE_ID>",
        "blog_posts",
        &ID::unique(),
        json!({
            "$createdAt": "<ORIGINAL_CREATED_AT_ISO>",
            "$updatedAt": "<LAST_MODIFIED_ISO>",
            "title": "<TITLE>",
            "content": "<CONTENT>"
        }),
        None,
        None,
    ).await?;

    println!("Created: {:?}", result);
    Ok(())
}
```

#### Backdating records
For historical data entry or when creating records that represent past events:

```server-nodejs
await tablesDB.createRow({
    databaseId: '<DATABASE_ID>',
    tableId: 'transactions',
    rowId: sdk.ID.unique(),
    data: {
    '$createdAt': '2023-12-31T23:59:59.000Z',
    '$updatedAt': '2023-12-31T23:59:59.000Z',
    amount: 1000,
    type: 'year-end-bonus'
  }
})
```
```server-php
$tablesDB->createRow(
  databaseId: '<DATABASE_ID>',
  tableId: 'transactions',
  rowId: 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 tablesDB.createRow(
  databaseId: "<DATABASE_ID>",
  tableId: "transactions",
  rowId: 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
tablesDB.create_row(
  database_id='<DATABASE_ID>',
  table_id='transactions',
  row_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
tablesDB.create_row(
  database_id: '<DATABASE_ID>',
  table_id: 'transactions',
  row_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 tablesDB.CreateRow(
  databaseId: "<DATABASE_ID>",
  tableId: "transactions",
  rowId: 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 tablesDB.createRow(
  databaseId: '<DATABASE_ID>',
  tableId: 'transactions',
  rowId: ID.unique(),
  data: {
    '\$createdAt': '2023-12-31T23:59:59.000Z',
    '\$updatedAt': '2023-12-31T23:59:59.000Z',
    'amount': 1000,
    'type': 'year-end-bonus'
  },
);
```
```rust
use appwrite::Client;
use appwrite::services::tables_db::TablesDB;
use appwrite::id::ID;
use serde_json::json;

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

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

    let result = tablesDB.create_row(
        "<DATABASE_ID>",
        "transactions",
        &ID::unique(),
        json!({
            "$createdAt": "2023-12-31T23:59:59.000Z",
            "$updatedAt": "2023-12-31T23:59:59.000Z",
            "amount": 1000,
            "type": "year-end-bonus"
        }),
        None,
        None,
    ).await?;

    println!("Created: {:?}", result);
    Ok(())
}
```

#### Synchronization
When synchronizing data between systems while maintaining timestamp consistency:

```server-nodejs
await tablesDB.upsertRow({
    databaseId: '<DATABASE_ID>',
    tableId: 'users',
    rowId: '<ROW_ID_OR_NEW_ID>',
    data: {
    '$updatedAt': '<EXTERNAL_LAST_MODIFIED_ISO>',
    profile: '<PROFILE_DATA>'
  }
})
```
```server-php
$tablesDB->upsertRow(
  databaseId: '<DATABASE_ID>',
  tableId: 'users',
  rowId: '<ROW_ID_OR_NEW_ID>',
  [
    '$updatedAt' => '<EXTERNAL_LAST_MODIFIED_ISO>',
    'profile' => '<PROFILE_DATA>'
  ]
);
```
```server-swift
let _ = try await tablesDB.upsertRow(
  databaseId: "<DATABASE_ID>",
  tableId: "users",
  rowId: "<ROW_ID_OR_NEW_ID>",
  data: [
    "$updatedAt": "<EXTERNAL_LAST_MODIFIED_ISO>",
    "profile": "<PROFILE_DATA>"
  ]
)
```
```server-python
tablesDB.upsert_row(
  database_id='<DATABASE_ID>',
  table_id='users',
  row_id='<ROW_ID_OR_NEW_ID>',
  data={
    '$updatedAt': '<EXTERNAL_LAST_MODIFIED_ISO>',
    'profile': '<PROFILE_DATA>'
  }
)
```
```server-ruby
tablesDB.upsert_row(
  database_id: '<DATABASE_ID>',
  table_id: 'users',
  row_id: '<ROW_ID_OR_NEW_ID>',
  data: {
    '$updatedAt' => '<EXTERNAL_LAST_MODIFIED_ISO>',
    'profile' => '<PROFILE_DATA>'
  }
)
```
```server-dotnet
await tablesDB.UpsertRow(
  databaseId: "<DATABASE_ID>",
  tableId: "users",
  rowId: "<ROW_ID_OR_NEW_ID>",
  data: new Dictionary<string, object>
  {
    ["$updatedAt"] = "<EXTERNAL_LAST_MODIFIED_ISO>",
    ["profile"] = "<PROFILE_DATA>"
  }
);
```
```server-dart
await tablesDB.upsertRow(
  databaseId: '<DATABASE_ID>',
  tableId: 'users',
  rowId: '<ROW_ID_OR_NEW_ID>',
  data: {
    '\$updatedAt': '<EXTERNAL_LAST_MODIFIED_ISO>',
    'profile': '<PROFILE_DATA>'
  },
);
```
```rust
use appwrite::Client;
use appwrite::services::tables_db::TablesDB;
use serde_json::json;

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

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

    let result = tablesDB.upsert_row(
        "<DATABASE_ID>",
        "users",
        "<ROW_ID_OR_NEW_ID>",
        Some(json!({
            "$updatedAt": "<EXTERNAL_LAST_MODIFIED_ISO>",
            "profile": "<PROFILE_DATA>"
        })),
        None,
        None,
    ).await?;

    println!("Upserted: {:?}", result);
    Ok(())
}
```

**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 columns as needed. If omitted, Appwrite sets them automatically.

---

## Transactions

URL: https://appwrite.io/docs/products/databases/tablesdb/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

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.

**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.

### 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

Call the `createTransaction` method to begin. It returns a transaction model that includes `$id`. Pass this ID as `transactionId` to subsequent operations.

```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);
}));
```
```server-rust
use appwrite::Client;
use appwrite::services::tables_db::TablesDB;

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

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

    let tx = tables_db.create_transaction(None).await?;
    // tx.id is your transaction_id

    Ok(())
}
```

### 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

Create, update, upsert, delete, and atomic numeric operations accept `transactionId`, as well as their bulk versions (createRows, updateRows, upsertRows, deleteRows).

```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
)
```
```server-rust
use appwrite::Client;
use appwrite::services::tables_db::TablesDB;
use serde_json::json;

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

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

    let tx = tables_db.create_transaction(None).await?;

    // Update inside a transaction
    tables_db.update_row(
        "<DATABASE_ID>",
        "<TABLE_ID>",
        "<ROW_ID>",
        Some(json!({ "plan": "pro" })),
        None,
        Some(&tx.id),
    ).await?;

    // Delete inside a transaction
    tables_db.delete_row(
        "<DATABASE_ID>",
        "<TABLE_ID>",
        "<ROW_ID>",
        Some(&tx.id),
    ).await?;

    Ok(())
}
```

#### Stage many with createOperations

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": "<TABLE_ID>",
    "rowId": "<ROW_ID>",
    "data": {}
  }
]
```

##### Provide data for each action (createOperations)

##### Create, update, and upsert
Pass a raw data object.
```json
{ "name": "Walter" }
```

##### Increment and decrement
Pass a value and optionally `min`/`max` bounds.
```json
{ "value": 1, "min": 0, "max": 1000, "column": "<COLUMN_NAME>" }
```

##### Bulk create and bulk upsert
Pass an array of raw data objects.
```json
[{ "$id": "123", "name": "Walter" }]
```

##### Bulk update
Pass queries and the data to apply.
```json
{ "queries": [{"method": "equal", "attribute": "status", "values": ["draft"]}], "data": { "status": "published" } }
```

##### Bulk delete
Pass queries to select rows to delete.
```json
{ "queries": [{"method": "equal", "attribute": "archived", "values": [true]}] }
```

```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"]
    ]
  ]
)
```
```server-rust
use appwrite::Client;
use appwrite::services::tables_db::TablesDB;
use serde_json::json;

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

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

    let tx = tables_db.create_transaction(None).await?;

    // Stage multiple operations at once
    tables_db.create_operations(
        &tx.id,
        Some(vec![
            json!({
                "action": "create",
                "databaseId": "<DB_A>",
                "tableId": "<TABLE_1>",
                "rowId": "u1",
                "data": { "name": "Walter" }
            }),
            json!({
                "action": "increment",
                "databaseId": "<DB_B>",
                "tableId": "<TABLE_2>",
                "rowId": "u2",
                "data": { "value": 1, "min": 0, "column": "credits" }
            }),
        ]),
    ).await?;

    Ok(())
}
```

### Commit or roll back

When you are done staging operations, call the `updateTransaction` method to finalize the transaction.

```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
);
```
```server-rust
use appwrite::Client;
use appwrite::services::tables_db::TablesDB;

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

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

    // Commit
    tables_db.update_transaction(
        "<TRANSACTION_ID>",
        Some(true),
        None,
    ).await?;

    // Roll back
    tables_db.update_transaction(
        "<TRANSACTION_ID>",
        None,
        Some(true),
    ).await?;

    Ok(())
}
```

### 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.

**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.

[Explore the API references](/docs/references)

---

## Type generation

URL: https://appwrite.io/docs/products/databases/tablesdb/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": "varchar",
                    "required": true,
                    "array": false,
                    "size": 255,
                    "default": null
                },
                {
                    "key": "author",
                    "type": "varchar",
                    "required": true,
                    "array": false,
                    "size": 255,
                    "default": null
                },
                {
                    "key": "release_year",
                    "type": "datetime",
                    "required": false,
                    "array": false,
                    "format": "",
                    "default": null
                },
                {
                    "key": "category",
                    "type": "varchar",
                    "required": false,
                    "array": false,
                    "elements": [
                        "fiction",
                        "nonfiction"
                    ],
                    "format": "enum",
                    "default": null
                },
                {
                    "key": "genre",
                    "type": "varchar",
                    "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:

**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;
}
```

**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
 */
```

**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 +
                '}';
    }
}
```

**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;
  }
}
```

**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,
    };
  }
}
```

**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,
)
```

**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
        )
    }
}
```

---

## VectorsDB

URL: https://appwrite.io/docs/products/databases/vectorsdb

Appwrite VectorsDB lets you store vector embeddings and run similarity search over them.
A collection is created with a fixed `dimension`, every document holds an `embeddings` vector of that length plus optional `metadata`, and an HNSW index keeps similarity search fast as your data grows.

**Looking for file storage?**

Databases store data, if you need to store files like images, PDFs or videos, use [Appwrite Storage](/docs/products/storage).

You organize data into databases, collections, and documents, the same way you do across Appwrite Databases. What sets VectorsDB apart is the fixed schema built for vectors and the ability to generate text embeddings and search by similarity.

[Quick start](/docs/products/databases/vectorsdb/quick-start)

### Key concepts

- [Databases](/docs/products/databases/vectorsdb/databases): Create and manage vector databases, the largest organizational unit.
- [Collections](/docs/products/databases/vectorsdb/collections): Created with a required `dimension`, the length of the vectors they hold, plus an optional `metadata` object per document.
- [Documents](/docs/products/databases/vectorsdb/documents): Store one embedding under `embeddings` and any associated JSON under `metadata`.
- [Embeddings](/docs/products/databases/vectorsdb/embeddings): Generate embeddings from text with built-in models, without a separate embedding service.
- [Permissions](/docs/products/databases/vectorsdb/permissions): Control access at the collection and document level.
- [Queries](/docs/products/databases/vectorsdb/queries): Filter documents by their `metadata` with comparison, string, and logical operators.
- [Order](/docs/products/databases/vectorsdb/order): Sort results by metadata fields.
- [Backups](/docs/products/databases/vectorsdb/backups): Schedule backup policies and restore from any backup.

### Guides

- [Vector search](/docs/products/databases/vectorsdb/vector-search): Create an HNSW index on `embeddings`, then rank documents by cosine, dot product, or Euclidean distance.
- [Pagination](/docs/products/databases/vectorsdb/pagination): Page through results with offset or cursor pagination.
- [Transactions](/docs/products/databases/vectorsdb/transactions): Stage operations and commit or roll them back atomically.
- [Bulk operations](/docs/products/databases/vectorsdb/bulk-operations): Create, update, upsert, or delete many documents in one request.
- [Timestamp overrides](/docs/products/databases/vectorsdb/timestamp-overrides): Set custom creation and update timestamps during imports.
- [CSV imports](/docs/products/databases/vectorsdb/csv-imports): Import documents from CSV files.
- [CSV exports](/docs/products/databases/vectorsdb/csv-exports): Export collections to CSV files.

---

## Backups

URL: https://appwrite.io/docs/products/databases/vectorsdb/backups

Appwrite Backups enable seamless, **encrypted** database backups.
All backups are **hot** backups, ensuring zero downtime and fast recovery.

**Backups are available for all Pro and Enterprise customers.**

You manage backups from a database's **Backups** tab, where you can automate backups with policies or create manual backups on demand. A backup captures the database along with its collections, documents, and embeddings.

![Backups tab](/images/docs/products/databases/vectorsdb/backups-tab.avif)

### Backup policies

Backup policies automate your backups on a schedule. To create one, open your database's **Backups** tab and click **Create policy**, then choose a preset policy or add a custom one.

![Create backup policy](/images/docs/databases/documentsdb/backup-policy.avif)

The available options depend on your plan:

- On the **Pro** plan, you get a **Daily** backup policy retained for 7 days.
- On the **Enterprise** plan, you get access to additional preset policies and custom policies, where you control how often backups run and how long they are retained.

Click **Create** to save the policy. Your database is now set up for automated backups.

### Manual backups

You can create an on-demand backup whenever necessary. In your database's **Backups** tab, click **Manual backup**, then click **Create**.

![Manual backup](/images/docs/databases/documentsdb/manual-backup.avif)

Manual backups are retained until you delete them. Depending on the size of your database, the backup may take some time to complete. You can monitor its progress in the backups list.

### Restoring backups

To restore a database, you need an existing backup.

1. Open your database's **Backups** tab.
2. In the backups list, open the **Actions** menu for the backup you want to restore.
3. Click **Restore**.
4. Enter a name for the new database and an optional database ID.
5. Click **Restore**.

Depending on the size of your database, the restoration may take some time. The restore creates a new database from the backup, leaving the original untouched.

### Backup security & 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

To ensure your backups are robust and effective, consider the following best practices:

1. **Schedule regular backups**:
   Add backup policies based on the frequency of database changes. Daily 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.

---

## Bulk operations

URL: https://appwrite.io/docs/products/databases/vectorsdb/bulk-operations

Appwrite VectorsDB 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.

Each document's data follows the fixed schema provisioned by its collection: an `embeddings` vector whose length must equal the collection's `dimension`, and an optional free-form `metadata` object. The examples on this page use a collection with `dimension: 4` to keep the arrays readable.

**Important notes**

Bulk operations trigger Functions, Webhooks, or Realtime events for each document manipulated. Rather than a single event for the entire bulk operation, each document generates a separate event on the existing realtime channels for its operation type.

### 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.

### Plan limits

Bulk operations have different limits based on your Appwrite plan:

| Plan | Documents per request |
|------|----------------------|
| Free | 100 |
| Pro | 1,000 |

These limits apply to all bulk operations including create, update, upsert, and delete operations. If you need higher limits than what the Pro plan offers, you can [inquire](/contact-us/enterprise) about a custom plan.

### Create documents

You can create multiple documents in a single request using the `createDocuments` method.

**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.

```server-nodejs
const sdk = require('node-appwrite');

const client = new sdk.Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<YOUR_PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const vectorsDB = new sdk.VectorsDB(client);

const result = await vectorsDB.createDocuments({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documents: [
        {
            $id: sdk.ID.unique(),
            embeddings: [0.12, 0.84, 0.33, 0.57],
            metadata: { title: 'Hamlet', genre: 'tragedy' }
        },
        {
            $id: sdk.ID.unique(),
            embeddings: [0.91, 0.22, 0.14, 0.65],
            metadata: { title: 'Macbeth', genre: 'tragedy' }
        }
    ]
});
```

```python
from appwrite.client import Client
from appwrite.services.vectors_db import VectorsDB
from appwrite.id import ID

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<YOUR_PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

vectors_db = VectorsDB(client)

result = vectors_db.create_documents(
    database_id = '<DATABASE_ID>',
    collection_id = '<COLLECTION_ID>',
    documents = [
        {
            '$id': ID.unique(),
            'embeddings': [0.12, 0.84, 0.33, 0.57],
            'metadata': { 'title': 'Hamlet', 'genre': 'tragedy' }
        },
        {
            '$id': ID.unique(),
            'embeddings': [0.91, 0.22, 0.14, 0.65],
            'metadata': { 'title': 'Macbeth', 'genre': 'tragedy' }
        }
    ]
)
```

```server-rust
use appwrite::Client;
use appwrite::services::VectorsDB;
use appwrite::id::ID;
use serde_json::json;

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

    let vectors_db = VectorsDB::new(&client);

    let result = vectors_db.create_documents(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
        vec![
            json!({
                "$id": ID::unique(),
                "embeddings": [0.12, 0.84, 0.33, 0.57],
                "metadata": { "title": "Hamlet", "genre": "tragedy" }
            }),
            json!({
                "$id": ID::unique(),
                "embeddings": [0.91, 0.22, 0.14, 0.65],
                "metadata": { "title": "Macbeth", "genre": "tragedy" }
            }),
        ],
    ).await?;

    Ok(())
}
```

### Update documents

**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/vectorsdb/permissions)

You can update multiple documents in a single request using the `updateDocuments` method. Pass the fields to change in `data`, and use `queries` to select which documents are affected.

```server-nodejs
const sdk = require('node-appwrite');

const client = new sdk.Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<YOUR_PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const vectorsDB = new sdk.VectorsDB(client);

const result = await vectorsDB.updateDocuments({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    data: {
        metadata: { genre: 'drama' }
    },
    queries: [
        sdk.Query.equal('metadata.genre', 'tragedy')
    ]
});
```

```python
from appwrite.client import Client
from appwrite.services.vectors_db import VectorsDB
from appwrite.query import Query

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<YOUR_PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

vectors_db = VectorsDB(client)

result = vectors_db.update_documents(
    database_id = '<DATABASE_ID>',
    collection_id = '<COLLECTION_ID>',
    data = {
        'metadata': { 'genre': 'drama' }
    },
    queries = [
        Query.equal('metadata.genre', 'tragedy')
    ]
)
```

```server-rust
use appwrite::Client;
use appwrite::services::VectorsDB;
use appwrite::query::Query;
use serde_json::json;

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

    let vectors_db = VectorsDB::new(&client);

    let result = vectors_db.update_documents(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
        Some(json!({
            "metadata": { "genre": "drama" }
        })),
        Some(vec![
            Query::equal("metadata.genre", "tragedy").to_string(),
        ]),
        None,
    ).await?;

    Ok(())
}
```

### Upsert documents

**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/vectorsdb/permissions)

You can upsert multiple documents in a single request using the `upsertDocuments` method. Documents with a new `$id` are created, while documents with an existing `$id` are updated.

```server-nodejs
const sdk = require('node-appwrite');

const client = new sdk.Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<YOUR_PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const vectorsDB = new sdk.VectorsDB(client);

const result = await vectorsDB.upsertDocuments({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documents: [
        {
            $id: sdk.ID.unique(),
            embeddings: [0.40, 0.40, 0.40, 0.40],
            metadata: { title: 'Othello', genre: 'tragedy' }
        },
        {
            $id: 'document-id-2', // Existing document ID
            embeddings: [0.10, 0.10, 0.10, 0.10],
            metadata: { title: 'Hamlet', genre: 'tragedy' }
        }
    ]
});
```

```python
from appwrite.client import Client
from appwrite.services.vectors_db import VectorsDB
from appwrite.id import ID

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<YOUR_PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

vectors_db = VectorsDB(client)

result = vectors_db.upsert_documents(
    database_id = '<DATABASE_ID>',
    collection_id = '<COLLECTION_ID>',
    documents = [
        {
            '$id': ID.unique(),
            'embeddings': [0.40, 0.40, 0.40, 0.40],
            'metadata': { 'title': 'Othello', 'genre': 'tragedy' }
        },
        {
            '$id': 'document-id-2',  # Existing document ID
            'embeddings': [0.10, 0.10, 0.10, 0.10],
            'metadata': { 'title': 'Hamlet', 'genre': 'tragedy' }
        }
    ]
)
```

```server-rust
use appwrite::Client;
use appwrite::services::VectorsDB;
use appwrite::id::ID;
use serde_json::json;

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

    let vectors_db = VectorsDB::new(&client);

    let result = vectors_db.upsert_documents(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
        vec![
            json!({
                "$id": ID::unique(),
                "embeddings": [0.40, 0.40, 0.40, 0.40],
                "metadata": { "title": "Othello", "genre": "tragedy" }
            }),
            json!({
                "$id": "document-id-2", // Existing document ID
                "embeddings": [0.10, 0.10, 0.10, 0.10],
                "metadata": { "title": "Hamlet", "genre": "tragedy" }
            }),
        ],
        None,
    ).await?;

    Ok(())
}
```

### Delete documents

**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/vectorsdb/permissions)

You can delete multiple documents in a single request using the `deleteDocuments` method.

```server-nodejs
const sdk = require('node-appwrite');

const client = new sdk.Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<YOUR_PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const vectorsDB = new sdk.VectorsDB(client);

const result = await vectorsDB.deleteDocuments({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    queries: [
        sdk.Query.equal('metadata.genre', 'drama')
    ]
});
```

```python
from appwrite.client import Client
from appwrite.services.vectors_db import VectorsDB
from appwrite.query import Query

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<YOUR_PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

vectors_db = VectorsDB(client)

result = vectors_db.delete_documents(
    database_id = '<DATABASE_ID>',
    collection_id = '<COLLECTION_ID>',
    queries = [
        Query.equal('metadata.genre', 'drama')
    ]
)
```

```server-rust
use appwrite::Client;
use appwrite::services::VectorsDB;
use appwrite::query::Query;

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

    let vectors_db = VectorsDB::new(&client);

    let result = vectors_db.delete_documents(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
        Some(vec![
            Query::equal("metadata.genre", "drama").to_string(),
        ]),
        None,
    ).await?;

    Ok(())
}
```

**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/vectorsdb/queries).

### Use transactions

`updateDocuments`, `upsertDocuments`, and `deleteDocuments` accept a `transactionId`. When provided, Appwrite stages the bulk request and applies it on commit. See [Transactions](/docs/products/databases/vectorsdb/transactions).

```server-nodejs
await vectorsDB.upsertDocuments({
  databaseId: '<DATABASE_ID>',
  collectionId: '<COLLECTION_ID>',
  documents: [
    { $id: sdk.ID.unique(), embeddings: [0.12, 0.84, 0.33, 0.57], metadata: { title: 'One' } },
    { $id: sdk.ID.unique(), embeddings: [0.91, 0.22, 0.14, 0.65], metadata: { title: 'Two' } }
  ],
  transactionId: '<TRANSACTION_ID>'
});
```
```python
vectors_db.upsert_documents(
  database_id = '<DATABASE_ID>',
  collection_id = '<COLLECTION_ID>',
  documents = [
    { '$id': ID.unique(), 'embeddings': [0.12, 0.84, 0.33, 0.57], 'metadata': { 'title': 'One' } },
    { '$id': ID.unique(), 'embeddings': [0.91, 0.22, 0.14, 0.65], 'metadata': { 'title': 'Two' } }
  ],
  transaction_id = '<TRANSACTION_ID>'
)
```
```server-rust
let result = vectors_db.upsert_documents(
    "<DATABASE_ID>",
    "<COLLECTION_ID>",
    vec![
        json!({
            "$id": ID::unique(),
            "embeddings": [0.12, 0.84, 0.33, 0.57],
            "metadata": { "title": "One" }
        }),
        json!({
            "$id": ID::unique(),
            "embeddings": [0.91, 0.22, 0.14, 0.65],
            "metadata": { "title": "Two" }
        }),
    ],
    Some("<TRANSACTION_ID>"),
).await?;
```

---

## Collections

URL: https://appwrite.io/docs/products/databases/vectorsdb/collections

Appwrite uses collections as containers of documents.
A VectorsDB collection stores embeddings, so every collection is created with a fixed **`dimension`**, the length of the embedding vectors it holds. All documents in the collection must use vectors of that exact length.

Unlike TablesDB, you don't define a schema for a VectorsDB collection. The schema is fixed and provisioned for you when the collection is created:

| Attribute | Type | Description |
|--------------|----------|-----------------------------------------------------------------------------|
| `embeddings` | `vector` | The embedding vector. Required, and sized to the collection's `dimension`. |
| `metadata` | `object` | Arbitrary JSON stored alongside the vector. Optional. |

Because the schema is fixed, VectorsDB has no typed-attribute endpoints like TablesDB columns. You cannot add, update, or delete attributes on a collection. You store your vector under `embeddings` and any associated data under `metadata`. Indexes are the one part of a collection you do control, and you manage them from [vector search](/docs/products/databases/vectorsdb/vector-search#manage-indexes).

### Create collection

![Create collection dialog](/images/docs/products/databases/vectorsdb/create-collection.avif)
You can create collections using the Appwrite Console, a [Server SDK](/docs/sdks#server), or using the [CLI](/docs/tooling/command-line/installation).

Head to the **Databases** page, open a [database](/docs/products/databases/vectorsdb/databases), and click **Create collection**. Name the collection and pick the **Embedding model** you plan to use. The Console lists each model with the dimension it produces and sets the collection's `dimension` from your choice.

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).

The `dimension` is required and must match the length of the vectors you'll store. For example, the default `nomic-embed-text` model produces 768-dimensional vectors, so you would create the collection with `dimension: 768`.

```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 vectorsDB = new sdk.VectorsDB(client);

const result = await vectorsDB.createCollection({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    name: '<NAME>',
    dimension: 768,
    permissions: [sdk.Permission.read(sdk.Role.any())], // optional
    documentSecurity: false, // optional
    enabled: true // 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('<YOUR_PROJECT_ID>') // Your project ID
    .setKey('<YOUR_API_KEY>'); // Your secret API key

const vectorsDB = new sdk.VectorsDB(client);

const result = await vectorsDB.createCollection({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    name: '<NAME>',
    dimension: 768,
    permissions: [sdk.Permission.read(sdk.Role.any())], // optional
    documentSecurity: false, // optional
    enabled: true // optional
});
```
```php
<?php

use Appwrite\Client;
use Appwrite\Services\VectorsDB;
use Appwrite\Permission;
use Appwrite\Role;

$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

$vectorsDB = new VectorsDB($client);

$result = $vectorsDB->createCollection(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    name: '<NAME>',
    dimension: 768,
    permissions: [Permission::read(Role::any())], // optional
    documentSecurity: false, // optional
    enabled: true // optional
);
```
```python
from appwrite.client import Client
from appwrite.services.vectors_db import VectorsDB
from appwrite.permission import Permission
from appwrite.role import Role

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

vectors_db = VectorsDB(client)

result = vectors_db.create_collection(
    database_id = '<DATABASE_ID>',
    collection_id = '<COLLECTION_ID>',
    name = '<NAME>',
    dimension = 768,
    permissions = [Permission.read(Role.any())], # optional
    document_security = False, # optional
    enabled = True # optional
)
```
```ruby
require 'appwrite'

include Appwrite
include Appwrite::Permission
include Appwrite::Role

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

vectors_db = VectorsDB.new(client)

result = vectors_db.create_collection(
    database_id: '<DATABASE_ID>',
    collection_id: '<COLLECTION_ID>',
    name: '<NAME>',
    dimension: 768,
    permissions: [Permission.read(Role.any())], # optional
    document_security: false, # optional
    enabled: true # optional
)
```
```csharp
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

VectorsDB vectorsDB = new VectorsDB(client);

Collection result = await vectorsDB.CreateCollection(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    name: "<NAME>",
    dimension: 768,
    permissions: new List<string> { Permission.Read(Role.Any()) }, // optional
    documentSecurity: false, // optional
    enabled: true // optional
);
```
```dart
import 'package:dart_appwrite/dart_appwrite.dart';
import 'package:dart_appwrite/permission.dart';
import 'package:dart_appwrite/role.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

VectorsDB vectorsDB = VectorsDB(client);

Collection result = await vectorsDB.createCollection(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    name: '<NAME>',
    dimension: 768,
    permissions: [Permission.read(Role.any())], // (optional)
    documentSecurity: false, // (optional)
    enabled: true, // (optional)
);
```
```kotlin
import io.appwrite.Client
import io.appwrite.Permission
import io.appwrite.Role
import io.appwrite.services.VectorsDB

val client = Client(context)
    .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 vectorsDB = VectorsDB(client)

val response = vectorsDB.createCollection(
    databaseId = "<DATABASE_ID>",
    collectionId = "<COLLECTION_ID>",
    name = "<NAME>",
    dimension = 768,
    permissions = listOf(Permission.read(Role.any())), // optional
    documentSecurity = false, // optional
    enabled = true, // optional
)
```
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.VectorsDB;

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

VectorsDB vectorsDB = new VectorsDB(client);

vectorsDB.createCollection(
    "<DATABASE_ID>",
    "<COLLECTION_ID>",
    "<NAME>",
    768,
    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("<YOUR_PROJECT_ID>") // Your project ID
    .setKey("<YOUR_API_KEY>") // Your secret API key

let vectorsDB = VectorsDB(client)

let collection = try await vectorsDB.createCollection(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    name: "<NAME>",
    dimension: 768,
    permissions: [Permission.read(Role.any())], // optional
    documentSecurity: false, // optional
    enabled: true // optional
)
```
```server-rust
use appwrite::Client;
use appwrite::services::VectorsDB;
use appwrite::permission::Permission;
use appwrite::role::Role;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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

    let vectors_db = VectorsDB::new(&client);

    let result = vectors_db.create_collection(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
        "<NAME>",
        768,
        Some(vec![Permission::read(Role::any()).to_string()]), // permissions (optional)
        Some(false), // documentSecurity (optional)
        Some(true), // enabled (optional)
    ).await?;

    println!("{:?}", result);
    Ok(())
}
```
```bash
appwrite vectorsdb create-collection \
    --database-id "<DATABASE_ID>" \
    --collection-id "<COLLECTION_ID>" \
    --name "<NAME>" \
    --dimension 768
```

**Dimension is fixed**

The `dimension` is set when the collection is created and applies to every document in it. Choose a value that matches your embedding model, between 1 and 16000. To store vectors of a different length, create a separate collection.

### Schema
A VectorsDB collection's schema is provisioned automatically and cannot be changed. Every collection has exactly two attributes:

- `embeddings`, a required `vector` attribute sized to the collection's `dimension`.
- `metadata`, an optional `object` attribute that holds arbitrary JSON.

A document's `data` therefore looks like this, where the vector length equals the collection's `dimension`:

```json
{
    "embeddings": [0.12, 0.84, 0.05, 0.63],
    "metadata": { "title": "Introduction", "source": "docs" }
}
```

An `object` index on `metadata` is also created for you. There are no endpoints to add, modify, or remove attributes. To learn how to write and read this data, see [Documents](/docs/products/databases/vectorsdb/documents).

### Manage collections
Use a [Server SDK](/docs/sdks#server) to list, retrieve, update, and delete collections.

#### List collections

```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 vectorsDB = new sdk.VectorsDB(client);

const result = await vectorsDB.listCollections({
    databaseId: '<DATABASE_ID>'
});
```
```deno
import * as sdk from "npm: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 vectorsDB = new sdk.VectorsDB(client);

const result = await vectorsDB.listCollections({
    databaseId: '<DATABASE_ID>'
});
```
```php
<?php

use Appwrite\Client;
use Appwrite\Services\VectorsDB;

$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

$vectorsDB = new VectorsDB($client);

$result = $vectorsDB->listCollections(
    databaseId: '<DATABASE_ID>'
);
```
```python
from appwrite.client import Client
from appwrite.services.vectors_db import VectorsDB

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

vectors_db = VectorsDB(client)

result = vectors_db.list_collections(
    database_id = '<DATABASE_ID>'
)
```
```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

vectors_db = VectorsDB.new(client)

result = vectors_db.list_collections(
    database_id: '<DATABASE_ID>'
)
```
```csharp
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

VectorsDB vectorsDB = new VectorsDB(client);

CollectionList result = await vectorsDB.ListCollections(
    databaseId: "<DATABASE_ID>"
);
```
```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

VectorsDB vectorsDB = VectorsDB(client);

CollectionList result = await vectorsDB.listCollections(
    databaseId: '<DATABASE_ID>',
);
```
```kotlin
import io.appwrite.Client
import io.appwrite.services.VectorsDB

val client = Client(context)
    .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 vectorsDB = VectorsDB(client)

val response = vectorsDB.listCollections(
    databaseId = "<DATABASE_ID>",
)
```
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.VectorsDB;

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

VectorsDB vectorsDB = new VectorsDB(client);

vectorsDB.listCollections(
    "<DATABASE_ID>",
    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("<YOUR_PROJECT_ID>") // Your project ID
    .setKey("<YOUR_API_KEY>") // Your secret API key

let vectorsDB = VectorsDB(client)

let collectionList = try await vectorsDB.listCollections(
    databaseId: "<DATABASE_ID>"
)
```
```server-rust
use appwrite::Client;
use appwrite::services::VectorsDB;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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

    let vectors_db = VectorsDB::new(&client);

    let result = vectors_db.list_collections(
        "<DATABASE_ID>",
        None, // queries (optional)
        None, // search (optional)
        None, // total (optional)
    ).await?;

    println!("{:?}", result);
    Ok(())
}
```
```bash
appwrite vectorsdb list-collections \
    --database-id "<DATABASE_ID>"
```

#### Get collection

```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 vectorsDB = new sdk.VectorsDB(client);

const result = await vectorsDB.getCollection({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>'
});
```
```deno
import * as sdk from "npm: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 vectorsDB = new sdk.VectorsDB(client);

const result = await vectorsDB.getCollection({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>'
});
```
```php
<?php

use Appwrite\Client;
use Appwrite\Services\VectorsDB;

$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

$vectorsDB = new VectorsDB($client);

$result = $vectorsDB->getCollection(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>'
);
```
```python
from appwrite.client import Client
from appwrite.services.vectors_db import VectorsDB

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

vectors_db = VectorsDB(client)

result = vectors_db.get_collection(
    database_id = '<DATABASE_ID>',
    collection_id = '<COLLECTION_ID>'
)
```
```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

vectors_db = VectorsDB.new(client)

result = vectors_db.get_collection(
    database_id: '<DATABASE_ID>',
    collection_id: '<COLLECTION_ID>'
)
```
```csharp
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

VectorsDB vectorsDB = new VectorsDB(client);

Collection result = await vectorsDB.GetCollection(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>"
);
```
```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

VectorsDB vectorsDB = VectorsDB(client);

Collection result = await vectorsDB.getCollection(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
);
```
```kotlin
import io.appwrite.Client
import io.appwrite.services.VectorsDB

val client = Client(context)
    .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 vectorsDB = VectorsDB(client)

val response = vectorsDB.getCollection(
    databaseId = "<DATABASE_ID>",
    collectionId = "<COLLECTION_ID>",
)
```
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.VectorsDB;

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

VectorsDB vectorsDB = new VectorsDB(client);

vectorsDB.getCollection(
    "<DATABASE_ID>",
    "<COLLECTION_ID>",
    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("<YOUR_PROJECT_ID>") // Your project ID
    .setKey("<YOUR_API_KEY>") // Your secret API key

let vectorsDB = VectorsDB(client)

let collection = try await vectorsDB.getCollection(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>"
)
```
```server-rust
use appwrite::Client;
use appwrite::services::VectorsDB;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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

    let vectors_db = VectorsDB::new(&client);

    let result = vectors_db.get_collection(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
    ).await?;

    println!("{:?}", result);
    Ok(())
}
```
```bash
appwrite vectorsdb get-collection \
    --database-id "<DATABASE_ID>" \
    --collection-id "<COLLECTION_ID>"
```

#### Update collection
You can rename a collection and change its permissions, `documentSecurity`, or `enabled` state. The `name` is required when updating.

```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 vectorsDB = new sdk.VectorsDB(client);

const result = await vectorsDB.updateCollection({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    name: '<NAME>',
    documentSecurity: true // 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('<YOUR_PROJECT_ID>') // Your project ID
    .setKey('<YOUR_API_KEY>'); // Your secret API key

const vectorsDB = new sdk.VectorsDB(client);

const result = await vectorsDB.updateCollection({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    name: '<NAME>',
    documentSecurity: true // optional
});
```
```php
<?php

use Appwrite\Client;
use Appwrite\Services\VectorsDB;

$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

$vectorsDB = new VectorsDB($client);

$result = $vectorsDB->updateCollection(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    name: '<NAME>',
    documentSecurity: true // optional
);
```
```python
from appwrite.client import Client
from appwrite.services.vectors_db import VectorsDB

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

vectors_db = VectorsDB(client)

result = vectors_db.update_collection(
    database_id = '<DATABASE_ID>',
    collection_id = '<COLLECTION_ID>',
    name = '<NAME>',
    document_security = True # optional
)
```
```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

vectors_db = VectorsDB.new(client)

result = vectors_db.update_collection(
    database_id: '<DATABASE_ID>',
    collection_id: '<COLLECTION_ID>',
    name: '<NAME>',
    document_security: true # optional
)
```
```csharp
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

VectorsDB vectorsDB = new VectorsDB(client);

Collection result = await vectorsDB.UpdateCollection(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    name: "<NAME>",
    documentSecurity: true // optional
);
```
```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

VectorsDB vectorsDB = VectorsDB(client);

Collection result = await vectorsDB.updateCollection(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    name: '<NAME>',
    documentSecurity: true, // (optional)
);
```
```kotlin
import io.appwrite.Client
import io.appwrite.services.VectorsDB

val client = Client(context)
    .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 vectorsDB = VectorsDB(client)

val response = vectorsDB.updateCollection(
    databaseId = "<DATABASE_ID>",
    collectionId = "<COLLECTION_ID>",
    name = "<NAME>",
    documentSecurity = true, // optional
)
```
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.VectorsDB;

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

VectorsDB vectorsDB = new VectorsDB(client);

vectorsDB.updateCollection(
    "<DATABASE_ID>",
    "<COLLECTION_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("<YOUR_PROJECT_ID>") // Your project ID
    .setKey("<YOUR_API_KEY>") // Your secret API key

let vectorsDB = VectorsDB(client)

let collection = try await vectorsDB.updateCollection(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    name: "<NAME>",
    documentSecurity: true // optional
)
```
```server-rust
use appwrite::Client;
use appwrite::services::VectorsDB;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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

    let vectors_db = VectorsDB::new(&client);

    let result = vectors_db.update_collection(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
        "<NAME>",
        None, // dimension (optional)
        None, // permissions (optional)
        Some(true), // documentSecurity (optional)
        None, // enabled (optional)
    ).await?;

    println!("{:?}", result);
    Ok(())
}
```
```bash
appwrite vectorsdb update-collection \
    --database-id "<DATABASE_ID>" \
    --collection-id "<COLLECTION_ID>" \
    --name "<NAME>"
```

#### Delete collection
Deleting a collection permanently removes it and all of its documents.

```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 vectorsDB = new sdk.VectorsDB(client);

const result = await vectorsDB.deleteCollection({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>'
});
```
```deno
import * as sdk from "npm: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 vectorsDB = new sdk.VectorsDB(client);

const result = await vectorsDB.deleteCollection({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>'
});
```
```php
<?php

use Appwrite\Client;
use Appwrite\Services\VectorsDB;

$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

$vectorsDB = new VectorsDB($client);

$result = $vectorsDB->deleteCollection(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>'
);
```
```python
from appwrite.client import Client
from appwrite.services.vectors_db import VectorsDB

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

vectors_db = VectorsDB(client)

result = vectors_db.delete_collection(
    database_id = '<DATABASE_ID>',
    collection_id = '<COLLECTION_ID>'
)
```
```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

vectors_db = VectorsDB.new(client)

result = vectors_db.delete_collection(
    database_id: '<DATABASE_ID>',
    collection_id: '<COLLECTION_ID>'
)
```
```csharp
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

VectorsDB vectorsDB = new VectorsDB(client);

await vectorsDB.DeleteCollection(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>"
);
```
```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

VectorsDB vectorsDB = VectorsDB(client);

await vectorsDB.deleteCollection(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
);
```
```kotlin
import io.appwrite.Client
import io.appwrite.services.VectorsDB

val client = Client(context)
    .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 vectorsDB = VectorsDB(client)

vectorsDB.deleteCollection(
    databaseId = "<DATABASE_ID>",
    collectionId = "<COLLECTION_ID>",
)
```
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.VectorsDB;

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

VectorsDB vectorsDB = new VectorsDB(client);

vectorsDB.deleteCollection(
    "<DATABASE_ID>",
    "<COLLECTION_ID>",
    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("<YOUR_PROJECT_ID>") // Your project ID
    .setKey("<YOUR_API_KEY>") // Your secret API key

let vectorsDB = VectorsDB(client)

try await vectorsDB.deleteCollection(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>"
)
```
```server-rust
use appwrite::Client;
use appwrite::services::VectorsDB;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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

    let vectors_db = VectorsDB::new(&client);

    vectors_db.delete_collection(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
    ).await?;

    Ok(())
}
```
```bash
appwrite vectorsdb delete-collection \
    --database-id "<DATABASE_ID>" \
    --collection-id "<COLLECTION_ID>"
```

### Permissions
Appwrite uses permissions to control data access.
For security, only users that are granted permissions can access a resource.

By default, Appwrite doesn't grant permissions to any users when a new collection is created.
This means users can't create documents or read, update, and delete existing documents until you grant access.

Set `documentSecurity` to `true` on a collection to configure permissions on individual documents. A user then needs either collection level or document level permissions to access a document.

[Learn about configuring permissions](/docs/products/databases/vectorsdb/permissions).

---

## CSV exports

URL: https://appwrite.io/docs/products/databases/vectorsdb/csv-exports

Appwrite's CSV export feature lets you export documents from a VectorsDB collection to a CSV file. This is useful for reporting, sharing a dataset with your team, creating custom backups, or handing embeddings and their metadata off to other tools.

### Exported columns

A VectorsDB collection has a fixed schema, so every export has the same shape. Each row carries the document's system fields together with the two collection attributes:

| Column | Type | Description |
|--------------|--------|--------------------------------------------------------------------------|
| `$id` | string | The document ID. |
| `embeddings` | JSON | The embedding vector, serialized as a JSON array of numbers. |
| `metadata` | JSON | The metadata stored alongside the vector, serialized as a JSON object. |

System columns like `$id`, `$createdAt`, and `$updatedAt` are included automatically. Because `embeddings` is the full vector for each document, exported files can be large for high dimension collections.

An example of exported data, where each `embeddings` value is the full vector for that document:

```text
$id,embeddings,metadata
doc-1,"[0.12,0.84,0.33,0.57]","{""title"":""Hamlet"",""year"":1601}"
doc-2,"[0.2,0.1,0.6,0.1]","{""title"":""Macbeth"",""year"":1606}"
doc-3,"[0.5,0.5,0.5,0.5]","{""title"":""Othello"",""year"":1603}"
```

### Export configuration

Before exporting, you can configure several options to control the output format and contents.

#### Apply filters

You can pass [queries](/docs/products/databases/vectorsdb/queries) to export only the documents you need rather than the whole collection. This is useful when you want a subset of your data for a specific use case.

#### Select columns

You can choose which columns to include in the export. By default, all columns are exported. Selecting specific columns creates more focused datasets, for example exporting only `metadata` when you don't need the raw vectors.

#### Custom delimiter

You can set a custom delimiter for the CSV file. While commas are standard, you can use tabs, semicolons, or other delimiters based on the tools you import into.

Common delimiters:
- **Comma (`,`)**: Standard format, compatible with most tools
- **Tab**: Useful when your data contains many commas
- **Semicolon (`;`)**: Common in European Excel versions
- **Pipe (`|`)**: Useful when your data contains many semicolons

#### Header row

You can choose whether to include a header row with column names. Headers make the data easier to read in spreadsheets, but some import tools work better without them.

### Timestamps

The `$createdAt` and `$updatedAt` columns are exported in ISO 8601 format, making them compatible with most spreadsheet and database tools.

### Permissions

If [document security](/docs/products/databases/vectorsdb/permissions) is enabled for your collection, the `$permissions` column is included in the export. Permission strings are formatted as comma-separated role definitions within quotes.

```text
$id,embeddings,$permissions
doc-1,"[0.12,0.84,0.33,0.57]","read(""any""),update(""user:user-123"")"
doc-2,"[0.2,0.1,0.6,0.1]","read(""team:team-456""),update(""team:team-456"")"
```

The roles used are API strings that can be found in the [permissions documentation](/docs/products/databases/vectorsdb/permissions).

### Run the export

Exports can only be started from the Console. Open the collection, click **Export CSV** in the toolbar, choose the columns, filters, delimiter, and header options described above, and start the export. Exports cannot be started with an API key or a server SDK.

### Background processing

Exports run as background jobs, so a large collection doesn't block your workflow. Appwrite emails you a download link when the export finishes, unless you pass `notify` as `false`.

Open the database and select the **Export / Import** tab to watch the job and download the file once it completes.

![Export / Import page of a database](/images/docs/products/databases/vectorsdb/import-export.avif)

### Use cases

CSV exports are useful for many common workflows:

- **Reporting**: Generate reports for stakeholders who need data in spreadsheet format
- **Data sharing**: Share an embeddings dataset with teammates
- **Analytics hand-off**: Provide vectors and metadata to analysts using other tools
- **Custom backups**: Archive specific data subsets for record-keeping
- **Migration preparation**: Extract data to move it into another system

### Best practices

To get the most out of CSV exports:

1. **Filter your data**: Export only the documents you need to reduce file size and processing time
2. **Select specific columns**: Export only `metadata` when you don't need the raw vectors, since high dimension `embeddings` columns make files much larger
3. **Choose appropriate delimiters**: Use tabs or semicolons if your data contains many commas
4. **Consider header requirements**: Include headers for human readability, exclude them for automated imports

### Additional resources

- [CSV imports](/docs/products/databases/vectorsdb/csv-imports) - Import data from CSV files
- [Documents](/docs/products/databases/vectorsdb/documents) - Create, read, update, and delete documents
- [Permissions](/docs/products/databases/vectorsdb/permissions) - Configure document-level security
- [Backups](/docs/products/databases/vectorsdb/backups) - Automated backup policies

---

## CSV imports

URL: https://appwrite.io/docs/products/databases/vectorsdb/csv-imports

Appwrite's CSV Import feature allows you to create multiple documents in a collection by uploading a single CSV file. This is especially useful for loading precomputed embeddings, seeding test environments, or migrating vectors from another system.

### Prepare your CSV

A VectorsDB collection has a fixed schema, so every CSV maps to the same two columns:

| Column | Type | Description |
|--------------|----------|----------------------------------------------------------------------------------|
| `embeddings` | `vector` | The embedding vector, written as a JSON array. Required, and its length must equal the collection's `dimension`. |
| `metadata` | `object` | Arbitrary JSON stored alongside the vector, written as a JSON object. Optional. |

Each row represents a new document. The `embeddings` value is parsed as a JSON array of numbers, and the `metadata` value is parsed as a JSON object. Each row is validated before being imported.

**Good to know**

You can optionally include the `$id` column to define custom document IDs. If not provided, Appwrite will generate unique IDs for each document automatically.

Appwrite imports documents in batches of 100 documents at a time. If a provided ID already exists in the collection, the entire batch containing that document will fail, but documents in other batches will continue to be imported successfully.

An example of a valid CSV file for a collection created with `dimension: 4`:

```text
$id,embeddings,metadata
vec_1,"[0.12,0.04,0.88,0.31]","{""title"":""First vector"",""year"":2024}"
vec_2,"[0.55,0.61,0.07,0.42]","{""title"":""Second vector"",""year"":2025}"
vec_3,"[0.20,0.20,0.20,0.20]","{""title"":""Third vector"",""year"":2025}"
```

The double quotes around each value let you include the commas inside the JSON array and object. The inner double quotes of the JSON keys and string values are escaped by doubling them (`""`). See [Special characters](#special-characters) for the escaping rules.

**Vector length**

Every `embeddings` array must contain exactly as many numbers as the collection's `dimension`. A row whose vector length does not match the dimension is rejected.

### Metadata values

The `metadata` column is an object, so each value must be a valid JSON object or the literal `null`:

- **A JSON object**, for example `"{""key"":""value""}"`. An empty object `"{}"` is also valid.
- **`null`** (the unquoted literal) stores no metadata for that document.

A blank `metadata` value is not accepted. To omit metadata for a row, use `null` rather than leaving the field empty:

```text
$id,embeddings,metadata
vec_1,"[0.12,0.04,0.88,0.31]","{""title"":""With metadata""}"
vec_2,"[0.55,0.61,0.07,0.42]",null
```

### Create and update timestamps

You can also optionally include `$createdAt` and `$updatedAt` columns to set custom timestamps for imported documents. If omitted, Appwrite sets these automatically during import.

An example of a valid CSV file with `$createdAt` and `$updatedAt` timestamps:

```text
$id,$createdAt,$updatedAt,embeddings,metadata
vec_1,2025-08-10T12:34:56.000Z,2025-08-10T12:34:56.000Z,"[0.12,0.04,0.88,0.31]","{""title"":""First vector""}"
vec_2,2025-08-11T09:15:00.000Z,2025-08-11T10:00:00.000Z,"[0.55,0.61,0.07,0.42]","{""title"":""Second vector""}"
```

**Timestamps format**

`$createdAt` and `$updatedAt` must be valid ISO 8601 date-time strings, for example: `2025-08-10T12:34:56.000Z`.

### Permissions

You can set permissions for documents in your CSV file by adding data for the `$permissions` column. Make sure document level security is enabled for your collection.

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 document permissions:

```text
$id,embeddings,metadata,$permissions
vec_1,"[0.12,0.04,0.88,0.31]","{""title"":""First vector""}","read(""any""),update(""user:user_id""),delete(""user:user_id"")"
vec_2,"[0.55,0.61,0.07,0.42]","{""title"":""Second vector""}","read(""users""),update(""user:user_id"")"
```

### Special characters

The `embeddings` and `metadata` values are JSON written inside CSV fields, so you need to escape the characters that CSV treats specially.

#### Comma

The JSON array and object both contain commas, so wrap each value in double quotes (`"[0.1,0.2,0.3,0.4]"`). Without the surrounding quotes, the commas would be read as column separators.

#### Double quotes

JSON keys and string values use double quotes, and a double quote inside a quoted CSV field must be escaped by doubling it (`""`). For example, the object `{"title":"hello"}` is written in the CSV as `"{""title"":""hello""}"`.

### Run the import

CSV imports run as a background migration and can only be started from the Console. Open the collection, click **Import CSV** in the toolbar, then upload a CSV from your device or choose one that is already in a [storage bucket](/docs/products/storage/buckets). Imports cannot be started with an API key or a server SDK.

The import stops at the first document whose `$id` already exists in the collection.

**Track imports in the Console**

Open the database and select the **Export / Import** tab to watch a job run and see how many documents it wrote. Finished exports offer a **Download** action on the same tab.

### Additional resources

- [Appwrite CLI](/docs/command-line)
- [Database permissions](/docs/products/databases/vectorsdb/permissions)
- [Embeddings](/docs/products/databases/vectorsdb/embeddings)

---

## Databases

URL: https://appwrite.io/docs/products/databases/vectorsdb/databases

Databases are the largest organizational unit in Appwrite.
Each database contains a group of [collections](/docs/products/databases/vectorsdb/collections).

A database runs on shared or dedicated infrastructure; see [shared and dedicated databases](/docs/products/databases#shared-and-dedicated).

### Create in Console

![Create database with VectorsDB selected](/images/docs/products/databases/vectorsdb/create-database-type.avif)
Navigate to the **Databases** page and click **Create database**, then choose **VectorsDB** as the database type. Name the database, select your preferred tier, and pick a replica count and backup policy. Click **Create database** to finish.

### Create using Server SDKs
You can programmatically create a shared database using a [Server SDK](/docs/sdks#server). Appwrite [Server SDKs](/docs/sdks#server) require an [API key](/docs/advanced/platform/api-keys).

```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 vectorsDB = new sdk.VectorsDB(client);

const result = await vectorsDB.create({
    databaseId: '<DATABASE_ID>',
    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('<YOUR_PROJECT_ID>') // Your project ID
    .setKey('<YOUR_API_KEY>'); // Your secret API key

const vectorsDB = new sdk.VectorsDB(client);

const result = await vectorsDB.create({
    databaseId: '<DATABASE_ID>',
    name: '<NAME>'
});
```
```php
<?php

use Appwrite\Client;
use Appwrite\Services\VectorsDB;

$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

$vectorsDB = new VectorsDB($client);

$result = $vectorsDB->create(
    databaseId: '<DATABASE_ID>',
    name: '<NAME>'
);
```
```python
from appwrite.client import Client
from appwrite.services.vectors_db import VectorsDB

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

vectors_db = VectorsDB(client)

result = vectors_db.create(
    database_id = '<DATABASE_ID>',
    name = '<NAME>'
)
```
```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

vectors_db = VectorsDB.new(client)

result = vectors_db.create(
    database_id: '<DATABASE_ID>',
    name: '<NAME>'
)
```
```csharp
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

VectorsDB vectorsDB = new VectorsDB(client);

Database result = await vectorsDB.Create(
    databaseId: "<DATABASE_ID>",
    name: "<NAME>"
);
```
```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

VectorsDB vectorsDB = VectorsDB(client);

Database result = await vectorsDB.create(
    databaseId: '<DATABASE_ID>',
    name: '<NAME>',
);
```
```kotlin
import io.appwrite.Client
import io.appwrite.services.VectorsDB

val client = Client(context)
    .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 vectorsDB = VectorsDB(client)

val response = vectorsDB.create(
    databaseId = "<DATABASE_ID>",
    name = "<NAME>",
)
```
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.VectorsDB;

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

VectorsDB vectorsDB = new VectorsDB(client);

vectorsDB.create(
    "<DATABASE_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("<YOUR_PROJECT_ID>") // Your project ID
    .setKey("<YOUR_API_KEY>") // Your secret API key

let vectorsDB = VectorsDB(client)

let database = try await vectorsDB.create(
    databaseId: "<DATABASE_ID>",
    name: "<NAME>"
)
```
```server-rust
use appwrite::Client;
use appwrite::services::VectorsDB;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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

    let vectors_db = VectorsDB::new(&client);

    let result = vectors_db.create(
        "<DATABASE_ID>",
        "<NAME>",
        None, // enabled (optional)
        None, // specification (optional)
        None, // replicas (optional)
        None, // sync_mode (optional)
    ).await?;

    println!("{:?}", result);
    Ok(())
}
```
```bash
appwrite vectorsdb create \
    --database-id <DATABASE_ID> \
    --name <NAME>
```

### Choose a specification
Three parameters on `create` and `update` decide what a database runs on.

| Parameter | Description |
| --- | --- |
| `specification` | The compute tier. Defaults to `serverless`, which is the shared pool. Any other value provisions a dedicated database on that tier. |
| `replicas` | How many high availability replicas back a dedicated database, from 0 to 5. High availability turns on above 0. |
| `syncMode` | How replicas acknowledge writes: `async`, `sync`, or `quorum`. It applies once the database has at least one replica. |

**Replicas need a dedicated specification**

`replicas` and `syncMode` only apply to a dedicated database. Passing either one alongside a serverless specification fails with `database_ha_requires_dedicated`.

Use the `listSpecifications` method to read the tiers your organization can select, along with their CPU, memory, connection limit, and price.

```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 vectorsDB = new sdk.VectorsDB(client);

const result = await vectorsDB.listSpecifications();
```
```deno
import * as sdk from "npm: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 vectorsDB = new sdk.VectorsDB(client);

const result = await vectorsDB.listSpecifications();
```
```php
<?php

use Appwrite\Client;
use Appwrite\Services\VectorsDB;

$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

$vectorsDB = new VectorsDB($client);

$result = $vectorsDB->listSpecifications();
```
```python
from appwrite.client import Client
from appwrite.services.vectors_db import VectorsDB

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

vectors_db = VectorsDB(client)

result = vectors_db.list_specifications()
```
```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

vectors_db = VectorsDB.new(client)

result = vectors_db.list_specifications()
```
```csharp
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

VectorsDB vectorsDB = new VectorsDB(client);

DedicatedDatabaseSpecificationList result = await vectorsDB.ListSpecifications();
```
```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

VectorsDB vectorsDB = VectorsDB(client);

DedicatedDatabaseSpecificationList result = await vectorsDB.listSpecifications();
```
```kotlin
import io.appwrite.Client
import io.appwrite.services.VectorsDB

val client = Client(context)
    .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 vectorsDB = VectorsDB(client)

val response = vectorsDB.listSpecifications()
```
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.VectorsDB;

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

VectorsDB vectorsDB = new VectorsDB(client);

vectorsDB.listSpecifications(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("<YOUR_PROJECT_ID>") // Your project ID
    .setKey("<YOUR_API_KEY>") // Your secret API key

let vectorsDB = VectorsDB(client)

let dedicatedDatabaseSpecificationList = try await vectorsDB.listSpecifications()
```
```server-rust
use appwrite::Client;
use appwrite::services::VectorsDB;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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

    let vectors_db = VectorsDB::new(&client);

    let result = vectors_db.list_specifications().await?;

    println!("{:?}", result);
    Ok(())
}
```
```bash
appwrite vectorsdb list-specifications
```

Each entry has a `slug` to pass as `specification`, and an `enabled` flag that tells you whether your current plan can select it.

### List databases
Use the `list` method to retrieve all databases in your project.

```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 vectorsDB = new sdk.VectorsDB(client);

const result = await vectorsDB.list({});
```
```deno
import * as sdk from "npm: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 vectorsDB = new sdk.VectorsDB(client);

const result = await vectorsDB.list({});
```
```php
<?php

use Appwrite\Client;
use Appwrite\Services\VectorsDB;

$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

$vectorsDB = new VectorsDB($client);

$result = $vectorsDB->list();
```
```python
from appwrite.client import Client
from appwrite.services.vectors_db import VectorsDB

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

vectors_db = VectorsDB(client)

result = vectors_db.list()
```
```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

vectors_db = VectorsDB.new(client)

result = vectors_db.list()
```
```csharp
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

VectorsDB vectorsDB = new VectorsDB(client);

DatabaseList result = await vectorsDB.List();
```
```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

VectorsDB vectorsDB = VectorsDB(client);

DatabaseList result = await vectorsDB.list();
```
```kotlin
import io.appwrite.Client
import io.appwrite.services.VectorsDB

val client = Client(context)
    .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 vectorsDB = VectorsDB(client)

val response = vectorsDB.list()
```
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.VectorsDB;

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

VectorsDB vectorsDB = new VectorsDB(client);

vectorsDB.list(
    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("<YOUR_PROJECT_ID>") // Your project ID
    .setKey("<YOUR_API_KEY>") // Your secret API key

let vectorsDB = VectorsDB(client)

let databaseList = try await vectorsDB.list()
```
```server-rust
use appwrite::Client;
use appwrite::services::VectorsDB;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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

    let vectors_db = VectorsDB::new(&client);

    let result = vectors_db.list(
        None, // queries (optional)
        None, // total (optional)
    ).await?;

    println!("{:?}", result);
    Ok(())
}
```
```bash
appwrite vectorsdb list
```

### Get a database
Use the `get` method to retrieve a single database by its ID.

```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 vectorsDB = new sdk.VectorsDB(client);

const result = await vectorsDB.get({
    databaseId: '<DATABASE_ID>'
});
```
```deno
import * as sdk from "npm: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 vectorsDB = new sdk.VectorsDB(client);

const result = await vectorsDB.get({
    databaseId: '<DATABASE_ID>'
});
```
```php
<?php

use Appwrite\Client;
use Appwrite\Services\VectorsDB;

$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

$vectorsDB = new VectorsDB($client);

$result = $vectorsDB->get(
    databaseId: '<DATABASE_ID>'
);
```
```python
from appwrite.client import Client
from appwrite.services.vectors_db import VectorsDB

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

vectors_db = VectorsDB(client)

result = vectors_db.get(
    database_id = '<DATABASE_ID>'
)
```
```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

vectors_db = VectorsDB.new(client)

result = vectors_db.get(
    database_id: '<DATABASE_ID>'
)
```
```csharp
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

VectorsDB vectorsDB = new VectorsDB(client);

Database result = await vectorsDB.Get(
    databaseId: "<DATABASE_ID>"
);
```
```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

VectorsDB vectorsDB = VectorsDB(client);

Database result = await vectorsDB.get(
    databaseId: '<DATABASE_ID>',
);
```
```kotlin
import io.appwrite.Client
import io.appwrite.services.VectorsDB

val client = Client(context)
    .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 vectorsDB = VectorsDB(client)

val response = vectorsDB.get(
    databaseId = "<DATABASE_ID>",
)
```
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.VectorsDB;

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

VectorsDB vectorsDB = new VectorsDB(client);

vectorsDB.get(
    "<DATABASE_ID>",
    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("<YOUR_PROJECT_ID>") // Your project ID
    .setKey("<YOUR_API_KEY>") // Your secret API key

let vectorsDB = VectorsDB(client)

let database = try await vectorsDB.get(
    databaseId: "<DATABASE_ID>"
)
```
```server-rust
use appwrite::Client;
use appwrite::services::VectorsDB;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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

    let vectors_db = VectorsDB::new(&client);

    let result = vectors_db.get(
        "<DATABASE_ID>",
    ).await?;

    println!("{:?}", result);
    Ok(())
}
```
```bash
appwrite vectorsdb get \
    --database-id <DATABASE_ID>
```

### Update a database
Use the `update` method to change a database's name or enabled state.

```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 vectorsDB = new sdk.VectorsDB(client);

const result = await vectorsDB.update({
    databaseId: '<DATABASE_ID>',
    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('<YOUR_PROJECT_ID>') // Your project ID
    .setKey('<YOUR_API_KEY>'); // Your secret API key

const vectorsDB = new sdk.VectorsDB(client);

const result = await vectorsDB.update({
    databaseId: '<DATABASE_ID>',
    name: '<NAME>'
});
```
```php
<?php

use Appwrite\Client;
use Appwrite\Services\VectorsDB;

$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

$vectorsDB = new VectorsDB($client);

$result = $vectorsDB->update(
    databaseId: '<DATABASE_ID>',
    name: '<NAME>'
);
```
```python
from appwrite.client import Client
from appwrite.services.vectors_db import VectorsDB

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

vectors_db = VectorsDB(client)

result = vectors_db.update(
    database_id = '<DATABASE_ID>',
    name = '<NAME>'
)
```
```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

vectors_db = VectorsDB.new(client)

result = vectors_db.update(
    database_id: '<DATABASE_ID>',
    name: '<NAME>'
)
```
```csharp
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

VectorsDB vectorsDB = new VectorsDB(client);

Database result = await vectorsDB.Update(
    databaseId: "<DATABASE_ID>",
    name: "<NAME>"
);
```
```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

VectorsDB vectorsDB = VectorsDB(client);

Database result = await vectorsDB.update(
    databaseId: '<DATABASE_ID>',
    name: '<NAME>',
);
```
```kotlin
import io.appwrite.Client
import io.appwrite.services.VectorsDB

val client = Client(context)
    .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 vectorsDB = VectorsDB(client)

val response = vectorsDB.update(
    databaseId = "<DATABASE_ID>",
    name = "<NAME>",
)
```
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.VectorsDB;

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

VectorsDB vectorsDB = new VectorsDB(client);

vectorsDB.update(
    "<DATABASE_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("<YOUR_PROJECT_ID>") // Your project ID
    .setKey("<YOUR_API_KEY>") // Your secret API key

let vectorsDB = VectorsDB(client)

let database = try await vectorsDB.update(
    databaseId: "<DATABASE_ID>",
    name: "<NAME>"
)
```
```server-rust
use appwrite::Client;
use appwrite::services::VectorsDB;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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

    let vectors_db = VectorsDB::new(&client);

    let result = vectors_db.update(
        "<DATABASE_ID>",
        "<NAME>",
        None, // enabled (optional)
        None, // specification (optional)
        None, // replicas (optional)
        None, // sync_mode (optional)
    ).await?;

    println!("{:?}", result);
    Ok(())
}
```
```bash
appwrite vectorsdb update \
    --database-id <DATABASE_ID> \
    --name <NAME>
```

### Delete a database
Use the `delete` method to permanently remove a database and all of its collections and documents.

```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 vectorsDB = new sdk.VectorsDB(client);

const result = await vectorsDB.delete({
    databaseId: '<DATABASE_ID>'
});
```
```deno
import * as sdk from "npm: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 vectorsDB = new sdk.VectorsDB(client);

const result = await vectorsDB.delete({
    databaseId: '<DATABASE_ID>'
});
```
```php
<?php

use Appwrite\Client;
use Appwrite\Services\VectorsDB;

$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

$vectorsDB = new VectorsDB($client);

$result = $vectorsDB->delete(
    databaseId: '<DATABASE_ID>'
);
```
```python
from appwrite.client import Client
from appwrite.services.vectors_db import VectorsDB

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

vectors_db = VectorsDB(client)

result = vectors_db.delete(
    database_id = '<DATABASE_ID>'
)
```
```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

vectors_db = VectorsDB.new(client)

result = vectors_db.delete(
    database_id: '<DATABASE_ID>'
)
```
```csharp
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

VectorsDB vectorsDB = new VectorsDB(client);

await vectorsDB.Delete(
    databaseId: "<DATABASE_ID>"
);
```
```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

VectorsDB vectorsDB = VectorsDB(client);

await vectorsDB.delete(
    databaseId: '<DATABASE_ID>',
);
```
```kotlin
import io.appwrite.Client
import io.appwrite.services.VectorsDB

val client = Client(context)
    .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 vectorsDB = VectorsDB(client)

vectorsDB.delete(
    databaseId = "<DATABASE_ID>",
)
```
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.VectorsDB;

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

VectorsDB vectorsDB = new VectorsDB(client);

vectorsDB.delete(
    "<DATABASE_ID>",
    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("<YOUR_PROJECT_ID>") // Your project ID
    .setKey("<YOUR_API_KEY>") // Your secret API key

let vectorsDB = VectorsDB(client)

try await vectorsDB.delete(
    databaseId: "<DATABASE_ID>"
)
```
```server-rust
use appwrite::Client;
use appwrite::services::VectorsDB;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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

    let vectors_db = VectorsDB::new(&client);

    vectors_db.delete(
        "<DATABASE_ID>",
    ).await?;

    Ok(())
}
```
```bash
appwrite vectorsdb delete \
    --database-id <DATABASE_ID>
```

---

## Documents

URL: https://appwrite.io/docs/products/databases/vectorsdb/documents

Each piece of data in Appwrite VectorsDB is a document.
A document's data follows the fixed schema provisioned by its collection: an `embeddings` vector and an optional `metadata` object.

An embedding is a list of numbers that represents the meaning of a piece of content, so similar content gets similar numbers. You can bring your own vectors or [generate embeddings from text](/docs/products/databases/vectorsdb/embeddings) with Appwrite's built-in models.

The `embeddings` array is required, and its length must equal the `dimension` you set when [creating the collection](/docs/products/databases/vectorsdb/collections).
The `metadata` field is free-form JSON, so you can attach any data you want to keep alongside each vector.

**Embedding length must match the dimension**

If the `embeddings` array is longer or shorter than the collection's `dimension`, the request is rejected.
The examples on this page use a collection with `dimension: 4` to keep the arrays readable.

### Create documents

**Permissions required**

You must grant _create_ permissions to users at the _collection level_ before users can create documents.
[Learn more about permissions](#permissions)

Use the `createDocument` method to add a document to a collection. Appwrite [Server SDKs](/docs/sdks#server) require an [API key](/docs/advanced/platform/api-keys).

```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 vectorsDB = new sdk.VectorsDB(client);

const result = await vectorsDB.createDocument({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: sdk.ID.unique(),
    data: {
        embeddings: [0.12, 0.84, 0.33, 0.57],
        metadata: { title: 'Hamlet', year: 1601 }
    },
    permissions: [sdk.Permission.read(sdk.Role.any())] // 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('<YOUR_PROJECT_ID>') // Your project ID
    .setKey('<YOUR_API_KEY>'); // Your secret API key

const vectorsDB = new sdk.VectorsDB(client);

const result = await vectorsDB.createDocument({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: sdk.ID.unique(),
    data: {
        embeddings: [0.12, 0.84, 0.33, 0.57],
        metadata: { title: 'Hamlet', year: 1601 }
    },
    permissions: [sdk.Permission.read(sdk.Role.any())] // optional
});
```
```php
<?php

use Appwrite\Client;
use Appwrite\Services\VectorsDB;
use Appwrite\ID;
use Appwrite\Permission;
use Appwrite\Role;

$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

$vectorsDB = new VectorsDB($client);

$result = $vectorsDB->createDocument(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: ID::unique(),
    data: [
        'embeddings' => [0.12, 0.84, 0.33, 0.57],
        'metadata' => ['title' => 'Hamlet', 'year' => 1601]
    ],
    permissions: [Permission::read(Role::any())] // optional
);
```
```python
from appwrite.client import Client
from appwrite.services.vectors_db import VectorsDB
from appwrite.id import ID
from appwrite.permission import Permission
from appwrite.role import Role

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

vectors_db = VectorsDB(client)

result = vectors_db.create_document(
    database_id = '<DATABASE_ID>',
    collection_id = '<COLLECTION_ID>',
    document_id = ID.unique(),
    data = {
        "embeddings": [0.12, 0.84, 0.33, 0.57],
        "metadata": { "title": "Hamlet", "year": 1601 }
    },
    permissions = [Permission.read(Role.any())] # optional
)
```
```ruby
require 'appwrite'

include Appwrite
include Appwrite::Permission
include Appwrite::Role

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

vectors_db = VectorsDB.new(client)

result = vectors_db.create_document(
    database_id: '<DATABASE_ID>',
    collection_id: '<COLLECTION_ID>',
    document_id: ID.unique(),
    data: {
        "embeddings" => [0.12, 0.84, 0.33, 0.57],
        "metadata" => { "title" => "Hamlet", "year" => 1601 }
    },
    permissions: [Permission.read(Role.any())] # optional
)
```
```csharp
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

VectorsDB vectorsDB = new VectorsDB(client);

Document result = await vectorsDB.CreateDocument(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    documentId: ID.Unique(),
    data: new Dictionary<string, object> {
        { "embeddings", new List<double> { 0.12, 0.84, 0.33, 0.57 } },
        { "metadata", new Dictionary<string, object> { { "title", "Hamlet" }, { "year", 1601 } } }
    },
    permissions: new List<string> { Permission.Read(Role.Any()) } // optional
);
```
```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

VectorsDB vectorsDB = VectorsDB(client);

Document result = await vectorsDB.createDocument(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: ID.unique(),
    data: {
        "embeddings": [0.12, 0.84, 0.33, 0.57],
        "metadata": { "title": "Hamlet", "year": 1601 }
    },
    permissions: [Permission.read(Role.any())], // (optional)
);
```
```kotlin
import io.appwrite.Client
import io.appwrite.ID
import io.appwrite.Permission
import io.appwrite.Role
import io.appwrite.services.VectorsDB

val client = Client(context)
    .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 vectorsDB = VectorsDB(client)

val response = vectorsDB.createDocument(
    databaseId = "<DATABASE_ID>",
    collectionId = "<COLLECTION_ID>",
    documentId = ID.unique(),
    data = mapOf(
        "embeddings" to listOf(0.12, 0.84, 0.33, 0.57),
        "metadata" to mapOf("title" to "Hamlet", "year" to 1601)
    ),
    permissions = listOf(Permission.read(Role.any())), // optional
)
```
```java
import io.appwrite.Client;
import io.appwrite.ID;
import io.appwrite.Permission;
import io.appwrite.Role;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.VectorsDB;
import java.util.List;
import java.util.Map;

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

VectorsDB vectorsDB = new VectorsDB(client);

vectorsDB.createDocument(
    "<DATABASE_ID>",
    "<COLLECTION_ID>",
    ID.unique(),
    Map.of(
        "embeddings", List.of(0.12, 0.84, 0.33, 0.57),
        "metadata", Map.of("title", "Hamlet", "year", 1601)
    ),
    List.of(Permission.read(Role.any())),
    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("<YOUR_PROJECT_ID>") // Your project ID
    .setKey("<YOUR_API_KEY>") // Your secret API key

let vectorsDB = VectorsDB(client)

let document = try await vectorsDB.createDocument(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    documentId: ID.unique(),
    data: [
        "embeddings": [0.12, 0.84, 0.33, 0.57],
        "metadata": ["title": "Hamlet", "year": 1601]
    ],
    permissions: [Permission.read(Role.any())] // optional
)
```
```server-rust
use appwrite::Client;
use appwrite::services::VectorsDB;
use appwrite::id::ID;
use appwrite::permission::Permission;
use appwrite::role::Role;
use serde_json::json;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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

    let vectors_db = VectorsDB::new(&client);

    let result = vectors_db.create_document(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
        ID::unique(),
        json!({
            "embeddings": [0.12, 0.84, 0.33, 0.57],
            "metadata": { "title": "Hamlet", "year": 1601 }
        }),
        Some(vec![Permission::read(Role::any()).to_string()]), // optional
    ).await?;

    println!("{:?}", result);
    Ok(())
}
```
```bash
appwrite vectorsdb create-document \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --document-id 'unique()' \
    --data '{ "embeddings": [0.12, 0.84, 0.33, 0.57], "metadata": { "title": "Hamlet", "year": 1601 } }'
```

To insert many documents in a single request, use [bulk operations](/docs/products/databases/vectorsdb/bulk-operations) instead of calling `createDocument` in a loop.

### Get document
Use the `getDocument` method to read a single document by its ID.

```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 vectorsDB = new sdk.VectorsDB(client);

const result = await vectorsDB.getDocument({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>'
});
```
```deno
import * as sdk from "npm: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 vectorsDB = new sdk.VectorsDB(client);

const result = await vectorsDB.getDocument({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>'
});
```
```php
<?php

use Appwrite\Client;
use Appwrite\Services\VectorsDB;

$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

$vectorsDB = new VectorsDB($client);

$result = $vectorsDB->getDocument(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>'
);
```
```python
from appwrite.client import Client
from appwrite.services.vectors_db import VectorsDB

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

vectors_db = VectorsDB(client)

result = vectors_db.get_document(
    database_id = '<DATABASE_ID>',
    collection_id = '<COLLECTION_ID>',
    document_id = '<DOCUMENT_ID>'
)
```
```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

vectors_db = VectorsDB.new(client)

result = vectors_db.get_document(
    database_id: '<DATABASE_ID>',
    collection_id: '<COLLECTION_ID>',
    document_id: '<DOCUMENT_ID>'
)
```
```csharp
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

VectorsDB vectorsDB = new VectorsDB(client);

Document result = await vectorsDB.GetDocument(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    documentId: "<DOCUMENT_ID>"
);
```
```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

VectorsDB vectorsDB = VectorsDB(client);

Document result = await vectorsDB.getDocument(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>',
);
```
```kotlin
import io.appwrite.Client
import io.appwrite.services.VectorsDB

val client = Client(context)
    .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 vectorsDB = VectorsDB(client)

val response = vectorsDB.getDocument(
    databaseId = "<DATABASE_ID>",
    collectionId = "<COLLECTION_ID>",
    documentId = "<DOCUMENT_ID>",
)
```
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.VectorsDB;

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

VectorsDB vectorsDB = new VectorsDB(client);

vectorsDB.getDocument(
    "<DATABASE_ID>",
    "<COLLECTION_ID>",
    "<DOCUMENT_ID>",
    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("<YOUR_PROJECT_ID>") // Your project ID
    .setKey("<YOUR_API_KEY>") // Your secret API key

let vectorsDB = VectorsDB(client)

let document = try await vectorsDB.getDocument(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    documentId: "<DOCUMENT_ID>"
)
```
```server-rust
use appwrite::Client;
use appwrite::services::VectorsDB;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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

    let vectors_db = VectorsDB::new(&client);

    let result = vectors_db.get_document(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
        "<DOCUMENT_ID>",
        None, // queries (optional)
        None, // transactionId (optional)
    ).await?;

    println!("{:?}", result);
    Ok(())
}
```
```bash
appwrite vectorsdb get-document \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --document-id <DOCUMENT_ID>
```

### List documents
Use the `listDocuments` method to read documents from a collection. Pass [queries](/docs/products/databases/vectorsdb/queries) to filter, order, and paginate the results.

To rank documents by similarity to a query vector, use a vector search query instead. See [Vector search](/docs/products/databases/vectorsdb/vector-search).

`listDocuments` sends queries in the URL. When your queries are too long for that, `createQuery` takes the same arguments in the request body and returns the same document list. See [Send queries in the request body](/docs/products/databases/vectorsdb/vector-search#create-query).

```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 vectorsDB = new sdk.VectorsDB(client);

const result = await vectorsDB.listDocuments({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    queries: [
        sdk.Query.limit(10)
    ]
});
```
```deno
import * as sdk from "npm: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 vectorsDB = new sdk.VectorsDB(client);

const result = await vectorsDB.listDocuments({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    queries: [
        sdk.Query.limit(10)
    ]
});
```
```php
<?php

use Appwrite\Client;
use Appwrite\Services\VectorsDB;
use Appwrite\Query;

$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

$vectorsDB = new VectorsDB($client);

$result = $vectorsDB->listDocuments(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    queries: [
        Query::limit(10)
    ]
);
```
```python
from appwrite.client import Client
from appwrite.services.vectors_db import VectorsDB
from appwrite.query import Query

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

vectors_db = VectorsDB(client)

result = vectors_db.list_documents(
    database_id = '<DATABASE_ID>',
    collection_id = '<COLLECTION_ID>',
    queries = [
        Query.limit(10)
    ]
)
```
```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

vectors_db = VectorsDB.new(client)

result = vectors_db.list_documents(
    database_id: '<DATABASE_ID>',
    collection_id: '<COLLECTION_ID>',
    queries: [
        Query.limit(10)
    ]
)
```
```csharp
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

VectorsDB vectorsDB = new VectorsDB(client);

DocumentList result = await vectorsDB.ListDocuments(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    queries: new List<string> {
        Query.Limit(10)
    }
);
```
```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

VectorsDB vectorsDB = VectorsDB(client);

DocumentList result = await vectorsDB.listDocuments(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    queries: [
        Query.limit(10)
    ],
);
```
```kotlin
import io.appwrite.Client
import io.appwrite.Query
import io.appwrite.services.VectorsDB

val client = Client(context)
    .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 vectorsDB = VectorsDB(client)

val response = vectorsDB.listDocuments(
    databaseId = "<DATABASE_ID>",
    collectionId = "<COLLECTION_ID>",
    queries = listOf(
        Query.limit(10)
    ),
)
```
```java
import io.appwrite.Client;
import io.appwrite.Query;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.VectorsDB;
import java.util.List;

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

VectorsDB vectorsDB = new VectorsDB(client);

vectorsDB.listDocuments(
    "<DATABASE_ID>",
    "<COLLECTION_ID>",
    List.of(
        Query.limit(10)
    ),
    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("<YOUR_PROJECT_ID>") // Your project ID
    .setKey("<YOUR_API_KEY>") // Your secret API key

let vectorsDB = VectorsDB(client)

let documentList = try await vectorsDB.listDocuments(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    queries: [
        Query.limit(10)
    ]
)
```
```server-rust
use appwrite::Client;
use appwrite::services::VectorsDB;
use appwrite::query::Query;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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

    let vectors_db = VectorsDB::new(&client);

    let result = vectors_db.list_documents(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
        Some(vec![Query::limit(10).to_string()]),
        None, // transactionId (optional)
        None, // total (optional)
        None, // ttl (optional)
    ).await?;

    println!("{:?}", result);
    Ok(())
}
```
```bash
appwrite vectorsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"limit","values":[10]}'
```

### Update document
Use the `updateDocument` method to update a document. The update is a patch, so you only pass the fields you want to change. To change the vector, pass a new `embeddings` array of the same length as the collection's `dimension`.

```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 vectorsDB = new sdk.VectorsDB(client);

const result = await vectorsDB.updateDocument({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>',
    data: {
        embeddings: [0.20, 0.10, 0.60, 0.10],
        metadata: { title: 'Hamlet', year: 1602 }
    }
});
```
```deno
import * as sdk from "npm: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 vectorsDB = new sdk.VectorsDB(client);

const result = await vectorsDB.updateDocument({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>',
    data: {
        embeddings: [0.20, 0.10, 0.60, 0.10],
        metadata: { title: 'Hamlet', year: 1602 }
    }
});
```
```php
<?php

use Appwrite\Client;
use Appwrite\Services\VectorsDB;

$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

$vectorsDB = new VectorsDB($client);

$result = $vectorsDB->updateDocument(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>',
    data: [
        'embeddings' => [0.20, 0.10, 0.60, 0.10],
        'metadata' => ['title' => 'Hamlet', 'year' => 1602]
    ]
);
```
```python
from appwrite.client import Client
from appwrite.services.vectors_db import VectorsDB

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

vectors_db = VectorsDB(client)

result = vectors_db.update_document(
    database_id = '<DATABASE_ID>',
    collection_id = '<COLLECTION_ID>',
    document_id = '<DOCUMENT_ID>',
    data = {
        "embeddings": [0.20, 0.10, 0.60, 0.10],
        "metadata": { "title": "Hamlet", "year": 1602 }
    }
)
```
```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

vectors_db = VectorsDB.new(client)

result = vectors_db.update_document(
    database_id: '<DATABASE_ID>',
    collection_id: '<COLLECTION_ID>',
    document_id: '<DOCUMENT_ID>',
    data: {
        "embeddings" => [0.20, 0.10, 0.60, 0.10],
        "metadata" => { "title" => "Hamlet", "year" => 1602 }
    }
)
```
```csharp
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

VectorsDB vectorsDB = new VectorsDB(client);

Document result = await vectorsDB.UpdateDocument(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    documentId: "<DOCUMENT_ID>",
    data: new Dictionary<string, object> {
        { "embeddings", new List<double> { 0.20, 0.10, 0.60, 0.10 } },
        { "metadata", new Dictionary<string, object> { { "title", "Hamlet" }, { "year", 1602 } } }
    }
);
```
```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

VectorsDB vectorsDB = VectorsDB(client);

Document result = await vectorsDB.updateDocument(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>',
    data: {
        "embeddings": [0.20, 0.10, 0.60, 0.10],
        "metadata": { "title": "Hamlet", "year": 1602 }
    },
);
```
```kotlin
import io.appwrite.Client
import io.appwrite.services.VectorsDB

val client = Client(context)
    .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 vectorsDB = VectorsDB(client)

val response = vectorsDB.updateDocument(
    databaseId = "<DATABASE_ID>",
    collectionId = "<COLLECTION_ID>",
    documentId = "<DOCUMENT_ID>",
    data = mapOf(
        "embeddings" to listOf(0.20, 0.10, 0.60, 0.10),
        "metadata" to mapOf("title" to "Hamlet", "year" to 1602)
    ),
)
```
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.VectorsDB;
import java.util.List;
import java.util.Map;

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

VectorsDB vectorsDB = new VectorsDB(client);

vectorsDB.updateDocument(
    "<DATABASE_ID>",
    "<COLLECTION_ID>",
    "<DOCUMENT_ID>",
    Map.of(
        "embeddings", List.of(0.20, 0.10, 0.60, 0.10),
        "metadata", Map.of("title", "Hamlet", "year", 1602)
    ),
    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("<YOUR_PROJECT_ID>") // Your project ID
    .setKey("<YOUR_API_KEY>") // Your secret API key

let vectorsDB = VectorsDB(client)

let document = try await vectorsDB.updateDocument(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    documentId: "<DOCUMENT_ID>",
    data: [
        "embeddings": [0.20, 0.10, 0.60, 0.10],
        "metadata": ["title": "Hamlet", "year": 1602]
    ]
)
```
```server-rust
use appwrite::Client;
use appwrite::services::VectorsDB;
use serde_json::json;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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

    let vectors_db = VectorsDB::new(&client);

    let result = vectors_db.update_document(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
        "<DOCUMENT_ID>",
        Some(json!({
            "embeddings": [0.20, 0.10, 0.60, 0.10],
            "metadata": { "title": "Hamlet", "year": 1602 }
        })),
        None, // permissions (optional)
        None, // transactionId (optional)
    ).await?;

    println!("{:?}", result);
    Ok(())
}
```
```bash
appwrite vectorsdb update-document \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --document-id <DOCUMENT_ID> \
    --data '{ "embeddings": [0.20, 0.10, 0.60, 0.10], "metadata": { "title": "Hamlet", "year": 1602 } }'
```

### Upsert document
Use the `upsertDocument` method to create a document if it doesn't exist, or update it if it does.

```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 vectorsDB = new sdk.VectorsDB(client);

const result = await vectorsDB.upsertDocument({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>',
    data: {
        embeddings: [0.50, 0.50, 0.50, 0.50],
        metadata: { title: 'Hamlet', year: 1603 }
    }
});
```
```deno
import * as sdk from "npm: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 vectorsDB = new sdk.VectorsDB(client);

const result = await vectorsDB.upsertDocument({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>',
    data: {
        embeddings: [0.50, 0.50, 0.50, 0.50],
        metadata: { title: 'Hamlet', year: 1603 }
    }
});
```
```php
<?php

use Appwrite\Client;
use Appwrite\Services\VectorsDB;

$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

$vectorsDB = new VectorsDB($client);

$result = $vectorsDB->upsertDocument(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>',
    data: [
        'embeddings' => [0.50, 0.50, 0.50, 0.50],
        'metadata' => ['title' => 'Hamlet', 'year' => 1603]
    ]
);
```
```python
from appwrite.client import Client
from appwrite.services.vectors_db import VectorsDB

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

vectors_db = VectorsDB(client)

result = vectors_db.upsert_document(
    database_id = '<DATABASE_ID>',
    collection_id = '<COLLECTION_ID>',
    document_id = '<DOCUMENT_ID>',
    data = {
        "embeddings": [0.50, 0.50, 0.50, 0.50],
        "metadata": { "title": "Hamlet", "year": 1603 }
    }
)
```
```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

vectors_db = VectorsDB.new(client)

result = vectors_db.upsert_document(
    database_id: '<DATABASE_ID>',
    collection_id: '<COLLECTION_ID>',
    document_id: '<DOCUMENT_ID>',
    data: {
        "embeddings" => [0.50, 0.50, 0.50, 0.50],
        "metadata" => { "title" => "Hamlet", "year" => 1603 }
    }
)
```
```csharp
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

VectorsDB vectorsDB = new VectorsDB(client);

Document result = await vectorsDB.UpsertDocument(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    documentId: "<DOCUMENT_ID>",
    data: new Dictionary<string, object> {
        { "embeddings", new List<double> { 0.50, 0.50, 0.50, 0.50 } },
        { "metadata", new Dictionary<string, object> { { "title", "Hamlet" }, { "year", 1603 } } }
    }
);
```
```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

VectorsDB vectorsDB = VectorsDB(client);

Document result = await vectorsDB.upsertDocument(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>',
    data: {
        "embeddings": [0.50, 0.50, 0.50, 0.50],
        "metadata": { "title": "Hamlet", "year": 1603 }
    },
);
```
```kotlin
import io.appwrite.Client
import io.appwrite.services.VectorsDB

val client = Client(context)
    .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 vectorsDB = VectorsDB(client)

val response = vectorsDB.upsertDocument(
    databaseId = "<DATABASE_ID>",
    collectionId = "<COLLECTION_ID>",
    documentId = "<DOCUMENT_ID>",
    data = mapOf(
        "embeddings" to listOf(0.50, 0.50, 0.50, 0.50),
        "metadata" to mapOf("title" to "Hamlet", "year" to 1603)
    ),
)
```
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.VectorsDB;
import java.util.List;
import java.util.Map;

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

VectorsDB vectorsDB = new VectorsDB(client);

vectorsDB.upsertDocument(
    "<DATABASE_ID>",
    "<COLLECTION_ID>",
    "<DOCUMENT_ID>",
    Map.of(
        "embeddings", List.of(0.50, 0.50, 0.50, 0.50),
        "metadata", Map.of("title", "Hamlet", "year", 1603)
    ),
    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("<YOUR_PROJECT_ID>") // Your project ID
    .setKey("<YOUR_API_KEY>") // Your secret API key

let vectorsDB = VectorsDB(client)

let document = try await vectorsDB.upsertDocument(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    documentId: "<DOCUMENT_ID>",
    data: [
        "embeddings": [0.50, 0.50, 0.50, 0.50],
        "metadata": ["title": "Hamlet", "year": 1603]
    ]
)
```
```server-rust
use appwrite::Client;
use appwrite::services::VectorsDB;
use serde_json::json;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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

    let vectors_db = VectorsDB::new(&client);

    let result = vectors_db.upsert_document(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
        "<DOCUMENT_ID>",
        Some(json!({
            "embeddings": [0.50, 0.50, 0.50, 0.50],
            "metadata": { "title": "Hamlet", "year": 1603 }
        })),
        None, // permissions (optional)
        None, // transactionId (optional)
    ).await?;

    println!("{:?}", result);
    Ok(())
}
```
```bash
appwrite vectorsdb upsert-document \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --document-id <DOCUMENT_ID> \
    --data '{ "embeddings": [0.50, 0.50, 0.50, 0.50], "metadata": { "title": "Hamlet", "year": 1603 } }'
```

### Delete document
Use the `deleteDocument` method to remove a document from a collection.

```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 vectorsDB = new sdk.VectorsDB(client);

const result = await vectorsDB.deleteDocument({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>'
});
```
```deno
import * as sdk from "npm: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 vectorsDB = new sdk.VectorsDB(client);

const result = await vectorsDB.deleteDocument({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>'
});
```
```php
<?php

use Appwrite\Client;
use Appwrite\Services\VectorsDB;

$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

$vectorsDB = new VectorsDB($client);

$result = $vectorsDB->deleteDocument(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>'
);
```
```python
from appwrite.client import Client
from appwrite.services.vectors_db import VectorsDB

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

vectors_db = VectorsDB(client)

result = vectors_db.delete_document(
    database_id = '<DATABASE_ID>',
    collection_id = '<COLLECTION_ID>',
    document_id = '<DOCUMENT_ID>'
)
```
```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

vectors_db = VectorsDB.new(client)

result = vectors_db.delete_document(
    database_id: '<DATABASE_ID>',
    collection_id: '<COLLECTION_ID>',
    document_id: '<DOCUMENT_ID>'
)
```
```csharp
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

VectorsDB vectorsDB = new VectorsDB(client);

await vectorsDB.DeleteDocument(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    documentId: "<DOCUMENT_ID>"
);
```
```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

VectorsDB vectorsDB = VectorsDB(client);

await vectorsDB.deleteDocument(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>',
);
```
```kotlin
import io.appwrite.Client
import io.appwrite.services.VectorsDB

val client = Client(context)
    .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 vectorsDB = VectorsDB(client)

vectorsDB.deleteDocument(
    databaseId = "<DATABASE_ID>",
    collectionId = "<COLLECTION_ID>",
    documentId = "<DOCUMENT_ID>",
)
```
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.VectorsDB;

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

VectorsDB vectorsDB = new VectorsDB(client);

vectorsDB.deleteDocument(
    "<DATABASE_ID>",
    "<COLLECTION_ID>",
    "<DOCUMENT_ID>",
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        System.out.println("Deleted");
    })
);
```
```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 vectorsDB = VectorsDB(client)

try await vectorsDB.deleteDocument(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    documentId: "<DOCUMENT_ID>"
)
```
```server-rust
use appwrite::Client;
use appwrite::services::VectorsDB;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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

    let vectors_db = VectorsDB::new(&client);

    vectors_db.delete_document(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
        "<DOCUMENT_ID>",
        None, // transactionId (optional)
    ).await?;

    Ok(())
}
```
```bash
appwrite vectorsdb delete-document \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --document-id <DOCUMENT_ID>
```

### Permissions
To access documents through a [Client SDK](/docs/sdks#client), you must grant the relevant permissions.
By default, Appwrite doesn't grant any user permissions when a new collection is created.

You can configure permissions at the collection level, or enable `documentSecurity` on the collection to also set permissions on individual documents.

[Learn about configuring permissions](/docs/products/databases/vectorsdb/permissions).

### Next steps

Continue learning with these related guides:

- [Vector search](/docs/products/databases/vectorsdb/vector-search): Rank documents by similarity to a query vector using cosine, dot product, or Euclidean distance.

- [Bulk operations](/docs/products/databases/vectorsdb/bulk-operations): Create, update, upsert, and delete many documents in a single request.

- [Collections](/docs/products/databases/vectorsdb/collections): Configure the dimension and indexes that define how your vectors are stored and searched.

---

## Embeddings

URL: https://appwrite.io/docs/products/databases/vectorsdb/embeddings

An embedding is a list of numbers that represents the meaning of a piece of text.
Appwrite generates embeddings for you with built-in models, so you can turn text into vectors and store them in a collection without running a separate embedding service.

The typical flow is two steps: generate an embedding from your text, then store that embedding in a document's `embeddings` field. Once stored, you can run [vector search](/docs/products/databases/vectorsdb/vector-search) over your documents.

### Generate embeddings
Embeddings come from the Embeddings service rather than the VectorsDB service, and generating them doesn't involve a database or collection at all.

Use the `createTextEmbeddings` method to turn one or more strings into vector embeddings. Pass an array of `texts` and, optionally, a `model`. When you omit `model`, Appwrite uses the default `nomic-embed-text` model. Appwrite [Server SDKs](/docs/sdks#server) require an [API key](/docs/advanced/platform/api-keys) with the `embeddings.write` scope.

```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 embeddings = new sdk.Embeddings(client);

const result = await embeddings.createTextEmbeddings({
    texts: ['The quick brown fox jumps over the lazy dog'],
    model: sdk.EmbeddingModel.Nomicembedtext // optional, defaults to nomic-embed-text
});
```
```deno
import * as sdk from "npm: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 embeddings = new sdk.Embeddings(client);

const result = await embeddings.createTextEmbeddings({
    texts: ['The quick brown fox jumps over the lazy dog'],
    model: sdk.EmbeddingModel.Nomicembedtext // optional, defaults to nomic-embed-text
});
```
```php
<?php

use Appwrite\Client;
use Appwrite\Services\Embeddings;
use Appwrite\Enums\EmbeddingModel;

$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

$embeddings = new Embeddings($client);

$result = $embeddings->createTextEmbeddings(
    texts: ['The quick brown fox jumps over the lazy dog'],
    model: EmbeddingModel::NOMICEMBEDTEXT() // optional, defaults to nomic-embed-text
);
```
```python
from appwrite.client import Client
from appwrite.services.embeddings import Embeddings
from appwrite.enums import EmbeddingModel

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

embeddings = Embeddings(client)

result = embeddings.create_text_embeddings(
    texts = ['The quick brown fox jumps over the lazy dog'],
    model = EmbeddingModel.NOMIC_EMBED_TEXT # optional, defaults to nomic-embed-text
)
```
```ruby
require 'appwrite'

include Appwrite
include Appwrite::Enums

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

embeddings = Embeddings.new(client)

result = embeddings.create_text_embeddings(
    texts: ['The quick brown fox jumps over the lazy dog'],
    model: EmbeddingModel::NOMIC_EMBED_TEXT # optional, defaults to nomic-embed-text
)
```
```csharp
using Appwrite;
using Appwrite.Enums;
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

Embeddings embeddings = new Embeddings(client);

EmbeddingList result = await embeddings.CreateTextEmbeddings(
    texts: new List<string> { "The quick brown fox jumps over the lazy dog" },
    model: EmbeddingModel.NomicEmbedText // optional, defaults to nomic-embed-text
);
```
```dart
import 'package:dart_appwrite/dart_appwrite.dart';
import 'package:dart_appwrite/enums.dart' as enums;

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

Embeddings embeddings = Embeddings(client);

EmbeddingList result = await embeddings.createTextEmbeddings(
    texts: ['The quick brown fox jumps over the lazy dog'],
    model: enums.EmbeddingModel.nomicEmbedText, // optional, defaults to nomic-embed-text
);
```
```kotlin
import io.appwrite.Client
import io.appwrite.services.Embeddings
import io.appwrite.enums.EmbeddingModel

val client = Client(context)
    .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 embeddings = Embeddings(client)

val response = embeddings.createTextEmbeddings(
    texts = listOf("The quick brown fox jumps over the lazy dog"),
    model = EmbeddingModel.NOMIC_EMBED_TEXT // optional, defaults to nomic-embed-text
)
```
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Embeddings;
import io.appwrite.enums.EmbeddingModel;

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

Embeddings embeddings = new Embeddings(client);

embeddings.createTextEmbeddings(
    List.of("The quick brown fox jumps over the lazy dog"), // texts
    EmbeddingModel.NOMIC_EMBED_TEXT, // model (optional)
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        System.out.println(result);
    })
);
```
```swift
import Appwrite
import AppwriteEnums

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 embeddings = Embeddings(client)

let embeddingList = try await embeddings.createTextEmbeddings(
    texts: ["The quick brown fox jumps over the lazy dog"],
    model: .nomicEmbedText // optional, defaults to nomic-embed-text
)
```
```server-rust
use appwrite::Client;
use appwrite::services::Embeddings;
use appwrite::enums::EmbeddingModel;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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

    let embeddings = Embeddings::new(&client);

    let result = embeddings.create_text_embeddings(
        vec!["The quick brown fox jumps over the lazy dog"],
        Some(EmbeddingModel::NomicEmbedText), // optional, defaults to nomic-embed-text
    ).await?;

    println!("{:?}", result);
    Ok(())
}
```
```bash
appwrite embeddings create-text-embeddings \
    --texts 'The quick brown fox jumps over the lazy dog' \
    --model 'nomic-embed-text'
```

The response is an embedding list. The `embeddings` array holds one entry per input text, in the same order you passed them.

```json
{
    "total": 1,
    "embeddings": [
        {
            "model": "nomic-embed-text",
            "dimension": 768,
            "embedding": [-0.012246467, 0.02621112, -0.15247375, ...],
            "error": ""
        }
    ]
}
```

Each entry contains:

| Field | Description |
| --- | --- |
| `model` | The model that generated this embedding. |
| `dimension` | The number of values in the embedding vector. |
| `embedding` | The embedding vector as an array of floats. If generation fails, this is an empty array. |
| `error` | An error message if this text could not be embedded. An empty string means there was no error. |

### Available models
Appwrite ships with the following text embedding models. The `dimension` of a model is the length of the vector it produces, and it must match the `dimension` you set on the [collection](/docs/products/databases/vectorsdb/collections) where you store the embeddings.

| Model | Dimension | Provider | Languages | Best for | Notes |
| --- | --- | --- | --- | --- | --- |
| `nomic-embed-text` | 768 | Nomic AI | English | General-purpose retrieval over long English documents | **Default.** 8K context window, so long documents embed in one call. |
| `all-minilm` | 384 | Sentence Transformers | English | High-volume workloads where speed and storage matter most | Fast to generate and cheap to store, at some cost to accuracy. |
| `embedding-gemma` (coming soon) | 768 | Google | 100+ | Multilingual search and cross-language retrieval | A query in one language matches content in another. |
| `bge-small` (coming soon) | 384 | BAAI | English | Ranking and reranking short English passages | Tuned for ranking quality over speed. |

Models marked coming soon are not available yet. Passing one to `createTextEmbeddings` returns a `400` error until it ships.

Embedding usage is metered in tokens and priced per model. See [Text Embeddings billing](/docs/advanced/billing/embeddings) for plan allowances and rates.

**Match the model to your collection dimension**

Pick a model before you create your collection. In the Console you pick the model and the collection takes its dimension. Through an SDK you set `dimension` to the model's dimension. Every document in a collection uses vectors of the same length, so a collection only works with models that match its dimension.

### Store embeddings
Once you have an embedding, store it in a document's `embeddings` field. The collection's `dimension` must match the embedding's `dimension`. You can store any related data alongside the vector in the document's `metadata` field.

```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 embeddings = new sdk.Embeddings(client);
const vectorsDB = new sdk.VectorsDB(client);

const text = 'The quick brown fox jumps over the lazy dog';

const generated = await embeddings.createTextEmbeddings({
    texts: [text]
});

const result = await vectorsDB.createDocument({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: sdk.ID.unique(),
    data: {
        embeddings: generated.embeddings[0].embedding,
        metadata: { text }
    }
});
```
```deno
import * as sdk from "npm: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 embeddings = new sdk.Embeddings(client);
const vectorsDB = new sdk.VectorsDB(client);

const text = 'The quick brown fox jumps over the lazy dog';

const generated = await embeddings.createTextEmbeddings({
    texts: [text]
});

const result = await vectorsDB.createDocument({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: sdk.ID.unique(),
    data: {
        embeddings: generated.embeddings[0].embedding,
        metadata: { text }
    }
});
```
```php
<?php

use Appwrite\Client;
use Appwrite\Services\Embeddings;
use Appwrite\Services\VectorsDB;
use Appwrite\ID;

$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

$embeddings = new Embeddings($client);
$vectorsDB = new VectorsDB($client);

$text = 'The quick brown fox jumps over the lazy dog';

$generated = $embeddings->createTextEmbeddings(
    texts: [$text]
);

$result = $vectorsDB->createDocument(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: ID::unique(),
    data: [
        'embeddings' => $generated['embeddings'][0]['embedding'],
        'metadata' => ['text' => $text]
    ]
);
```
```python
from appwrite.client import Client
from appwrite.services.embeddings import Embeddings
from appwrite.services.vectors_db import VectorsDB
from appwrite.id import ID

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

embeddings = Embeddings(client)
vectors_db = VectorsDB(client)

text = 'The quick brown fox jumps over the lazy dog'

generated = embeddings.create_text_embeddings(
    texts = [text]
)

result = vectors_db.create_document(
    database_id = '<DATABASE_ID>',
    collection_id = '<COLLECTION_ID>',
    document_id = ID.unique(),
    data = {
        "embeddings": generated["embeddings"][0]["embedding"],
        "metadata": { "text": text }
    }
)
```
```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

embeddings = Embeddings.new(client)
vectors_db = VectorsDB.new(client)

text = 'The quick brown fox jumps over the lazy dog'

generated = embeddings.create_text_embeddings(
    texts: [text]
)

result = vectors_db.create_document(
    database_id: '<DATABASE_ID>',
    collection_id: '<COLLECTION_ID>',
    document_id: ID.unique(),
    data: {
        "embeddings" => generated.embeddings[0].embedding,
        "metadata" => { "text" => text }
    }
)
```
```csharp
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

Embeddings embeddings = new Embeddings(client);
VectorsDB vectorsDB = new VectorsDB(client);

var text = "The quick brown fox jumps over the lazy dog";

EmbeddingList generated = await embeddings.CreateTextEmbeddings(
    texts: new List<string> { text }
);

Document result = await vectorsDB.CreateDocument(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    documentId: ID.Unique(),
    data: new {
        embeddings = generated.Embeddings[0].XEmbedding,
        metadata = new { text }
    }
);
```
```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

Embeddings embeddings = Embeddings(client);
VectorsDB vectorsDB = VectorsDB(client);

final text = 'The quick brown fox jumps over the lazy dog';

EmbeddingList generated = await embeddings.createTextEmbeddings(
    texts: [text],
);

Document result = await vectorsDB.createDocument(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: ID.unique(),
    data: {
        "embeddings": generated.embeddings[0].embedding,
        "metadata": { "text": text }
    },
);
```
```kotlin
import io.appwrite.Client
import io.appwrite.ID
import io.appwrite.services.Embeddings
import io.appwrite.services.VectorsDB

val client = Client(context)
    .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 embeddings = Embeddings(client)
val vectorsDB = VectorsDB(client)

val text = "The quick brown fox jumps over the lazy dog"

val generated = embeddings.createTextEmbeddings(
    texts = listOf(text),
)

val response = vectorsDB.createDocument(
    databaseId = "<DATABASE_ID>",
    collectionId = "<COLLECTION_ID>",
    documentId = ID.unique(),
    data = mapOf(
        "embeddings" to generated.embeddings[0].embedding,
        "metadata" to mapOf("text" to text)
    ),
)
```
```java
import io.appwrite.Client;
import io.appwrite.ID;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Embeddings;
import io.appwrite.services.VectorsDB;
import java.util.List;
import java.util.Map;

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

Embeddings embeddings = new Embeddings(client);
VectorsDB vectorsDB = new VectorsDB(client);

String text = "The quick brown fox jumps over the lazy dog";

embeddings.createTextEmbeddings(
    List.of(text),
    new CoroutineCallback<>((generated, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        vectorsDB.createDocument(
            "<DATABASE_ID>",
            "<COLLECTION_ID>",
            ID.unique(),
            Map.of(
                "embeddings", generated.getEmbeddings().get(0).getEmbedding(),
                "metadata", Map.of("text", text)
            ),
            new CoroutineCallback<>((result, err) -> {
                if (err != null) {
                    err.printStackTrace();
                    return;
                }

                System.out.println(result);
            })
        );
    })
);
```
```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 embeddings = Embeddings(client)
let vectorsDB = VectorsDB(client)

let text = "The quick brown fox jumps over the lazy dog"

let generated = try await embeddings.createTextEmbeddings(
    texts: [text]
)

let document = try await vectorsDB.createDocument(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    documentId: ID.unique(),
    data: [
        "embeddings": generated.embeddings[0].embedding,
        "metadata": ["text": text]
    ]
)
```
```server-rust
use appwrite::Client;
use appwrite::services::{Embeddings, VectorsDB};
use appwrite::id::ID;
use serde_json::json;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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

    let embeddings = Embeddings::new(&client);
    let vectors_db = VectorsDB::new(&client);

    let text = "The quick brown fox jumps over the lazy dog";

    let generated = embeddings.create_text_embeddings(
        vec![text],
        None, // model (optional)
    ).await?;

    let result = vectors_db.create_document(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
        ID::unique(),
        json!({
            "embeddings": generated.embeddings[0].embedding,
            "metadata": { "text": text }
        }),
        None, // permissions (optional)
    ).await?;

    println!("{:?}", result);
    Ok(())
}
```
```bash
appwrite vectorsdb create-document \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --document-id 'unique()' \
    --data '{ "embeddings": [-0.012246467, 0.02621112, -0.15247375], "metadata": { "text": "The quick brown fox jumps over the lazy dog" } }'
```

**Embed in batches**

You can pass several strings in one `createTextEmbeddings` call to embed them together. The response returns one entry per input text, in order, so you can map each embedding back to its source text before storing.

### Next steps
With embeddings stored in your documents, you can find the most similar documents to a query vector with vector search.

[Learn about vector search](/docs/products/databases/vectorsdb/vector-search)

---

## Order

URL: https://appwrite.io/docs/products/databases/vectorsdb/order

You can order the documents returned by [listDocuments](/docs/products/databases/vectorsdb/documents#list-documents) using the `Query.orderAsc()` and `Query.orderDesc()` query methods.

VectorsDB orders on the system fields that Appwrite maintains on every document, such as `$createdAt`, `$updatedAt`, `$sequence`, and `$id`.

**Metadata sub-fields can't be ordered**

A VectorsDB collection has a [fixed schema](/docs/products/databases/vectorsdb/collections#schema): an `embeddings` vector and a `metadata` object. Because `metadata` is stored as a single JSON object rather than typed columns, you can't order by a value inside it. Ordering by a nested path like `metadata.title` is rejected with `Invalid query: Cannot order by nested attribute: metadata`. Order by the system fields below instead, or keep an orderable value in a system field such as `$createdAt`.

### Order by a system field
Pass `Query.orderAsc()` or `Query.orderDesc()` with a system field to sort the documents returned. The example below orders documents from newest to oldest by `$createdAt`.

```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 vectorsDB = new sdk.VectorsDB(client);

const result = await vectorsDB.listDocuments({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    queries: [
        sdk.Query.orderDesc('$createdAt')
    ]
});
```
```deno
import * as sdk from "npm: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 vectorsDB = new sdk.VectorsDB(client);

const result = await vectorsDB.listDocuments({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    queries: [
        sdk.Query.orderDesc('$createdAt')
    ]
});
```
```php
<?php

use Appwrite\Client;
use Appwrite\Services\VectorsDB;
use Appwrite\Query;

$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

$vectorsDB = new VectorsDB($client);

$result = $vectorsDB->listDocuments(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    queries: [
        Query::orderDesc('$createdAt')
    ]
);
```
```python
from appwrite.client import Client
from appwrite.services.vectors_db import VectorsDB
from appwrite.query import Query

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

vectors_db = VectorsDB(client)

result = vectors_db.list_documents(
    database_id = '<DATABASE_ID>',
    collection_id = '<COLLECTION_ID>',
    queries = [
        Query.order_desc('$createdAt')
    ]
)
```
```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

vectors_db = VectorsDB.new(client)

result = vectors_db.list_documents(
    database_id: '<DATABASE_ID>',
    collection_id: '<COLLECTION_ID>',
    queries: [
        Query.order_desc('$createdAt')
    ]
)
```
```csharp
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

VectorsDB vectorsDB = new VectorsDB(client);

DocumentList result = await vectorsDB.ListDocuments(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    queries: new List<string> {
        Query.OrderDesc("$createdAt")
    }
);
```
```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

VectorsDB vectorsDB = VectorsDB(client);

DocumentList result = await vectorsDB.listDocuments(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    queries: [
        Query.orderDesc('\$createdAt')
    ],
);
```
```kotlin
import io.appwrite.Client
import io.appwrite.Query
import io.appwrite.services.VectorsDB

val client = Client(context)
    .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 vectorsDB = VectorsDB(client)

val response = vectorsDB.listDocuments(
    databaseId = "<DATABASE_ID>",
    collectionId = "<COLLECTION_ID>",
    queries = listOf(
        Query.orderDesc("\$createdAt")
    ),
)
```
```java
import io.appwrite.Client;
import io.appwrite.Query;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.VectorsDB;
import java.util.List;

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

VectorsDB vectorsDB = new VectorsDB(client);

vectorsDB.listDocuments(
    "<DATABASE_ID>",
    "<COLLECTION_ID>",
    List.of(
        Query.orderDesc("$createdAt")
    ),
    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("<YOUR_PROJECT_ID>") // Your project ID
    .setKey("<YOUR_API_KEY>") // Your secret API key

let vectorsDB = VectorsDB(client)

let documentList = try await vectorsDB.listDocuments(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    queries: [
        Query.orderDesc("$createdAt")
    ]
)
```
```server-rust
use appwrite::Client;
use appwrite::services::VectorsDB;
use appwrite::query::Query;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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

    let vectors_db = VectorsDB::new(&client);

    let result = vectors_db.list_documents(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
        Some(vec![Query::order_desc("$createdAt").to_string()]),
        None, // transactionId (optional)
        None, // total (optional)
        None, // ttl (optional)
    ).await?;

    println!("{:?}", result);
    Ok(())
}
```
```bash
appwrite vectorsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"orderDesc","attribute":"$createdAt"}'
```

### Order by multiple fields
To sort by more than one field, pass multiple order queries. They are applied in order, so the first query is the primary sort and each later query breaks ties.

In the example below, documents are sorted first by `$createdAt` in descending order, then by `$id` in ascending order to break ties.

```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 vectorsDB = new sdk.VectorsDB(client);

const result = await vectorsDB.listDocuments({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    queries: [
        sdk.Query.orderDesc('$createdAt'), // Order first by creation time, newest first
        sdk.Query.orderAsc('$id') // Then break ties by document ID
    ]
});
```
```deno
import * as sdk from "npm: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 vectorsDB = new sdk.VectorsDB(client);

const result = await vectorsDB.listDocuments({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    queries: [
        sdk.Query.orderDesc('$createdAt'), // Order first by creation time, newest first
        sdk.Query.orderAsc('$id') // Then break ties by document ID
    ]
});
```
```php
<?php

use Appwrite\Client;
use Appwrite\Services\VectorsDB;
use Appwrite\Query;

$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

$vectorsDB = new VectorsDB($client);

$result = $vectorsDB->listDocuments(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    queries: [
        Query::orderDesc('$createdAt'),
        Query::orderAsc('$id')
    ]
);
```
```python
from appwrite.client import Client
from appwrite.services.vectors_db import VectorsDB
from appwrite.query import Query

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

vectors_db = VectorsDB(client)

result = vectors_db.list_documents(
    database_id = '<DATABASE_ID>',
    collection_id = '<COLLECTION_ID>',
    queries = [
        Query.order_desc('$createdAt'),
        Query.order_asc('$id')
    ]
)
```
```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

vectors_db = VectorsDB.new(client)

result = vectors_db.list_documents(
    database_id: '<DATABASE_ID>',
    collection_id: '<COLLECTION_ID>',
    queries: [
        Query.order_desc('$createdAt'),
        Query.order_asc('$id')
    ]
)
```
```csharp
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

VectorsDB vectorsDB = new VectorsDB(client);

DocumentList result = await vectorsDB.ListDocuments(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    queries: new List<string> {
        Query.OrderDesc("$createdAt"),
        Query.OrderAsc("$id")
    }
);
```
```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

VectorsDB vectorsDB = VectorsDB(client);

DocumentList result = await vectorsDB.listDocuments(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    queries: [
        Query.orderDesc('\$createdAt'),
        Query.orderAsc('\$id')
    ],
);
```
```kotlin
import io.appwrite.Client
import io.appwrite.Query
import io.appwrite.services.VectorsDB

val client = Client(context)
    .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 vectorsDB = VectorsDB(client)

val response = vectorsDB.listDocuments(
    databaseId = "<DATABASE_ID>",
    collectionId = "<COLLECTION_ID>",
    queries = listOf(
        Query.orderDesc("\$createdAt"),
        Query.orderAsc("\$id")
    ),
)
```
```java
import io.appwrite.Client;
import io.appwrite.Query;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.VectorsDB;
import java.util.List;

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

VectorsDB vectorsDB = new VectorsDB(client);

vectorsDB.listDocuments(
    "<DATABASE_ID>",
    "<COLLECTION_ID>",
    List.of(
        Query.orderDesc("$createdAt"),
        Query.orderAsc("$id")
    ),
    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("<YOUR_PROJECT_ID>") // Your project ID
    .setKey("<YOUR_API_KEY>") // Your secret API key

let vectorsDB = VectorsDB(client)

let documentList = try await vectorsDB.listDocuments(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    queries: [
        Query.orderDesc("$createdAt"),
        Query.orderAsc("$id")
    ]
)
```
```server-rust
use appwrite::Client;
use appwrite::services::VectorsDB;
use appwrite::query::Query;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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

    let vectors_db = VectorsDB::new(&client);

    let result = vectors_db.list_documents(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
        Some(vec![
            Query::order_desc("$createdAt").to_string(),
            Query::order_asc("$id").to_string(),
        ]),
        None, // transactionId (optional)
        None, // total (optional)
        None, // ttl (optional)
    ).await?;

    println!("{:?}", result);
    Ok(())
}
```
```bash
appwrite vectorsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"orderDesc","attribute":"$createdAt"}' \
    --queries '{"method":"orderAsc","attribute":"$id"}'
```

### Order by sequence
For ordering based on insertion order, use the `$sequence` field, which Appwrite adds to every document. Sorting by `$sequence` returns documents in the order they were created.

```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 vectorsDB = new sdk.VectorsDB(client);

const result = await vectorsDB.listDocuments({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    queries: [
        sdk.Query.orderAsc('$sequence')
    ]
});
```
```deno
import * as sdk from "npm: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 vectorsDB = new sdk.VectorsDB(client);

const result = await vectorsDB.listDocuments({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    queries: [
        sdk.Query.orderAsc('$sequence')
    ]
});
```
```php
<?php

use Appwrite\Client;
use Appwrite\Services\VectorsDB;
use Appwrite\Query;

$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

$vectorsDB = new VectorsDB($client);

$result = $vectorsDB->listDocuments(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    queries: [
        Query::orderAsc('$sequence')
    ]
);
```
```python
from appwrite.client import Client
from appwrite.services.vectors_db import VectorsDB
from appwrite.query import Query

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

vectors_db = VectorsDB(client)

result = vectors_db.list_documents(
    database_id = '<DATABASE_ID>',
    collection_id = '<COLLECTION_ID>',
    queries = [
        Query.order_asc('$sequence')
    ]
)
```
```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

vectors_db = VectorsDB.new(client)

result = vectors_db.list_documents(
    database_id: '<DATABASE_ID>',
    collection_id: '<COLLECTION_ID>',
    queries: [
        Query.order_asc('$sequence')
    ]
)
```
```csharp
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

VectorsDB vectorsDB = new VectorsDB(client);

DocumentList result = await vectorsDB.ListDocuments(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    queries: new List<string> {
        Query.OrderAsc("$sequence")
    }
);
```
```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

VectorsDB vectorsDB = VectorsDB(client);

DocumentList result = await vectorsDB.listDocuments(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    queries: [
        Query.orderAsc('\$sequence')
    ],
);
```
```kotlin
import io.appwrite.Client
import io.appwrite.Query
import io.appwrite.services.VectorsDB

val client = Client(context)
    .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 vectorsDB = VectorsDB(client)

val response = vectorsDB.listDocuments(
    databaseId = "<DATABASE_ID>",
    collectionId = "<COLLECTION_ID>",
    queries = listOf(
        Query.orderAsc("\$sequence")
    ),
)
```
```java
import io.appwrite.Client;
import io.appwrite.Query;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.VectorsDB;
import java.util.List;

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

VectorsDB vectorsDB = new VectorsDB(client);

vectorsDB.listDocuments(
    "<DATABASE_ID>",
    "<COLLECTION_ID>",
    List.of(
        Query.orderAsc("$sequence")
    ),
    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("<YOUR_PROJECT_ID>") // Your project ID
    .setKey("<YOUR_API_KEY>") // Your secret API key

let vectorsDB = VectorsDB(client)

let documentList = try await vectorsDB.listDocuments(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    queries: [
        Query.orderAsc("$sequence")
    ]
)
```
```server-rust
use appwrite::Client;
use appwrite::services::VectorsDB;
use appwrite::query::Query;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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

    let vectors_db = VectorsDB::new(&client);

    let result = vectors_db.list_documents(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
        Some(vec![Query::order_asc("$sequence").to_string()]),
        None, // transactionId (optional)
        None, // total (optional)
        None, // ttl (optional)
    ).await?;

    println!("{:?}", result);
    Ok(())
}
```
```bash
appwrite vectorsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"orderAsc","attribute":"$sequence"}'
```

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 insertion-order sorting without managing custom counter fields

### Order randomly
Use `Query.orderRandom()` to return documents in a random order. This is useful for sampling documents or surfacing varied results on each request. It takes no field.

```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 vectorsDB = new sdk.VectorsDB(client);

const result = await vectorsDB.listDocuments({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    queries: [
        sdk.Query.orderRandom()
    ]
});
```
```deno
import * as sdk from "npm: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 vectorsDB = new sdk.VectorsDB(client);

const result = await vectorsDB.listDocuments({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    queries: [
        sdk.Query.orderRandom()
    ]
});
```
```php
<?php

use Appwrite\Client;
use Appwrite\Services\VectorsDB;
use Appwrite\Query;

$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

$vectorsDB = new VectorsDB($client);

$result = $vectorsDB->listDocuments(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    queries: [
        Query::orderRandom()
    ]
);
```
```python
from appwrite.client import Client
from appwrite.services.vectors_db import VectorsDB
from appwrite.query import Query

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

vectors_db = VectorsDB(client)

result = vectors_db.list_documents(
    database_id = '<DATABASE_ID>',
    collection_id = '<COLLECTION_ID>',
    queries = [
        Query.order_random()
    ]
)
```
```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

vectors_db = VectorsDB.new(client)

result = vectors_db.list_documents(
    database_id: '<DATABASE_ID>',
    collection_id: '<COLLECTION_ID>',
    queries: [
        Query.order_random()
    ]
)
```
```csharp
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

VectorsDB vectorsDB = new VectorsDB(client);

DocumentList result = await vectorsDB.ListDocuments(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    queries: new List<string> {
        Query.OrderRandom()
    }
);
```
```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

VectorsDB vectorsDB = VectorsDB(client);

DocumentList result = await vectorsDB.listDocuments(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    queries: [
        Query.orderRandom()
    ],
);
```
```kotlin
import io.appwrite.Client
import io.appwrite.Query
import io.appwrite.services.VectorsDB

val client = Client(context)
    .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 vectorsDB = VectorsDB(client)

val response = vectorsDB.listDocuments(
    databaseId = "<DATABASE_ID>",
    collectionId = "<COLLECTION_ID>",
    queries = listOf(
        Query.orderRandom()
    ),
)
```
```java
import io.appwrite.Client;
import io.appwrite.Query;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.VectorsDB;
import java.util.List;

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

VectorsDB vectorsDB = new VectorsDB(client);

vectorsDB.listDocuments(
    "<DATABASE_ID>",
    "<COLLECTION_ID>",
    List.of(
        Query.orderRandom()
    ),
    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("<YOUR_PROJECT_ID>") // Your project ID
    .setKey("<YOUR_API_KEY>") // Your secret API key

let vectorsDB = VectorsDB(client)

let documentList = try await vectorsDB.listDocuments(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    queries: [
        Query.orderRandom()
    ]
)
```
```server-rust
use appwrite::Client;
use appwrite::services::VectorsDB;
use appwrite::query::Query;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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

    let vectors_db = VectorsDB::new(&client);

    let result = vectors_db.list_documents(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
        Some(vec![Query::order_random().to_string()]),
        None, // transactionId (optional)
        None, // total (optional)
        None, // ttl (optional)
    ).await?;

    println!("{:?}", result);
    Ok(())
}
```
```bash
appwrite vectorsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"orderRandom","values":[]}'
```

To rank documents by similarity to a query vector instead of ordering by a field, use a vector search query. See [Vector search](/docs/products/databases/vectorsdb/vector-search).

---

## Pagination

URL: https://appwrite.io/docs/products/databases/vectorsdb/pagination

As your collection grows in size, you'll need to paginate the documents returned.
Pagination improves performance by returning a subset of documents that match a query at a time, called a page.

By default, list operations return 25 documents per page, which can be changed using the `Query.limit()` query method.
There is no hard limit on the number of documents you can request. However, beware that **large pages can degrade performance**.

### Offset pagination

Offset pagination divides documents into pages of `N` documents each.
To read page number `P`, skip `offset = N * (P - 1)` documents, then read the next `N`.

Using `Query.limit()` and `Query.offset()` you can achieve offset pagination.
With `Query.limit()` you define how many documents can be returned from one request.
The `Query.offset()` is the number of documents you wish to skip before selecting documents.

```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 vectorsDB = new sdk.VectorsDB(client);

// Page 1
const page1 = await vectorsDB.listDocuments({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    queries: [
        sdk.Query.limit(25),
        sdk.Query.offset(0)
    ]
});

// Page 2
const page2 = await vectorsDB.listDocuments({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    queries: [
        sdk.Query.limit(25),
        sdk.Query.offset(25)
    ]
});
```
```deno
import * as sdk from "npm: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 vectorsDB = new sdk.VectorsDB(client);

// Page 1
const page1 = await vectorsDB.listDocuments({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    queries: [
        sdk.Query.limit(25),
        sdk.Query.offset(0)
    ]
});

// Page 2
const page2 = await vectorsDB.listDocuments({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    queries: [
        sdk.Query.limit(25),
        sdk.Query.offset(25)
    ]
});
```
```php
<?php

use Appwrite\Client;
use Appwrite\Services\VectorsDB;
use Appwrite\Query;

$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

$vectorsDB = new VectorsDB($client);

// Page 1
$page1 = $vectorsDB->listDocuments(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    queries: [
        Query::limit(25),
        Query::offset(0)
    ]
);

// Page 2
$page2 = $vectorsDB->listDocuments(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    queries: [
        Query::limit(25),
        Query::offset(25)
    ]
);
```
```python
from appwrite.client import Client
from appwrite.services.vectors_db import VectorsDB
from appwrite.query import Query

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

vectors_db = VectorsDB(client)

### Page 1
page1 = vectors_db.list_documents(
    database_id = '<DATABASE_ID>',
    collection_id = '<COLLECTION_ID>',
    queries = [
        Query.limit(25),
        Query.offset(0)
    ]
)

### Page 2
page2 = vectors_db.list_documents(
    database_id = '<DATABASE_ID>',
    collection_id = '<COLLECTION_ID>',
    queries = [
        Query.limit(25),
        Query.offset(25)
    ]
)
```
```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

vectors_db = VectorsDB.new(client)

### Page 1
page1 = vectors_db.list_documents(
    database_id: '<DATABASE_ID>',
    collection_id: '<COLLECTION_ID>',
    queries: [
        Query.limit(25),
        Query.offset(0)
    ]
)

### Page 2
page2 = vectors_db.list_documents(
    database_id: '<DATABASE_ID>',
    collection_id: '<COLLECTION_ID>',
    queries: [
        Query.limit(25),
        Query.offset(25)
    ]
)
```
```csharp
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

VectorsDB vectorsDB = new VectorsDB(client);

// Page 1
DocumentList page1 = await vectorsDB.ListDocuments(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    queries: new List<string> {
        Query.Limit(25),
        Query.Offset(0)
    }
);

// Page 2
DocumentList page2 = await vectorsDB.ListDocuments(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    queries: new List<string> {
        Query.Limit(25),
        Query.Offset(25)
    }
);
```
```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

VectorsDB vectorsDB = VectorsDB(client);

// Page 1
DocumentList page1 = await vectorsDB.listDocuments(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    queries: [
        Query.limit(25),
        Query.offset(0)
    ],
);

// Page 2
DocumentList page2 = await vectorsDB.listDocuments(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    queries: [
        Query.limit(25),
        Query.offset(25)
    ],
);
```
```kotlin
import io.appwrite.Client
import io.appwrite.Query
import io.appwrite.services.VectorsDB

val client = Client(context)
    .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 vectorsDB = VectorsDB(client)

// Page 1
val page1 = vectorsDB.listDocuments(
    databaseId = "<DATABASE_ID>",
    collectionId = "<COLLECTION_ID>",
    queries = listOf(
        Query.limit(25),
        Query.offset(0)
    ),
)

// Page 2
val page2 = vectorsDB.listDocuments(
    databaseId = "<DATABASE_ID>",
    collectionId = "<COLLECTION_ID>",
    queries = listOf(
        Query.limit(25),
        Query.offset(25)
    ),
)
```
```java
import io.appwrite.Client;
import io.appwrite.Query;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.VectorsDB;
import java.util.List;

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

VectorsDB vectorsDB = new VectorsDB(client);

vectorsDB.listDocuments(
    "<DATABASE_ID>",
    "<COLLECTION_ID>",
    List.of(
        Query.limit(25),
        Query.offset(0)
    ),
    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("<YOUR_PROJECT_ID>") // Your project ID
    .setKey("<YOUR_API_KEY>") // Your secret API key

let vectorsDB = VectorsDB(client)

// Page 1
let page1 = try await vectorsDB.listDocuments(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    queries: [
        Query.limit(25),
        Query.offset(0)
    ]
)

// Page 2
let page2 = try await vectorsDB.listDocuments(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    queries: [
        Query.limit(25),
        Query.offset(25)
    ]
)
```
```server-rust
use appwrite::Client;
use appwrite::services::VectorsDB;
use appwrite::query::Query;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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

    let vectors_db = VectorsDB::new(&client);

    // Page 1
    let page1 = vectors_db.list_documents(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
        Some(vec![
            Query::limit(25).to_string(),
            Query::offset(0).to_string(),
        ]),
        None, // transactionId (optional)
        None, // total (optional)
        None, // ttl (optional)
    ).await?;

    // Page 2
    let page2 = vectors_db.list_documents(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
        Some(vec![
            Query::limit(25).to_string(),
            Query::offset(25).to_string(),
        ]),
        None, // transactionId (optional)
        None, // total (optional)
        None, // ttl (optional)
    ).await?;

    Ok(())
}
```
```bash
appwrite vectorsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"limit","values":[25]}' \
    --queries '{"method":"offset","values":[25]}'
```

**Drawbacks**

While traditional offset pagination is familiar, it comes with some drawbacks.
The request gets slower as the offset increases because the database has to skip over all the preceding documents before it can start selecting data.
If the data changes frequently, offset pagination will also produce **missing and duplicate** results.

### 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.

```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 vectorsDB = new sdk.VectorsDB(client);

// Page 1
const page1 = await vectorsDB.listDocuments({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    queries: [
        sdk.Query.limit(25)
    ]
});

const lastId = page1.documents[page1.documents.length - 1].$id;

// Page 2
const page2 = await vectorsDB.listDocuments({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    queries: [
        sdk.Query.limit(25),
        sdk.Query.cursorAfter(lastId)
    ]
});
```
```deno
import * as sdk from "npm: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 vectorsDB = new sdk.VectorsDB(client);

// Page 1
const page1 = await vectorsDB.listDocuments({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    queries: [
        sdk.Query.limit(25)
    ]
});

const lastId = page1.documents[page1.documents.length - 1].$id;

// Page 2
const page2 = await vectorsDB.listDocuments({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    queries: [
        sdk.Query.limit(25),
        sdk.Query.cursorAfter(lastId)
    ]
});
```
```php
<?php

use Appwrite\Client;
use Appwrite\Services\VectorsDB;
use Appwrite\Query;

$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

$vectorsDB = new VectorsDB($client);

// Page 1
$page1 = $vectorsDB->listDocuments(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    queries: [
        Query::limit(25)
    ]
);

$lastId = $page1['documents'][count($page1['documents']) - 1]['$id'];

// Page 2
$page2 = $vectorsDB->listDocuments(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    queries: [
        Query::limit(25),
        Query::cursorAfter($lastId)
    ]
);
```
```python
from appwrite.client import Client
from appwrite.services.vectors_db import VectorsDB
from appwrite.query import Query

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

vectors_db = VectorsDB(client)

### Page 1
page1 = vectors_db.list_documents(
    database_id = '<DATABASE_ID>',
    collection_id = '<COLLECTION_ID>',
    queries = [
        Query.limit(25)
    ]
)

last_id = page1['documents'][-1]['$id']

### Page 2
page2 = vectors_db.list_documents(
    database_id = '<DATABASE_ID>',
    collection_id = '<COLLECTION_ID>',
    queries = [
        Query.limit(25),
        Query.cursor_after(last_id)
    ]
)
```
```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

vectors_db = VectorsDB.new(client)

### Page 1
page1 = vectors_db.list_documents(
    database_id: '<DATABASE_ID>',
    collection_id: '<COLLECTION_ID>',
    queries: [
        Query.limit(25)
    ]
)

last_id = page1.documents.last.id

### Page 2
page2 = vectors_db.list_documents(
    database_id: '<DATABASE_ID>',
    collection_id: '<COLLECTION_ID>',
    queries: [
        Query.limit(25),
        Query.cursor_after(last_id)
    ]
)
```
```csharp
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

VectorsDB vectorsDB = new VectorsDB(client);

// Page 1
DocumentList page1 = await vectorsDB.ListDocuments(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    queries: new List<string> {
        Query.Limit(25)
    }
);

string lastId = page1.Documents.Last().Id;

// Page 2
DocumentList page2 = await vectorsDB.ListDocuments(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    queries: new List<string> {
        Query.Limit(25),
        Query.CursorAfter(lastId)
    }
);
```
```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

VectorsDB vectorsDB = VectorsDB(client);

// Page 1
DocumentList page1 = await vectorsDB.listDocuments(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    queries: [
        Query.limit(25)
    ],
);

final lastId = page1.documents.last.$id;

// Page 2
DocumentList page2 = await vectorsDB.listDocuments(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    queries: [
        Query.limit(25),
        Query.cursorAfter(lastId)
    ],
);
```
```kotlin
import io.appwrite.Client
import io.appwrite.Query
import io.appwrite.services.VectorsDB

val client = Client(context)
    .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 vectorsDB = VectorsDB(client)

// Page 1
val page1 = vectorsDB.listDocuments(
    databaseId = "<DATABASE_ID>",
    collectionId = "<COLLECTION_ID>",
    queries = listOf(
        Query.limit(25)
    ),
)

val lastId = page1.documents.last().id

// Page 2
val page2 = vectorsDB.listDocuments(
    databaseId = "<DATABASE_ID>",
    collectionId = "<COLLECTION_ID>",
    queries = listOf(
        Query.limit(25),
        Query.cursorAfter(lastId)
    ),
)
```
```java
import io.appwrite.Client;
import io.appwrite.Query;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.VectorsDB;
import java.util.List;

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

VectorsDB vectorsDB = new VectorsDB(client);

vectorsDB.listDocuments(
    "<DATABASE_ID>",
    "<COLLECTION_ID>",
    List.of(
        Query.limit(25),
        Query.cursorAfter("<LAST_DOCUMENT_ID>")
    ),
    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("<YOUR_PROJECT_ID>") // Your project ID
    .setKey("<YOUR_API_KEY>") // Your secret API key

let vectorsDB = VectorsDB(client)

// Page 1
let page1 = try await vectorsDB.listDocuments(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    queries: [
        Query.limit(25)
    ]
)

let lastId = page1.documents.last!.id

// Page 2
let page2 = try await vectorsDB.listDocuments(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    queries: [
        Query.limit(25),
        Query.cursorAfter(lastId)
    ]
)
```
```server-rust
use appwrite::Client;
use appwrite::services::VectorsDB;
use appwrite::query::Query;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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

    let vectors_db = VectorsDB::new(&client);

    // Page 1
    let page1 = vectors_db.list_documents(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
        Some(vec![Query::limit(25).to_string()]),
        None, // transactionId (optional)
        None, // total (optional)
        None, // ttl (optional)
    ).await?;

    let last_id = page1.documents.last().unwrap().id.clone();

    // Page 2
    let page2 = vectors_db.list_documents(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
        Some(vec![
            Query::limit(25).to_string(),
            Query::cursor_after(last_id).to_string(),
        ]),
        None, // transactionId (optional)
        None, // total (optional)
        None, // ttl (optional)
    ).await?;

    Ok(())
}
```
```bash
appwrite vectorsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"limit","values":[25]}' \
    --queries '{"method":"cursorAfter","values":["<LAST_DOCUMENT_ID>"]}'
```

### When to use what?
Offset pagination should be used for collections that rarely change.
Offset pagination lets you build an indicator of the current page number and the total page count.
For example, a list with up to 20 pages or static data like a list of countries or currencies.
Using offset pagination on large 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.

### Cache list responses

You can cache list responses by passing a `ttl` (time-to-live) value in seconds. Subsequent identical requests return the cached result until the TTL expires. The cache is permission-aware, so users with different roles never see each other's cached data.

Set `ttl` between `1` and `86400` (24 hours). The default is `0` (caching disabled). The response includes an `X-Appwrite-Cache` header with value `hit` or `miss`.

```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 vectorsDB = new sdk.VectorsDB(client);

const page = await vectorsDB.listDocuments({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    queries: [
        sdk.Query.limit(25)
    ],
    ttl: 60 // Cache for 60 seconds
});
```
```deno
import * as sdk from "npm: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 vectorsDB = new sdk.VectorsDB(client);

const page = await vectorsDB.listDocuments({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    queries: [
        sdk.Query.limit(25)
    ],
    ttl: 60 // Cache for 60 seconds
});
```
```php
<?php

use Appwrite\Client;
use Appwrite\Services\VectorsDB;
use Appwrite\Query;

$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

$vectorsDB = new VectorsDB($client);

$page = $vectorsDB->listDocuments(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    queries: [
        Query::limit(25)
    ],
    ttl: 60 // Cache for 60 seconds
);
```
```python
from appwrite.client import Client
from appwrite.services.vectors_db import VectorsDB
from appwrite.query import Query

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

vectors_db = VectorsDB(client)

page = vectors_db.list_documents(
    database_id = '<DATABASE_ID>',
    collection_id = '<COLLECTION_ID>',
    queries = [
        Query.limit(25)
    ],
    ttl = 60 # Cache for 60 seconds
)
```
```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

vectors_db = VectorsDB.new(client)

page = vectors_db.list_documents(
    database_id: '<DATABASE_ID>',
    collection_id: '<COLLECTION_ID>',
    queries: [
        Query.limit(25)
    ],
    ttl: 60 # Cache for 60 seconds
)
```
```csharp
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

VectorsDB vectorsDB = new VectorsDB(client);

DocumentList page = await vectorsDB.ListDocuments(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    queries: new List<string> {
        Query.Limit(25)
    },
    ttl: 60 // Cache for 60 seconds
);
```
```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

VectorsDB vectorsDB = VectorsDB(client);

DocumentList page = await vectorsDB.listDocuments(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    queries: [
        Query.limit(25)
    ],
    ttl: 60, // Cache for 60 seconds
);
```
```kotlin
import io.appwrite.Client
import io.appwrite.Query
import io.appwrite.services.VectorsDB

val client = Client(context)
    .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 vectorsDB = VectorsDB(client)

val page = vectorsDB.listDocuments(
    databaseId = "<DATABASE_ID>",
    collectionId = "<COLLECTION_ID>",
    queries = listOf(
        Query.limit(25)
    ),
    ttl = 60 // Cache for 60 seconds
)
```
```java
import io.appwrite.Client;
import io.appwrite.Query;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.VectorsDB;
import java.util.List;

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

VectorsDB vectorsDB = new VectorsDB(client);

vectorsDB.listDocuments(
    "<DATABASE_ID>",
    "<COLLECTION_ID>",
    List.of(
        Query.limit(25)
    ),
    null, // transactionId
    null, // total
    60, // ttl - Cache for 60 seconds
    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("<YOUR_PROJECT_ID>") // Your project ID
    .setKey("<YOUR_API_KEY>") // Your secret API key

let vectorsDB = VectorsDB(client)

let page = try await vectorsDB.listDocuments(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    queries: [
        Query.limit(25)
    ],
    ttl: 60 // Cache for 60 seconds
)
```
```server-rust
use appwrite::Client;
use appwrite::services::VectorsDB;
use appwrite::query::Query;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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

    let vectors_db = VectorsDB::new(&client);

    let page = vectors_db.list_documents(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
        Some(vec![Query::limit(25).to_string()]),
        None,        // transactionId (optional)
        None,        // total (optional)
        Some(60),    // ttl - Cache for 60 seconds
    ).await?;

    println!("{:?}", page);
    Ok(())
}
```
```bash
appwrite vectorsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"limit","values":[25]}' \
    --ttl 60
```

Document writes do **not** invalidate the cache, so cached responses may contain stale data until the TTL expires. Use a short TTL for collections that change often, or skip caching entirely when you always need the latest documents.

---

## Database permissions

URL: https://appwrite.io/docs/products/databases/vectorsdb/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 receives 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.

Permissions are evaluated when documents are accessed through a [Client SDK](/docs/sdks#client). [Server SDKs](/docs/sdks#server) authenticated with an [API key](/docs/advanced/platform/api-keys) bypass permissions, so the examples below use a Server SDK to set and read back the permissions that a client would then be evaluated against.

### 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** > **Security** > **Permissions**, or pass a `permissions` array when you create or update the collection.

![Collection permissions in the Security tab](/images/docs/products/databases/vectorsdb/security-permissions.avif)

```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 vectorsDB = new sdk.VectorsDB(client);

const result = await vectorsDB.createCollection({
    databaseId: '<DATABASE_ID>',
    collectionId: sdk.ID.unique(),
    name: 'documents',
    dimension: 4,
    permissions: [
        sdk.Permission.read(sdk.Role.any()),
        sdk.Permission.create(sdk.Role.users()),
        sdk.Permission.update(sdk.Role.users()),
        sdk.Permission.delete(sdk.Role.users())
    ],
    documentSecurity: true // 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('<YOUR_PROJECT_ID>') // Your project ID
    .setKey('<YOUR_API_KEY>'); // Your secret API key

const vectorsDB = new sdk.VectorsDB(client);

const result = await vectorsDB.createCollection({
    databaseId: '<DATABASE_ID>',
    collectionId: sdk.ID.unique(),
    name: 'documents',
    dimension: 4,
    permissions: [
        sdk.Permission.read(sdk.Role.any()),
        sdk.Permission.create(sdk.Role.users()),
        sdk.Permission.update(sdk.Role.users()),
        sdk.Permission.delete(sdk.Role.users())
    ],
    documentSecurity: true // optional
});
```
```php
<?php

use Appwrite\Client;
use Appwrite\Services\VectorsDB;
use Appwrite\ID;
use Appwrite\Permission;
use Appwrite\Role;

$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

$vectorsDB = new VectorsDB($client);

$result = $vectorsDB->createCollection(
    databaseId: '<DATABASE_ID>',
    collectionId: ID::unique(),
    name: 'documents',
    dimension: 4,
    permissions: [
        Permission::read(Role::any()),
        Permission::create(Role::users()),
        Permission::update(Role::users()),
        Permission::delete(Role::users())
    ],
    documentSecurity: true // optional
);
```
```python
from appwrite.client import Client
from appwrite.services.vectors_db import VectorsDB
from appwrite.id import ID
from appwrite.permission import Permission
from appwrite.role import Role

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

vectors_db = VectorsDB(client)

result = vectors_db.create_collection(
    database_id = '<DATABASE_ID>',
    collection_id = ID.unique(),
    name = 'documents',
    dimension = 4,
    permissions = [
        Permission.read(Role.any()),
        Permission.create(Role.users()),
        Permission.update(Role.users()),
        Permission.delete(Role.users())
    ],
    document_security = True # optional
)
```
```ruby
require 'appwrite'

include Appwrite
include Appwrite::Permission
include Appwrite::Role

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

vectors_db = VectorsDB.new(client)

result = vectors_db.create_collection(
    database_id: '<DATABASE_ID>',
    collection_id: ID.unique(),
    name: 'documents',
    dimension: 4,
    permissions: [
        Permission.read(Role.any()),
        Permission.create(Role.users()),
        Permission.update(Role.users()),
        Permission.delete(Role.users())
    ],
    document_security: true # optional
)
```
```csharp
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

VectorsDB vectorsDB = new VectorsDB(client);

Collection result = await vectorsDB.CreateCollection(
    databaseId: "<DATABASE_ID>",
    collectionId: ID.Unique(),
    name: "documents",
    dimension: 4,
    permissions: new List<string> {
        Permission.Read(Role.Any()),
        Permission.Create(Role.Users()),
        Permission.Update(Role.Users()),
        Permission.Delete(Role.Users())
    },
    documentSecurity: true // optional
);
```
```dart
import 'package:dart_appwrite/dart_appwrite.dart';
import 'package:dart_appwrite/permission.dart';
import 'package:dart_appwrite/role.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

VectorsDB vectorsDB = VectorsDB(client);

Collection result = await vectorsDB.createCollection(
    databaseId: '<DATABASE_ID>',
    collectionId: ID.unique(),
    name: 'documents',
    dimension: 4,
    permissions: [
        Permission.read(Role.any()),
        Permission.create(Role.users()),
        Permission.update(Role.users()),
        Permission.delete(Role.users())
    ], // (optional)
    documentSecurity: true, // (optional)
);
```
```kotlin
import io.appwrite.Client
import io.appwrite.ID
import io.appwrite.Permission
import io.appwrite.Role
import io.appwrite.services.VectorsDB

val client = Client(context)
    .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 vectorsDB = VectorsDB(client)

val response = vectorsDB.createCollection(
    databaseId = "<DATABASE_ID>",
    collectionId = ID.unique(),
    name = "documents",
    dimension = 4,
    permissions = listOf(
        Permission.read(Role.any()),
        Permission.create(Role.users()),
        Permission.update(Role.users()),
        Permission.delete(Role.users())
    ), // optional
    documentSecurity = true, // optional
)
```
```java
import io.appwrite.Client;
import io.appwrite.ID;
import io.appwrite.Permission;
import io.appwrite.Role;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.VectorsDB;
import java.util.List;

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

VectorsDB vectorsDB = new VectorsDB(client);

vectorsDB.createCollection(
    "<DATABASE_ID>",
    ID.unique(),
    "documents",
    4,
    List.of(
        Permission.read(Role.any()),
        Permission.create(Role.users()),
        Permission.update(Role.users()),
        Permission.delete(Role.users())
    ),
    true,
    true,
    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("<YOUR_PROJECT_ID>") // Your project ID
    .setKey("<YOUR_API_KEY>") // Your secret API key

let vectorsDB = VectorsDB(client)

let collection = try await vectorsDB.createCollection(
    databaseId: "<DATABASE_ID>",
    collectionId: ID.unique(),
    name: "documents",
    dimension: 4,
    permissions: [
        Permission.read(Role.any()),
        Permission.create(Role.users()),
        Permission.update(Role.users()),
        Permission.delete(Role.users())
    ],
    documentSecurity: true // optional
)
```
```server-rust
use appwrite::Client;
use appwrite::services::VectorsDB;
use appwrite::id::ID;
use appwrite::permission::Permission;
use appwrite::role::Role;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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

    let vectors_db = VectorsDB::new(&client);

    let result = vectors_db.create_collection(
        "<DATABASE_ID>",
        ID::unique(),
        "documents",
        4,
        Some(vec![
            Permission::read(Role::any()).to_string(),
            Permission::create(Role::users(None)).to_string(),
            Permission::update(Role::users(None)).to_string(),
            Permission::delete(Role::users(None)).to_string(),
        ]),
        Some(true), // documentSecurity (optional)
        None, // enabled (optional)
    ).await?;

    println!("{:?}", result);
    Ok(())
}
```
```bash
appwrite vectorsdb create-collection \
    --database-id "<DATABASE_ID>" \
    --collection-id "<COLLECTION_ID>" \
    --name "documents" \
    --dimension 4 \
    --permissions 'read("any")' \
    --permissions 'create("users")' \
    --permissions 'update("users")' \
    --permissions 'delete("users")' \
    --document-security=true
```

To change a collection's permissions later, pass a new `permissions` array to `updateCollection`. The `name` is required when updating.

```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 vectorsDB = new sdk.VectorsDB(client);

const result = await vectorsDB.updateCollection({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    name: 'documents',
    permissions: [
        sdk.Permission.read(sdk.Role.any()),
        sdk.Permission.create(sdk.Role.users())
    ],
    documentSecurity: false // 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('<YOUR_PROJECT_ID>') // Your project ID
    .setKey('<YOUR_API_KEY>'); // Your secret API key

const vectorsDB = new sdk.VectorsDB(client);

const result = await vectorsDB.updateCollection({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    name: 'documents',
    permissions: [
        sdk.Permission.read(sdk.Role.any()),
        sdk.Permission.create(sdk.Role.users())
    ],
    documentSecurity: false // optional
});
```
```php
<?php

use Appwrite\Client;
use Appwrite\Services\VectorsDB;
use Appwrite\Permission;
use Appwrite\Role;

$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

$vectorsDB = new VectorsDB($client);

$result = $vectorsDB->updateCollection(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    name: 'documents',
    permissions: [
        Permission::read(Role::any()),
        Permission::create(Role::users())
    ],
    documentSecurity: false // optional
);
```
```python
from appwrite.client import Client
from appwrite.services.vectors_db import VectorsDB
from appwrite.permission import Permission
from appwrite.role import Role

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

vectors_db = VectorsDB(client)

result = vectors_db.update_collection(
    database_id = '<DATABASE_ID>',
    collection_id = '<COLLECTION_ID>',
    name = 'documents',
    permissions = [
        Permission.read(Role.any()),
        Permission.create(Role.users())
    ],
    document_security = False # optional
)
```
```ruby
require 'appwrite'

include Appwrite
include Appwrite::Permission
include Appwrite::Role

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

vectors_db = VectorsDB.new(client)

result = vectors_db.update_collection(
    database_id: '<DATABASE_ID>',
    collection_id: '<COLLECTION_ID>',
    name: 'documents',
    permissions: [
        Permission.read(Role.any()),
        Permission.create(Role.users())
    ],
    document_security: false # optional
)
```
```csharp
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

VectorsDB vectorsDB = new VectorsDB(client);

Collection result = await vectorsDB.UpdateCollection(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    name: "documents",
    permissions: new List<string> {
        Permission.Read(Role.Any()),
        Permission.Create(Role.Users())
    },
    documentSecurity: false // optional
);
```
```dart
import 'package:dart_appwrite/dart_appwrite.dart';
import 'package:dart_appwrite/permission.dart';
import 'package:dart_appwrite/role.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

VectorsDB vectorsDB = VectorsDB(client);

Collection result = await vectorsDB.updateCollection(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    name: 'documents',
    permissions: [
        Permission.read(Role.any()),
        Permission.create(Role.users())
    ], // (optional)
    documentSecurity: false, // (optional)
);
```
```kotlin
import io.appwrite.Client
import io.appwrite.Permission
import io.appwrite.Role
import io.appwrite.services.VectorsDB

val client = Client(context)
    .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 vectorsDB = VectorsDB(client)

val response = vectorsDB.updateCollection(
    databaseId = "<DATABASE_ID>",
    collectionId = "<COLLECTION_ID>",
    name = "documents",
    permissions = listOf(
        Permission.read(Role.any()),
        Permission.create(Role.users())
    ), // optional
    documentSecurity = false, // optional
)
```
```java
import io.appwrite.Client;
import io.appwrite.Permission;
import io.appwrite.Role;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.VectorsDB;
import java.util.List;

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

VectorsDB vectorsDB = new VectorsDB(client);

vectorsDB.updateCollection(
    "<DATABASE_ID>",
    "<COLLECTION_ID>",
    "documents",
    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("<YOUR_PROJECT_ID>") // Your project ID
    .setKey("<YOUR_API_KEY>") // Your secret API key

let vectorsDB = VectorsDB(client)

let collection = try await vectorsDB.updateCollection(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    name: "documents",
    permissions: [
        Permission.read(Role.any()),
        Permission.create(Role.users())
    ],
    documentSecurity: false // optional
)
```
```server-rust
use appwrite::Client;
use appwrite::services::VectorsDB;
use appwrite::permission::Permission;
use appwrite::role::Role;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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

    let vectors_db = VectorsDB::new(&client);

    let result = vectors_db.update_collection(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
        "documents",
        None, // dimension (optional)
        Some(vec![
            Permission::read(Role::any()).to_string(),
            Permission::create(Role::users(None)).to_string(),
        ]),
        Some(false), // documentSecurity (optional)
        None, // enabled (optional)
    ).await?;

    println!("{:?}", result);
    Ok(())
}
```
```bash
appwrite vectorsdb update-collection \
    --database-id "<DATABASE_ID>" \
    --collection-id "<COLLECTION_ID>" \
    --name "documents" \
    --permissions 'read("any")' \
    --permissions 'create("users")' \
    --document-security=false
```

[Learn more about permissions and roles](/docs/advanced/platform/permissions)

### Document level
Document level permissions grant access to individual documents.
If a user has read, update, or delete permissions at the document level, the user can access the **individual document**.

Document level permissions are only applied if `documentSecurity` is enabled on the collection. Enable it in the Console by navigating to **Your collection** > **Security** > **Document security**, or by setting `documentSecurity` to `true` when you create or update the collection, as shown above.

![Row level security toggle in the Security tab](/images/docs/products/databases/vectorsdb/security-rls.avif)

Set permissions on an individual document by passing a `permissions` array to `createDocument`. Use `Role.user('<USER_ID>')` to scope access to a specific user.

```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 vectorsDB = new sdk.VectorsDB(client);

const result = await vectorsDB.createDocument({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: sdk.ID.unique(),
    data: {
        embeddings: [0.12, 0.84, 0.33, 0.57],
        metadata: { title: 'Hamlet' }
    },
    permissions: [
        sdk.Permission.read(sdk.Role.any()),
        sdk.Permission.update(sdk.Role.user('<USER_ID>')),
        sdk.Permission.delete(sdk.Role.user('<USER_ID>'))
    ]
});
```
```deno
import * as sdk from "npm: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 vectorsDB = new sdk.VectorsDB(client);

const result = await vectorsDB.createDocument({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: sdk.ID.unique(),
    data: {
        embeddings: [0.12, 0.84, 0.33, 0.57],
        metadata: { title: 'Hamlet' }
    },
    permissions: [
        sdk.Permission.read(sdk.Role.any()),
        sdk.Permission.update(sdk.Role.user('<USER_ID>')),
        sdk.Permission.delete(sdk.Role.user('<USER_ID>'))
    ]
});
```
```php
<?php

use Appwrite\Client;
use Appwrite\Services\VectorsDB;
use Appwrite\ID;
use Appwrite\Permission;
use Appwrite\Role;

$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

$vectorsDB = new VectorsDB($client);

$result = $vectorsDB->createDocument(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: ID::unique(),
    data: [
        'embeddings' => [0.12, 0.84, 0.33, 0.57],
        'metadata' => ['title' => 'Hamlet']
    ],
    permissions: [
        Permission::read(Role::any()),
        Permission::update(Role::user('<USER_ID>')),
        Permission::delete(Role::user('<USER_ID>'))
    ]
);
```
```python
from appwrite.client import Client
from appwrite.services.vectors_db import VectorsDB
from appwrite.id import ID
from appwrite.permission import Permission
from appwrite.role import Role

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

vectors_db = VectorsDB(client)

result = vectors_db.create_document(
    database_id = '<DATABASE_ID>',
    collection_id = '<COLLECTION_ID>',
    document_id = ID.unique(),
    data = {
        "embeddings": [0.12, 0.84, 0.33, 0.57],
        "metadata": { "title": "Hamlet" }
    },
    permissions = [
        Permission.read(Role.any()),
        Permission.update(Role.user('<USER_ID>')),
        Permission.delete(Role.user('<USER_ID>'))
    ]
)
```
```ruby
require 'appwrite'

include Appwrite
include Appwrite::Permission
include Appwrite::Role

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

vectors_db = VectorsDB.new(client)

result = vectors_db.create_document(
    database_id: '<DATABASE_ID>',
    collection_id: '<COLLECTION_ID>',
    document_id: ID.unique(),
    data: {
        "embeddings" => [0.12, 0.84, 0.33, 0.57],
        "metadata" => { "title" => "Hamlet" }
    },
    permissions: [
        Permission.read(Role.any()),
        Permission.update(Role.user('<USER_ID>')),
        Permission.delete(Role.user('<USER_ID>'))
    ]
)
```
```csharp
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

VectorsDB vectorsDB = new VectorsDB(client);

Document result = await vectorsDB.CreateDocument(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    documentId: ID.Unique(),
    data: new Dictionary<string, object> {
        { "embeddings", new List<double> { 0.12, 0.84, 0.33, 0.57 } },
        { "metadata", new Dictionary<string, object> { { "title", "Hamlet" } } }
    },
    permissions: new List<string> {
        Permission.Read(Role.Any()),
        Permission.Update(Role.User("<USER_ID>")),
        Permission.Delete(Role.User("<USER_ID>"))
    }
);
```
```dart
import 'package:dart_appwrite/dart_appwrite.dart';
import 'package:dart_appwrite/permission.dart';
import 'package:dart_appwrite/role.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

VectorsDB vectorsDB = VectorsDB(client);

Document result = await vectorsDB.createDocument(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: ID.unique(),
    data: {
        "embeddings": [0.12, 0.84, 0.33, 0.57],
        "metadata": { "title": "Hamlet" }
    },
    permissions: [
        Permission.read(Role.any()),
        Permission.update(Role.user('<USER_ID>')),
        Permission.delete(Role.user('<USER_ID>'))
    ], // (optional)
);
```
```kotlin
import io.appwrite.Client
import io.appwrite.ID
import io.appwrite.Permission
import io.appwrite.Role
import io.appwrite.services.VectorsDB

val client = Client(context)
    .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 vectorsDB = VectorsDB(client)

val response = vectorsDB.createDocument(
    databaseId = "<DATABASE_ID>",
    collectionId = "<COLLECTION_ID>",
    documentId = ID.unique(),
    data = mapOf(
        "embeddings" to listOf(0.12, 0.84, 0.33, 0.57),
        "metadata" to mapOf("title" to "Hamlet")
    ),
    permissions = listOf(
        Permission.read(Role.any()),
        Permission.update(Role.user("<USER_ID>")),
        Permission.delete(Role.user("<USER_ID>"))
    ), // optional
)
```
```java
import io.appwrite.Client;
import io.appwrite.ID;
import io.appwrite.Permission;
import io.appwrite.Role;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.VectorsDB;
import java.util.List;
import java.util.Map;

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

VectorsDB vectorsDB = new VectorsDB(client);

vectorsDB.createDocument(
    "<DATABASE_ID>",
    "<COLLECTION_ID>",
    ID.unique(),
    Map.of(
        "embeddings", List.of(0.12, 0.84, 0.33, 0.57),
        "metadata", Map.of("title", "Hamlet")
    ),
    List.of(
        Permission.read(Role.any()),
        Permission.update(Role.user("<USER_ID>")),
        Permission.delete(Role.user("<USER_ID>"))
    ),
    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("<YOUR_PROJECT_ID>") // Your project ID
    .setKey("<YOUR_API_KEY>") // Your secret API key

let vectorsDB = VectorsDB(client)

let document = try await vectorsDB.createDocument(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    documentId: ID.unique(),
    data: [
        "embeddings": [0.12, 0.84, 0.33, 0.57],
        "metadata": ["title": "Hamlet"]
    ],
    permissions: [
        Permission.read(Role.any()),
        Permission.update(Role.user("<USER_ID>")),
        Permission.delete(Role.user("<USER_ID>"))
    ]
)
```
```server-rust
use appwrite::Client;
use appwrite::services::VectorsDB;
use appwrite::id::ID;
use appwrite::permission::Permission;
use appwrite::role::Role;
use serde_json::json;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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

    let vectors_db = VectorsDB::new(&client);

    let result = vectors_db.create_document(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
        ID::unique(),
        json!({
            "embeddings": [0.12, 0.84, 0.33, 0.57],
            "metadata": { "title": "Hamlet" }
        }),
        Some(vec![
            Permission::read(Role::any()).to_string(),
            Permission::update(Role::user("<USER_ID>", None)).to_string(),
            Permission::delete(Role::user("<USER_ID>", None)).to_string(),
        ]),
    ).await?;

    println!("{:?}", result);
    Ok(())
}
```
```bash
appwrite vectorsdb create-document \
    --database-id "<DATABASE_ID>" \
    --collection-id "<COLLECTION_ID>" \
    --document-id 'unique()' \
    --data '{ "embeddings": [0.12, 0.84, 0.33, 0.57], "metadata": { "title": "Hamlet" } }' \
    --permissions 'read("any")' \
    --permissions 'update("user:<USER_ID>")' \
    --permissions 'delete("user:<USER_ID>")'
```

To change a document's permissions later, pass a new `permissions` array to `updateDocument`. Only the fields you pass are changed, so you can update permissions without touching the vector or metadata.

```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 vectorsDB = new sdk.VectorsDB(client);

const result = await vectorsDB.updateDocument({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>',
    permissions: [
        sdk.Permission.read(sdk.Role.users())
    ]
});
```
```deno
import * as sdk from "npm: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 vectorsDB = new sdk.VectorsDB(client);

const result = await vectorsDB.updateDocument({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>',
    permissions: [
        sdk.Permission.read(sdk.Role.users())
    ]
});
```
```php
<?php

use Appwrite\Client;
use Appwrite\Services\VectorsDB;
use Appwrite\Permission;
use Appwrite\Role;

$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

$vectorsDB = new VectorsDB($client);

$result = $vectorsDB->updateDocument(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>',
    permissions: [
        Permission::read(Role::users())
    ]
);
```
```python
from appwrite.client import Client
from appwrite.services.vectors_db import VectorsDB
from appwrite.permission import Permission
from appwrite.role import Role

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

vectors_db = VectorsDB(client)

result = vectors_db.update_document(
    database_id = '<DATABASE_ID>',
    collection_id = '<COLLECTION_ID>',
    document_id = '<DOCUMENT_ID>',
    permissions = [
        Permission.read(Role.users())
    ]
)
```
```ruby
require 'appwrite'

include Appwrite
include Appwrite::Permission
include Appwrite::Role

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

vectors_db = VectorsDB.new(client)

result = vectors_db.update_document(
    database_id: '<DATABASE_ID>',
    collection_id: '<COLLECTION_ID>',
    document_id: '<DOCUMENT_ID>',
    permissions: [
        Permission.read(Role.users())
    ]
)
```
```csharp
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

VectorsDB vectorsDB = new VectorsDB(client);

Document result = await vectorsDB.UpdateDocument(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    documentId: "<DOCUMENT_ID>",
    permissions: new List<string> {
        Permission.Read(Role.Users())
    }
);
```
```dart
import 'package:dart_appwrite/dart_appwrite.dart';
import 'package:dart_appwrite/permission.dart';
import 'package:dart_appwrite/role.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

VectorsDB vectorsDB = VectorsDB(client);

Document result = await vectorsDB.updateDocument(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>',
    permissions: [
        Permission.read(Role.users())
    ], // (optional)
);
```
```kotlin
import io.appwrite.Client
import io.appwrite.Permission
import io.appwrite.Role
import io.appwrite.services.VectorsDB

val client = Client(context)
    .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 vectorsDB = VectorsDB(client)

val response = vectorsDB.updateDocument(
    databaseId = "<DATABASE_ID>",
    collectionId = "<COLLECTION_ID>",
    documentId = "<DOCUMENT_ID>",
    permissions = listOf(
        Permission.read(Role.users())
    ), // optional
)
```
```java
import io.appwrite.Client;
import io.appwrite.Permission;
import io.appwrite.Role;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.VectorsDB;
import java.util.List;

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

VectorsDB vectorsDB = new VectorsDB(client);

vectorsDB.updateDocument(
    "<DATABASE_ID>",
    "<COLLECTION_ID>",
    "<DOCUMENT_ID>",
    null,
    List.of(
        Permission.read(Role.users())
    ),
    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("<YOUR_PROJECT_ID>") // Your project ID
    .setKey("<YOUR_API_KEY>") // Your secret API key

let vectorsDB = VectorsDB(client)

let document = try await vectorsDB.updateDocument(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    documentId: "<DOCUMENT_ID>",
    permissions: [
        Permission.read(Role.users())
    ]
)
```
```server-rust
use appwrite::Client;
use appwrite::services::VectorsDB;
use appwrite::permission::Permission;
use appwrite::role::Role;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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

    let vectors_db = VectorsDB::new(&client);

    let result = vectors_db.update_document(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
        "<DOCUMENT_ID>",
        None, // data (optional)
        Some(vec![
            Permission::read(Role::users(None)).to_string(),
        ]),
        None, // transactionId (optional)
    ).await?;

    println!("{:?}", result);
    Ok(())
}
```
```bash
appwrite vectorsdb update-document \
    --database-id "<DATABASE_ID>" \
    --collection-id "<COLLECTION_ID>" \
    --document-id "<DOCUMENT_ID>" \
    --permissions 'read("users")'
```

[Learn more about permissions and roles](/docs/advanced/platform/permissions)

### 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

URL: https://appwrite.io/docs/products/databases/vectorsdb/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.

In VectorsDB, every document stores an `embeddings` vector and an optional `metadata` object. The queries on this page filter documents by the fields inside that `metadata` object. To rank documents by vector similarity instead, see [vector search](/docs/products/databases/vectorsdb/vector-search).

### 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

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('metadata.genre', ['sci-fi', 'drama'])` will fetch documents whose genre is `sci-fi` or `drama`.

To filter on a field inside the `metadata` object, reference it with dot notation, such as `metadata.genre` or `metadata.year`.

**Filter metadata with string values**

VectorsDB stores `metadata` as a JSON object, so metadata fields are compared as strings. Pass filter values as strings, even when the stored value is a number. For example, use `Query.greaterThan('metadata.year', '2010')`, not `Query.greaterThan('metadata.year', 2010)`. Passing a numeric value returns a `400` error.

**Default pagination behavior**

By default, results are limited to the **first 25 items**.
You can change this through [pagination](/docs/products/databases/vectorsdb/pagination).

A single request accepts up to 100 queries. Because `listDocuments` sends them in the URL, a long list or a wide vector query can push past the URL length limit. `createQuery` accepts the same queries in the request body, where that limit does not apply. See [Send queries in the request body](/docs/products/databases/vectorsdb/vector-search#create-query).

```client-web
import { Client, Query, VectorsDB } from "appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<YOUR_PROJECT_ID>');

const vectorsDB = new VectorsDB(client);

vectorsDB.listDocuments({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    queries: [
        Query.equal('metadata.genre', ['sci-fi', 'drama']),
        Query.greaterThan('metadata.year', '2010')
    ]
});
```
```server-nodejs
const sdk = require('node-appwrite');

const client = new sdk.Client();

const vectorsDB = new sdk.VectorsDB(client);

client
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<YOUR_PROJECT_ID>')
    .setKey('<YOUR_API_KEY>')
;

const promise = vectorsDB.listDocuments({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    queries: [
        sdk.Query.equal('metadata.genre', ['sci-fi', 'drama']),
        sdk.Query.greaterThan('metadata.year', '2010')
    ]
});

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://<REGION>.cloud.appwrite.io/v1')
        .setProject('<YOUR_PROJECT_ID>');

    final vectorsDB = VectorsDB(client);

    try {
        final documents = await vectorsDB.listDocuments(
            '<DATABASE_ID>',
            '<COLLECTION_ID>',
            [
                Query.equal('metadata.genre', ['sci-fi', 'drama']),
                Query.greaterThan('metadata.year', '2010')
            ]
        );
    } on AppwriteException catch(e) {
        print(e);
    }
}
```
```client-apple
import Appwrite
import AppwriteModels

func main() async throws {
    let client = Client()
        .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
        .setProject("<YOUR_PROJECT_ID>")

    let vectorsDB = VectorsDB(client)

    do {
        let documents = try await vectorsDB.listDocuments(
            databaseId: "<DATABASE_ID>",
            collectionId: "<COLLECTION_ID>",
            queries: [
                Query.equal("metadata.genre", value: ["sci-fi", "drama"]),
                Query.greaterThan("metadata.year", value: "2010")
            ]
        )
    } catch {
        print(error.localizedDescription)
    }
}
```
```client-android-kotlin
import io.appwrite.Client
import io.appwrite.Query
import io.appwrite.services.VectorsDB

suspend fun main() {
    val client = Client(applicationContext)
        .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
        .setProject('<YOUR_PROJECT_ID>');

    val vectorsDB = VectorsDB(client)

    try {
        val documents = vectorsDB.listDocuments(
            databaseId = "<DATABASE_ID>",
            collectionId = "<COLLECTION_ID>",
            queries = listOf(
                Query.equal("metadata.genre", listOf("sci-fi", "drama")),
                Query.greaterThan("metadata.year", "2010")
            )
        )
    } 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://<REGION>.cloud.appwrite.io/v1"),
        appwrite.WithProject("<YOUR_PROJECT_ID>"),
        appwrite.WithKey("<YOUR_API_KEY>"),
    )

    vectorsDB := appwrite.NewVectorsDB(client)

    documents, err := vectorsDB.ListDocuments(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
        vectorsDB.WithListDocumentsQueries([]string{
            query.Equal("metadata.genre", []string{"sci-fi", "drama"}),
            query.GreaterThan("metadata.year", "2010"),
        }),
    )
    if err != nil {
        log.Fatal(err)
    }

    fmt.Printf("Documents: %+v\n", documents)
}
```
```server-rust
use appwrite::Client;
use appwrite::services::VectorsDB;
use appwrite::query::Query;
use serde_json::Value;

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

    let vectors_db = VectorsDB::new(&client);

    let documents = vectors_db.list_documents(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
        Some(vec![
            Query::equal("metadata.genre", Value::Array(vec![
                Value::String("sci-fi".to_string()),
                Value::String("drama".to_string()),
            ])).to_string(),
            Query::greater_than("metadata.year", "2010").to_string(),
        ]),
        None,
        None,
        None,
    ).await?;

    println!("{:?}", documents);
    Ok(())
}
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Query;
use Appwrite\Services\VectorsDB;

$client = new Client();

$client
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<YOUR_PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>')
;

$vectorsDB = new VectorsDB($client);

$result = $vectorsDB->listDocuments(
    '<DATABASE_ID>',
    '<COLLECTION_ID>',
    [
        Query::equal('metadata.genre', ['sci-fi', 'drama']),
        Query::greaterThan('metadata.year', '2010')
    ]
);
```
```server-python
from appwrite.client import Client
from appwrite.query import Query
from appwrite.services.vectors_db import VectorsDB

client = Client()

(client
  .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
  .set_project('<YOUR_PROJECT_ID>')
  .set_key('<YOUR_API_KEY>')
)

vectorsDB = VectorsDB(client)

result = vectorsDB.list_documents(
    '<DATABASE_ID>',
    '<COLLECTION_ID>',
    [
        Query.equal('metadata.genre', ['sci-fi', 'drama']),
        Query.greater_than('metadata.year', '2010')
    ]
)
```
```graphql
query {
    vectorsDBListDocuments(
        databaseId: "<DATABASE_ID>",
        collectionId: "<COLLECTION_ID>"
        queries: [
            "{\"method\":\"equal\",\"attribute\":\"metadata.genre\",\"values\":[\"sci-fi\",\"drama\"]}",
            "{\"method\":\"greaterThan\",\"attribute\":\"metadata.year\",\"values\":[\"2010\"]}"
        ]
    ) {
        total
        documents {
            _id
            data
        }
    }
}
```
```http
GET /v1/vectorsdb/<DATABASE_ID>/collections/<COLLECTION_ID>/documents?queries[]=%7B%22method%22%3A%22equal%22%2C%22attribute%22%3A%22metadata.genre%22%2C%22values%22%3A%5B%22sci-fi%22%2C%22drama%22%5D%7D&queries[]=%7B%22method%22%3A%22greaterThan%22%2C%22attribute%22%3A%22metadata.year%22%2C%22values%22%3A%5B%222010%22%5D%7D HTTP/1.1
Content-Type: application/json
X-Appwrite-Project: <YOUR_PROJECT_ID>
```
```bash
appwrite vectorsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"equal","attribute":"metadata.genre","values":["sci-fi","drama"]}' \
    --queries '{"method":"greaterThan","attribute":"metadata.year","values":["2010"]}'
```

### Query operators

#### Select

The `select` operator allows you to specify which fields should be returned from a document. This optimizes response size and retrieves only the data you need. Each VectorsDB document has two data fields, `embeddings` and `metadata`.

```client-web
Query.select(["metadata"])
```
```client-flutter
Query.select(["metadata"])
```
```client-react-native
Query.select(["metadata"])
```
```client-apple
Query.select(["metadata"])
```
```client-android-kotlin
Query.select(listOf("metadata"))
```
```client-android-java
Query.select(Arrays.asList("metadata"))
```
```server-python
Query.select(["metadata"])
```
```server-ruby
Query.select(["metadata"])
```
```server-deno
Query.select(["metadata"])
```
```server-nodejs
Query.select(["metadata"])
```
```server-php
Query::select(["metadata"])
```
```server-dotnet
Query.Select(new List<string> { "metadata" })
```
```server-go
query.Select([]string{"metadata"})
```
```server-dart
Query.select(["metadata"])
```
```server-swift
Query.select(["metadata"])
```
```server-kotlin
Query.select(listOf("metadata"))
```
```server-rust
Query::select(vec!["metadata"]).to_string()
```
```bash
appwrite vectorsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"select","values":["metadata"]}'
```
```http
{"method":"select","values":["metadata"]}
```

##### Use selection patterns

| Pattern | Description | Use case |
|---------|-------------|----------|
| `["metadata"]` | Metadata object only | Return metadata without the embedding vector |
| `["embeddings"]` | Embedding vector only | Return the stored vector |
| `["metadata", "embeddings"]` | Both data fields | Get the complete document data |

##### Optimize performance

**Optimize response size** - Only select the fields you actually need. Embedding vectors are large, so omitting `embeddings` keeps responses small when you only need metadata.

**Reduce database load** - Selecting fewer fields reduces database processing time.

#### Comparison operators

Pass metadata filter values as strings. See [Filter metadata with string values](#building-queries).

##### Equal

Returns document if a metadata field is equal to any value in the provided array.

```client-web
Query.equal("metadata.genre", ["sci-fi"])
```
```client-flutter
Query.equal("metadata.genre", ["sci-fi"])
```
```client-react-native
Query.equal("metadata.genre", ["sci-fi"])
```
```client-apple
Query.equal("metadata.genre", value: ["sci-fi"])
```
```client-android-kotlin
Query.equal("metadata.genre", listOf("sci-fi"))
```
```client-android-java
Query.equal("metadata.genre", Arrays.asList("sci-fi"))
```
```server-python
Query.equal("metadata.genre", ["sci-fi"])
```
```server-ruby
Query.equal("metadata.genre", ["sci-fi"])
```
```server-deno
Query.equal("metadata.genre", ["sci-fi"])
```
```server-nodejs
Query.equal("metadata.genre", ["sci-fi"])
```
```server-php
Query::equal("metadata.genre", ["sci-fi"])
```
```server-dotnet
Query.Equal("metadata.genre", new List<string> { "sci-fi" })
```
```server-go
query.Equal("metadata.genre", []string{"sci-fi"})
```
```server-dart
Query.equal("metadata.genre", ["sci-fi"])
```
```server-swift
Query.equal("metadata.genre", value: ["sci-fi"])
```
```server-kotlin
Query.equal("metadata.genre", listOf("sci-fi"))
```
```server-rust
Query::equal("metadata.genre", Value::Array(vec![Value::String("sci-fi".to_string())])).to_string()
```
```bash
appwrite vectorsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"equal","attribute":"metadata.genre","values":["sci-fi"]}'
```
```http
{"method":"equal","attribute":"metadata.genre","values":["sci-fi"]}
```

##### Not equal

Returns document if a metadata field is not equal to the provided value.

```client-web
Query.notEqual("metadata.genre", "sci-fi")
```
```client-flutter
Query.notEqual("metadata.genre", "sci-fi")
```
```client-react-native
Query.notEqual("metadata.genre", "sci-fi")
```
```client-apple
Query.notEqual("metadata.genre", value: "sci-fi")
```
```client-android-kotlin
Query.notEqual("metadata.genre", "sci-fi")
```
```client-android-java
Query.notEqual("metadata.genre", "sci-fi")
```
```server-python
Query.not_equal("metadata.genre", "sci-fi")
```
```server-ruby
Query.not_equal("metadata.genre", "sci-fi")
```
```server-deno
Query.notEqual("metadata.genre", "sci-fi")
```
```server-nodejs
Query.notEqual("metadata.genre", "sci-fi")
```
```server-php
Query::notEqual("metadata.genre", "sci-fi")
```
```server-dotnet
Query.NotEqual("metadata.genre", "sci-fi")
```
```server-go
query.NotEqual("metadata.genre", "sci-fi")
```
```server-dart
Query.notEqual("metadata.genre", "sci-fi")
```
```server-swift
Query.notEqual("metadata.genre", value: "sci-fi")
```
```server-kotlin
Query.notEqual("metadata.genre", "sci-fi")
```
```server-rust
Query::not_equal("metadata.genre", "sci-fi").to_string()
```
```bash
appwrite vectorsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"notEqual","attribute":"metadata.genre","values":["sci-fi"]}'
```
```http
{"method":"notEqual","attribute":"metadata.genre","values":"sci-fi"}
```

##### Less than

Returns document if a metadata field is less than the provided value.

```client-web
Query.lessThan("metadata.year", "2009")
```
```client-flutter
Query.lessThan("metadata.year", "2009")
```
```client-react-native
Query.lessThan("metadata.year", "2009")
```
```client-apple
Query.lessThan("metadata.year", value: "2009")
```
```client-android-kotlin
Query.lessThan("metadata.year", "2009")
```
```client-android-java
Query.lessThan("metadata.year", "2009")
```
```server-python
Query.less_than("metadata.year", "2009")
```
```server-ruby
Query.less_than("metadata.year", "2009")
```
```server-deno
Query.lessThan("metadata.year", "2009")
```
```server-nodejs
Query.lessThan("metadata.year", "2009")
```
```server-php
Query::lessThan("metadata.year", "2009")
```
```server-dotnet
Query.LessThan("metadata.year", "2009")
```
```server-go
query.LessThan("metadata.year", "2009")
```
```server-dart
Query.lessThan("metadata.year", "2009")
```
```server-swift
Query.lessThan("metadata.year", value: "2009")
```
```server-kotlin
Query.lessThan("metadata.year", "2009")
```
```server-rust
Query::less_than("metadata.year", "2009").to_string()
```
```bash
appwrite vectorsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"lessThan","attribute":"metadata.year","values":["2009"]}'
```
```http
{"method":"lessThan","attribute":"metadata.year","values":["2009"]}
```

##### Less than or equal

Returns document if a metadata field is less than or equal to the provided value.

```client-web
Query.lessThanEqual("metadata.year", "2009")
```
```client-flutter
Query.lessThanEqual("metadata.year", "2009")
```
```client-react-native
Query.lessThanEqual("metadata.year", "2009")
```
```client-apple
Query.lessThanEqual("metadata.year", value: "2009")
```
```client-android-kotlin
Query.lessThanEqual("metadata.year", "2009")
```
```client-android-java
Query.lessThanEqual("metadata.year", "2009")
```
```server-python
Query.less_than_equal("metadata.year", "2009")
```
```server-ruby
Query.less_than_equal("metadata.year", "2009")
```
```server-deno
Query.lessThanEqual("metadata.year", "2009")
```
```server-nodejs
Query.lessThanEqual("metadata.year", "2009")
```
```server-php
Query::lessThanEqual("metadata.year", "2009")
```
```server-dotnet
Query.LessThanEqual("metadata.year", "2009")
```
```server-go
query.LessThanEqual("metadata.year", "2009")
```
```server-dart
Query.lessThanEqual("metadata.year", "2009")
```
```server-swift
Query.lessThanEqual("metadata.year", value: "2009")
```
```server-kotlin
Query.lessThanEqual("metadata.year", "2009")
```
```server-rust
Query::less_than_equal("metadata.year", "2009").to_string()
```
```bash
appwrite vectorsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"lessThanEqual","attribute":"metadata.year","values":["2009"]}'
```
```http
{"method":"lessThanEqual","attribute":"metadata.year","values":["2009"]}
```

##### Greater than

Returns document if a metadata field is greater than the provided value.

```client-web
Query.greaterThan("metadata.year", "2009")
```
```client-flutter
Query.greaterThan("metadata.year", "2009")
```
```client-react-native
Query.greaterThan("metadata.year", "2009")
```
```client-apple
Query.greaterThan("metadata.year", value: "2009")
```
```client-android-kotlin
Query.greaterThan("metadata.year", "2009")
```
```client-android-java
Query.greaterThan("metadata.year", "2009")
```
```server-python
Query.greater_than("metadata.year", "2009")
```
```server-ruby
Query.greater_than("metadata.year", "2009")
```
```server-deno
Query.greaterThan("metadata.year", "2009")
```
```server-nodejs
Query.greaterThan("metadata.year", "2009")
```
```server-php
Query::greaterThan("metadata.year", "2009")
```
```server-dotnet
Query.GreaterThan("metadata.year", "2009")
```
```server-go
query.GreaterThan("metadata.year", "2009")
```
```server-dart
Query.greaterThan("metadata.year", "2009")
```
```server-swift
Query.greaterThan("metadata.year", value: "2009")
```
```server-kotlin
Query.greaterThan("metadata.year", "2009")
```
```server-rust
Query::greater_than("metadata.year", "2009").to_string()
```
```bash
appwrite vectorsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"greaterThan","attribute":"metadata.year","values":["2009"]}'
```
```http
{"method":"greaterThan","attribute":"metadata.year","values":["2009"]}
```

##### Greater than or equal

Returns document if a metadata field is greater than or equal to the provided value.

```client-web
Query.greaterThanEqual("metadata.year", "2009")
```
```client-flutter
Query.greaterThanEqual("metadata.year", "2009")
```
```client-react-native
Query.greaterThanEqual("metadata.year", "2009")
```
```client-apple
Query.greaterThanEqual("metadata.year", value: "2009")
```
```client-android-kotlin
Query.greaterThanEqual("metadata.year", "2009")
```
```client-android-java
Query.greaterThanEqual("metadata.year", "2009")
```
```server-python
Query.greater_than_equal("metadata.year", "2009")
```
```server-ruby
Query.greater_than_equal("metadata.year", "2009")
```
```server-deno
Query.greaterThanEqual("metadata.year", "2009")
```
```server-nodejs
Query.greaterThanEqual("metadata.year", "2009")
```
```server-php
Query::greaterThanEqual("metadata.year", "2009")
```
```server-dotnet
Query.GreaterThanEqual("metadata.year", "2009")
```
```server-go
query.GreaterThanEqual("metadata.year", "2009")
```
```server-dart
Query.greaterThanEqual("metadata.year", "2009")
```
```server-swift
Query.greaterThanEqual("metadata.year", value: "2009")
```
```server-kotlin
Query.greaterThanEqual("metadata.year", "2009")
```
```server-rust
Query::greater_than_equal("metadata.year", "2009").to_string()
```
```bash
appwrite vectorsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"greaterThanEqual","attribute":"metadata.year","values":["2009"]}'
```
```http
{"method":"greaterThanEqual","attribute":"metadata.year","values":["2009"]}
```

##### Between

Returns document if a metadata field value falls between the two values. The boundary values are inclusive.

```client-web
Query.between("metadata.year", "2005", "2010")
```
```client-flutter
Query.between("metadata.year", "2005", "2010")
```
```client-react-native
Query.between("metadata.year", "2005", "2010")
```
```client-apple
Query.between("metadata.year", start: "2005", end: "2010")
```
```client-android-kotlin
Query.between("metadata.year", "2005", "2010")
```
```client-android-java
Query.between("metadata.year", "2005", "2010")
```
```server-python
Query.between("metadata.year", "2005", "2010")
```
```server-ruby
Query.between("metadata.year", "2005", "2010")
```
```server-deno
Query.between("metadata.year", "2005", "2010")
```
```server-nodejs
Query.between("metadata.year", "2005", "2010")
```
```server-php
Query::between("metadata.year", "2005", "2010")
```
```server-dotnet
Query.Between("metadata.year", "2005", "2010")
```
```server-go
query.Between("metadata.year", "2005", "2010")
```
```server-dart
Query.between("metadata.year", "2005", "2010")
```
```server-swift
Query.between("metadata.year", start: "2005", end: "2010")
```
```server-kotlin
Query.between("metadata.year", "2005", "2010")
```
```server-rust
Query::between("metadata.year", "2005", "2010").to_string()
```
```bash
appwrite vectorsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"between","attribute":"metadata.year","values":["2005","2010"]}'
```
```http
{"method":"between","attribute":"metadata.year","values":["2005","2010"]}
```

##### Not between

Returns documents if the metadata field value is outside the range defined by the two values. Boundary values are excluded.

```client-web
Query.notBetween("metadata.year", "2005", "2010")
```
```client-flutter
Query.notBetween("metadata.year", "2005", "2010")
```
```client-react-native
Query.notBetween("metadata.year", "2005", "2010")
```
```client-apple
Query.notBetween("metadata.year", start: "2005", end: "2010")
```
```client-android-kotlin
Query.notBetween("metadata.year", "2005", "2010")
```
```client-android-java
Query.notBetween("metadata.year", "2005", "2010")
```
```server-python
Query.not_between("metadata.year", "2005", "2010")
```
```server-ruby
Query.not_between("metadata.year", "2005", "2010")
```
```server-deno
Query.notBetween("metadata.year", "2005", "2010")
```
```server-nodejs
Query.notBetween("metadata.year", "2005", "2010")
```
```server-php
Query::notBetween("metadata.year", "2005", "2010")
```
```server-dotnet
Query.NotBetween("metadata.year", "2005", "2010")
```
```server-go
query.NotBetween("metadata.year", "2005", "2010")
```
```server-dart
Query.notBetween("metadata.year", "2005", "2010")
```
```server-swift
Query.notBetween("metadata.year", start: "2005", end: "2010")
```
```server-kotlin
Query.notBetween("metadata.year", "2005", "2010")
```
```server-rust
Query::not_between("metadata.year", "2005", "2010").to_string()
```
```bash
appwrite vectorsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"notBetween","attribute":"metadata.year","values":["2005","2010"]}'
```
```http
{"method":"notBetween","attribute":"metadata.year","values":["2005","2010"]}
```

#### Null checks

##### Is null

Returns documents where the metadata field value is null or absent.

```client-web
Query.isNull("metadata.archived")
```
```client-flutter
Query.isNull("metadata.archived")
```
```client-react-native
Query.isNull("metadata.archived")
```
```client-apple
Query.isNull("metadata.archived")
```
```client-android-kotlin
Query.isNull("metadata.archived")
```
```client-android-java
Query.isNull("metadata.archived")
```
```server-python
Query.is_null("metadata.archived")
```
```server-ruby
Query.is_null("metadata.archived")
```
```server-deno
Query.isNull("metadata.archived")
```
```server-nodejs
Query.isNull("metadata.archived")
```
```server-php
Query::isNull("metadata.archived")
```
```server-dotnet
Query.IsNull("metadata.archived")
```
```server-go
query.IsNull("metadata.archived")
```
```server-dart
Query.isNull("metadata.archived")
```
```server-swift
Query.isNull("metadata.archived")
```
```server-kotlin
Query.isNull("metadata.archived")
```
```server-rust
Query::is_null("metadata.archived").to_string()
```
```bash
appwrite vectorsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"isNull","attribute":"metadata.archived"}'
```
```http
{"method":"isNull","attribute":"metadata.archived"}
```

##### Is not null

Returns documents where the metadata field value is **not** null.

```client-web
Query.isNotNull("metadata.archived")
```
```client-flutter
Query.isNotNull("metadata.archived")
```
```client-react-native
Query.isNotNull("metadata.archived")
```
```client-apple
Query.isNotNull("metadata.archived")
```
```client-android-kotlin
Query.isNotNull("metadata.archived")
```
```client-android-java
Query.isNotNull("metadata.archived")
```
```server-python
Query.is_not_null("metadata.archived")
```
```server-ruby
Query.is_not_null("metadata.archived")
```
```server-deno
Query.isNotNull("metadata.archived")
```
```server-nodejs
Query.isNotNull("metadata.archived")
```
```server-php
Query::isNotNull("metadata.archived")
```
```server-dotnet
Query.IsNotNull("metadata.archived")
```
```server-go
query.IsNotNull("metadata.archived")
```
```server-dart
Query.isNotNull("metadata.archived")
```
```server-swift
Query.isNotNull("metadata.archived")
```
```server-kotlin
Query.isNotNull("metadata.archived")
```
```server-rust
Query::is_not_null("metadata.archived").to_string()
```
```bash
appwrite vectorsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"isNotNull","attribute":"metadata.archived"}'
```
```http
{"method":"isNotNull","attribute":"metadata.archived"}
```

#### String operations

##### Starts with

Returns documents if a metadata string field starts with a substring.

```client-web
Query.startsWith("metadata.title", "Iron")
```
```client-flutter
Query.startsWith("metadata.title", "Iron")
```
```client-react-native
Query.startsWith("metadata.title", "Iron")
```
```client-apple
Query.startsWith("metadata.title", value: "Iron")
```
```client-android-kotlin
Query.startsWith("metadata.title", "Iron")
```
```client-android-java
Query.startsWith("metadata.title", "Iron")
```
```server-python
Query.starts_with("metadata.title", "Iron")
```
```server-ruby
Query.starts_with("metadata.title", "Iron")
```
```server-deno
Query.startsWith("metadata.title", "Iron")
```
```server-nodejs
Query.startsWith("metadata.title", "Iron")
```
```server-php
Query::startsWith("metadata.title", "Iron")
```
```server-dotnet
Query.StartsWith("metadata.title", "Iron")
```
```server-go
query.StartsWith("metadata.title", "Iron")
```
```server-dart
Query.startsWith("metadata.title", "Iron")
```
```server-swift
Query.startsWith("metadata.title", value: "Iron")
```
```server-kotlin
Query.startsWith("metadata.title", "Iron")
```
```server-rust
Query::starts_with("metadata.title", "Iron").to_string()
```
```bash
appwrite vectorsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"startsWith","attribute":"metadata.title","values":["Iron"]}'
```
```http
{"method":"startsWith","attribute":"metadata.title","values":["Iron"]}
```

##### Not starts with

Returns documents if a metadata string field does not start with a substring.

```client-web
Query.notStartsWith("metadata.title", "Iron")
```
```client-flutter
Query.notStartsWith("metadata.title", "Iron")
```
```client-react-native
Query.notStartsWith("metadata.title", "Iron")
```
```client-apple
Query.notStartsWith("metadata.title", value: "Iron")
```
```client-android-kotlin
Query.notStartsWith("metadata.title", "Iron")
```
```client-android-java
Query.notStartsWith("metadata.title", "Iron")
```
```server-python
Query.not_starts_with("metadata.title", "Iron")
```
```server-ruby
Query.not_starts_with("metadata.title", "Iron")
```
```server-deno
Query.notStartsWith("metadata.title", "Iron")
```
```server-nodejs
Query.notStartsWith("metadata.title", "Iron")
```
```server-php
Query::notStartsWith("metadata.title", "Iron")
```
```server-dotnet
Query.NotStartsWith("metadata.title", "Iron")
```
```server-go
query.NotStartsWith("metadata.title", "Iron")
```
```server-dart
Query.notStartsWith("metadata.title", "Iron")
```
```server-swift
Query.notStartsWith("metadata.title", value: "Iron")
```
```server-kotlin
Query.notStartsWith("metadata.title", "Iron")
```
```server-rust
Query::not_starts_with("metadata.title", "Iron").to_string()
```
```bash
appwrite vectorsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"notStartsWith","attribute":"metadata.title","values":["Iron"]}'
```
```http
{"method":"notStartsWith","attribute":"metadata.title","values":["Iron"]}
```

##### Ends with

Returns documents if a metadata string field ends with a substring.

```client-web
Query.endsWith("metadata.title", "Man")
```
```client-flutter
Query.endsWith("metadata.title", "Man")
```
```client-react-native
Query.endsWith("metadata.title", "Man")
```
```client-apple
Query.endsWith("metadata.title", value: "Man")
```
```client-android-kotlin
Query.endsWith("metadata.title", "Man")
```
```client-android-java
Query.endsWith("metadata.title", "Man")
```
```server-python
Query.ends_with("metadata.title", "Man")
```
```server-ruby
Query.ends_with("metadata.title", "Man")
```
```server-deno
Query.endsWith("metadata.title", "Man")
```
```server-nodejs
Query.endsWith("metadata.title", "Man")
```
```server-php
Query::endsWith("metadata.title", "Man")
```
```server-dotnet
Query.EndsWith("metadata.title", "Man")
```
```server-go
query.EndsWith("metadata.title", "Man")
```
```server-dart
Query.endsWith("metadata.title", "Man")
```
```server-swift
Query.endsWith("metadata.title", value: "Man")
```
```server-kotlin
Query.endsWith("metadata.title", "Man")
```
```server-rust
Query::ends_with("metadata.title", "Man").to_string()
```
```bash
appwrite vectorsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"endsWith","attribute":"metadata.title","values":["Man"]}'
```
```http
{"method":"endsWith","attribute":"metadata.title","values":["Man"]}
```

##### Not ends with

Returns documents if a metadata string field does not end with a substring.

```client-web
Query.notEndsWith("metadata.title", "Man")
```
```client-flutter
Query.notEndsWith("metadata.title", "Man")
```
```client-react-native
Query.notEndsWith("metadata.title", "Man")
```
```client-apple
Query.notEndsWith("metadata.title", value: "Man")
```
```client-android-kotlin
Query.notEndsWith("metadata.title", "Man")
```
```client-android-java
Query.notEndsWith("metadata.title", "Man")
```
```server-python
Query.not_ends_with("metadata.title", "Man")
```
```server-ruby
Query.not_ends_with("metadata.title", "Man")
```
```server-deno
Query.notEndsWith("metadata.title", "Man")
```
```server-nodejs
Query.notEndsWith("metadata.title", "Man")
```
```server-php
Query::notEndsWith("metadata.title", "Man")
```
```server-dotnet
Query.NotEndsWith("metadata.title", "Man")
```
```server-go
query.NotEndsWith("metadata.title", "Man")
```
```server-dart
Query.notEndsWith("metadata.title", "Man")
```
```server-swift
Query.notEndsWith("metadata.title", value: "Man")
```
```server-kotlin
Query.notEndsWith("metadata.title", "Man")
```
```server-rust
Query::not_ends_with("metadata.title", "Man").to_string()
```
```bash
appwrite vectorsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"notEndsWith","attribute":"metadata.title","values":["Man"]}'
```
```http
{"method":"notEndsWith","attribute":"metadata.title","values":["Man"]}
```

##### Contains

Returns documents if a metadata array field contains the specified elements or if a metadata string field contains the specified substring.

```client-web
// For arrays
Query.contains("metadata.tags", ["space"])

// For strings
Query.contains("metadata.title", "Iron")
```
```client-flutter
// For arrays
Query.contains("metadata.tags", ["space"])

// For strings
Query.contains("metadata.title", "Iron")
```
```client-react-native
// For arrays
Query.contains("metadata.tags", ["space"])

// For strings
Query.contains("metadata.title", "Iron")
```
```client-apple
// For arrays
Query.contains("metadata.tags", value: ["space"])

// For strings
Query.contains("metadata.title", value: "Iron")
```
```client-android-kotlin
// For arrays
Query.contains("metadata.tags", listOf("space"))

// For strings
Query.contains("metadata.title", "Iron")
```
```client-android-java
// For arrays
Query.contains("metadata.tags", Arrays.asList("space"))

// For strings
Query.contains("metadata.title", "Iron")
```
```server-python
### For arrays
Query.contains("metadata.tags", ["space"])

### For strings
Query.contains("metadata.title", "Iron")
```
```server-ruby
### For arrays
Query.contains("metadata.tags", ["space"])

### For strings
Query.contains("metadata.title", "Iron")
```
```server-deno
// For arrays
Query.contains("metadata.tags", ["space"])

// For strings
Query.contains("metadata.title", "Iron")
```
```server-nodejs
// For arrays
Query.contains("metadata.tags", ["space"])

// For strings
Query.contains("metadata.title", "Iron")
```
```server-php
// For arrays
Query::contains("metadata.tags", ["space"])

// For strings
Query::contains("metadata.title", "Iron")
```
```server-dotnet
// For arrays
Query.Contains("metadata.tags", new List<string> { "space" })

// For strings
Query.Contains("metadata.title", "Iron")
```
```server-go
// For arrays
query.Contains("metadata.tags", []string{"space"})

// For strings
query.Contains("metadata.title", "Iron")
```
```server-dart
// For arrays
Query.contains("metadata.tags", ["space"])

// For strings
Query.contains("metadata.title", "Iron")
```
```server-swift
// For arrays
Query.contains("metadata.tags", value: ["space"])

// For strings
Query.contains("metadata.title", value: "Iron")
```
```server-kotlin
// For arrays
Query.contains("metadata.tags", listOf("space"))

// For strings
Query.contains("metadata.title", "Iron")
```
```server-rust
// For arrays
Query::contains("metadata.tags", Value::Array(vec![
    Value::String("space".to_string()),
])).to_string()

// For strings
Query::contains("metadata.title", "Iron").to_string()
```
```bash
appwrite vectorsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"contains","attribute":"metadata.tags","values":["space"]}'
```
```http
### For arrays
{"method":"contains","attribute":"metadata.tags","values":["space"]}

### For strings
{"method":"contains","attribute":"metadata.title","values":["Iron"]}
```

##### Not contains

Returns documents if a metadata array field does not contain the specified elements, or if a metadata string field does not contain the specified substring.

```client-web
// For arrays
Query.notContains("metadata.tags", ["space"])

// For strings
Query.notContains("metadata.title", "Iron")
```
```client-flutter
// For arrays
Query.notContains("metadata.tags", ["space"])

// For strings
Query.notContains("metadata.title", "Iron")
```
```client-react-native
// For arrays
Query.notContains("metadata.tags", ["space"])

// For strings
Query.notContains("metadata.title", "Iron")
```
```client-apple
// For arrays
Query.notContains("metadata.tags", value: ["space"])

// For strings
Query.notContains("metadata.title", value: "Iron")
```
```client-android-kotlin
// For arrays
Query.notContains("metadata.tags", listOf("space"))

// For strings
Query.notContains("metadata.title", "Iron")
```
```client-android-java
// For arrays
Query.notContains("metadata.tags", Arrays.asList("space"))

// For strings
Query.notContains("metadata.title", "Iron")
```
```server-python
### For arrays
Query.not_contains("metadata.tags", ["space"])

### For strings
Query.not_contains("metadata.title", "Iron")
```
```server-ruby
### For arrays
Query.not_contains("metadata.tags", ["space"])

### For strings
Query.not_contains("metadata.title", "Iron")
```
```server-deno
// For arrays
Query.notContains("metadata.tags", ["space"])

// For strings
Query.notContains("metadata.title", "Iron")
```
```server-nodejs
// For arrays
Query.notContains("metadata.tags", ["space"])

// For strings
Query.notContains("metadata.title", "Iron")
```
```server-php
// For arrays
Query::notContains("metadata.tags", ["space"])

// For strings
Query::notContains("metadata.title", "Iron")
```
```server-dotnet
// For arrays
Query.NotContains("metadata.tags", new List<string> { "space" })

// For strings
Query.NotContains("metadata.title", "Iron")
```
```server-go
// For arrays
query.NotContains("metadata.tags", []string{"space"})

// For strings
query.NotContains("metadata.title", "Iron")
```
```server-dart
// For arrays
Query.notContains("metadata.tags", ["space"])

// For strings
Query.notContains("metadata.title", "Iron")
```
```server-swift
// For arrays
Query.notContains("metadata.tags", value: ["space"])

// For strings
Query.notContains("metadata.title", value: "Iron")
```
```server-kotlin
// For arrays
Query.notContains("metadata.tags", listOf("space"))

// For strings
Query.notContains("metadata.title", "Iron")
```
```server-rust
// For arrays
Query::not_contains("metadata.tags", Value::Array(vec![
    Value::String("space".to_string()),
])).to_string()

// For strings
Query::not_contains("metadata.title", "Iron").to_string()
```
```bash
appwrite vectorsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"notContains","attribute":"metadata.tags","values":["space"]}'
```
```http
### For arrays
{"method":"notContains","attribute":"metadata.tags","values":["space"]}

### For strings
{"method":"notContains","attribute":"metadata.title","values":["Iron"]}
```

#### Logical operators

##### AND

Returns document if it matches all of the nested sub-queries in the array passed in.

```client-web
Query.and([
    Query.equal("metadata.genre", "sci-fi"),
    Query.startsWith("metadata.title", "Inter")
])
```
```client-flutter
Query.and([
    Query.equal("metadata.genre", "sci-fi"),
    Query.startsWith("metadata.title", "Inter")
])
```
```client-react-native
Query.and([
    Query.equal("metadata.genre", "sci-fi"),
    Query.startsWith("metadata.title", "Inter")
])
```
```client-apple
Query.and([
    Query.equal("metadata.genre", value: "sci-fi"),
    Query.startsWith("metadata.title", value: "Inter")
])
```
```client-android-kotlin
Query.and(listOf(
    Query.equal("metadata.genre", "sci-fi"),
    Query.startsWith("metadata.title", "Inter")
))
```
```client-android-java
Query.and(Arrays.asList(
    Query.equal("metadata.genre", "sci-fi"),
    Query.startsWith("metadata.title", "Inter")
))
```
```server-python
Query.and_queries([
    Query.equal("metadata.genre", "sci-fi"),
    Query.starts_with("metadata.title", "Inter")
])
```
```server-ruby
Query.and([
    Query.equal("metadata.genre", "sci-fi"),
    Query.starts_with("metadata.title", "Inter")
])
```
```server-deno
Query.and([
    Query.equal("metadata.genre", "sci-fi"),
    Query.startsWith("metadata.title", "Inter")
])
```
```server-nodejs
Query.and([
    Query.equal("metadata.genre", "sci-fi"),
    Query.startsWith("metadata.title", "Inter")
])
```
```server-php
Query::and([
    Query::equal("metadata.genre", "sci-fi"),
    Query::startsWith("metadata.title", "Inter")
])
```
```server-dotnet
Query.And(new List<string> {
    Query.Equal("metadata.genre", "sci-fi"),
    Query.StartsWith("metadata.title", "Inter")
})
```
```server-go
query.And([]string{
    query.Equal("metadata.genre", []string{"sci-fi"}),
    query.StartsWith("metadata.title", "Inter"),
})
```
```server-dart
Query.and([
    Query.equal("metadata.genre", "sci-fi"),
    Query.startsWith("metadata.title", "Inter")
])
```
```server-swift
Query.and([
    Query.equal("metadata.genre", value: "sci-fi"),
    Query.startsWith("metadata.title", value: "Inter")
])
```
```server-kotlin
Query.and(listOf(
    Query.equal("metadata.genre", "sci-fi"),
    Query.startsWith("metadata.title", "Inter")
))
```
```server-rust
Query::and(vec![
    Query::equal("metadata.genre", Value::String("sci-fi".to_string())).to_string(),
    Query::starts_with("metadata.title", "Inter").to_string(),
]).to_string()
```
```bash
appwrite vectorsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"and","values":[{"method":"equal","attribute":"metadata.genre","values":["sci-fi"]},{"method":"startsWith","attribute":"metadata.title","values":["Inter"]}]}'
```
```http
{"method":"and","values":[{"method":"equal","attribute":"metadata.genre","values":["sci-fi"]},{"method":"startsWith","attribute":"metadata.title","values":["Inter"]}]}
```

##### OR

Returns document if it matches any of the nested sub-queries in the array passed in.

```client-web
Query.or([
    Query.equal("metadata.genre", "romance"),
    Query.equal("metadata.genre", "sci-fi")
])
```
```client-flutter
Query.or([
    Query.equal("metadata.genre", "romance"),
    Query.equal("metadata.genre", "sci-fi")
])
```
```client-react-native
Query.or([
    Query.equal("metadata.genre", "romance"),
    Query.equal("metadata.genre", "sci-fi")
])
```
```client-apple
Query.or([
    Query.equal("metadata.genre", value: "romance"),
    Query.equal("metadata.genre", value: "sci-fi")
])
```
```client-android-kotlin
Query.or(listOf(
    Query.equal("metadata.genre", "romance"),
    Query.equal("metadata.genre", "sci-fi")
))
```
```client-android-java
Query.or(Arrays.asList(
    Query.equal("metadata.genre", "romance"),
    Query.equal("metadata.genre", "sci-fi")
))
```
```server-python
Query.or_queries([
    Query.equal("metadata.genre", "romance"),
    Query.equal("metadata.genre", "sci-fi")
])
```
```server-ruby
Query.or([
    Query.equal("metadata.genre", "romance"),
    Query.equal("metadata.genre", "sci-fi")
])
```
```server-deno
Query.or([
    Query.equal("metadata.genre", "romance"),
    Query.equal("metadata.genre", "sci-fi")
])
```
```server-nodejs
Query.or([
    Query.equal("metadata.genre", "romance"),
    Query.equal("metadata.genre", "sci-fi")
])
```
```server-php
Query::or([
    Query::equal("metadata.genre", "romance"),
    Query::equal("metadata.genre", "sci-fi")
])
```
```server-dotnet
Query.Or(new List<string> {
    Query.Equal("metadata.genre", "romance"),
    Query.Equal("metadata.genre", "sci-fi")
})
```
```server-go
query.Or([]string{
    query.Equal("metadata.genre", []string{"romance"}),
    query.Equal("metadata.genre", []string{"sci-fi"}),
})
```
```server-dart
Query.or([
    Query.equal("metadata.genre", "romance"),
    Query.equal("metadata.genre", "sci-fi")
])
```
```server-swift
Query.or([
    Query.equal("metadata.genre", value: "romance"),
    Query.equal("metadata.genre", value: "sci-fi")
])
```
```server-kotlin
Query.or(listOf(
    Query.equal("metadata.genre", "romance"),
    Query.equal("metadata.genre", "sci-fi")
))
```
```server-rust
Query::or(vec![
    Query::equal("metadata.genre", Value::String("romance".to_string())).to_string(),
    Query::equal("metadata.genre", Value::String("sci-fi".to_string())).to_string(),
]).to_string()
```
```bash
appwrite vectorsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"or","values":[{"method":"equal","attribute":"metadata.genre","values":["romance"]},{"method":"equal","attribute":"metadata.genre","values":["sci-fi"]}]}'
```
```http
{"method":"or","values":[{"method":"equal","attribute":"metadata.genre","values":["romance"]},{"method":"equal","attribute":"metadata.genre","values":["sci-fi"]}]}
```

#### Ordering

Order results by a top-level document attribute such as `$createdAt`, `$updatedAt`, or `$id`. Ordering by a nested `metadata` field is not supported.

##### Order descending

Orders results in descending order by attribute.

```client-web
Query.orderDesc("$createdAt")
```
```client-flutter
Query.orderDesc("$createdAt")
```
```client-react-native
Query.orderDesc("$createdAt")
```
```client-apple
Query.orderDesc("$createdAt")
```
```client-android-kotlin
Query.orderDesc("$createdAt")
```
```client-android-java
Query.orderDesc("$createdAt")
```
```server-python
Query.order_desc("$createdAt")
```
```server-ruby
Query.order_desc("$createdAt")
```
```server-deno
Query.orderDesc("$createdAt")
```
```server-nodejs
Query.orderDesc("$createdAt")
```
```server-php
Query::orderDesc("$createdAt")
```
```server-dotnet
Query.OrderDesc("$createdAt")
```
```server-go
query.OrderDesc("$createdAt")
```
```server-dart
Query.orderDesc("$createdAt")
```
```server-swift
Query.orderDesc("$createdAt")
```
```server-kotlin
Query.orderDesc("$createdAt")
```
```server-rust
Query::order_desc("$createdAt").to_string()
```
```bash
appwrite vectorsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"orderDesc","attribute":"$createdAt"}'
```
```http
{"method":"orderDesc","attribute":"$createdAt"}
```

##### Order ascending

Orders results in ascending order by attribute.

```client-web
Query.orderAsc("$createdAt")
```
```client-flutter
Query.orderAsc("$createdAt")
```
```client-react-native
Query.orderAsc("$createdAt")
```
```client-apple
Query.orderAsc("$createdAt")
```
```client-android-kotlin
Query.orderAsc("$createdAt")
```
```client-android-java
Query.orderAsc("$createdAt")
```
```server-python
Query.order_asc("$createdAt")
```
```server-ruby
Query.order_asc("$createdAt")
```
```server-deno
Query.orderAsc("$createdAt")
```
```server-nodejs
Query.orderAsc("$createdAt")
```
```server-php
Query::orderAsc("$createdAt")
```
```server-dotnet
Query.OrderAsc("$createdAt")
```
```server-go
query.OrderAsc("$createdAt")
```
```server-dart
Query.orderAsc("$createdAt")
```
```server-swift
Query.orderAsc("$createdAt")
```
```server-kotlin
Query.orderAsc("$createdAt")
```
```server-rust
Query::order_asc("$createdAt").to_string()
```
```bash
appwrite vectorsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"orderAsc","attribute":"$createdAt"}'
```
```http
{"method":"orderAsc","attribute":"$createdAt"}
```

##### Order random

Orders results in random order.

```client-web
Query.orderRandom()
```
```client-flutter
Query.orderRandom()
```
```client-react-native
Query.orderRandom()
```
```client-apple
Query.orderRandom()
```
```client-android-kotlin
Query.orderRandom()
```
```client-android-java
Query.orderRandom()
```
```server-python
Query.order_random()
```
```server-ruby
Query.order_random()
```
```server-deno
Query.orderRandom()
```
```server-nodejs
Query.orderRandom()
```
```server-php
Query::orderRandom()
```
```server-dotnet
Query.OrderRandom()
```
```server-go
query.OrderRandom()
```
```server-dart
Query.orderRandom()
```
```server-swift
Query.orderRandom()
```
```server-kotlin
Query.orderRandom()
```
```server-rust
Query::order_random().to_string()
```
```bash
appwrite vectorsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"orderRandom"}'
```
```http
{"method":"orderRandom"}
```

#### Pagination

##### Limit

Limits the number of results returned by the query. Used for [pagination](/docs/products/databases/vectorsdb/pagination).

```client-web
Query.limit(25)
```
```client-flutter
Query.limit(25)
```
```client-react-native
Query.limit(25)
```
```client-apple
Query.limit(25)
```
```client-android-kotlin
Query.limit(25)
```
```client-android-java
Query.limit(25)
```
```server-python
Query.limit(25)
```
```server-ruby
Query.limit(25)
```
```server-deno
Query.limit(25)
```
```server-nodejs
Query.limit(25)
```
```server-php
Query::limit(25)
```
```server-dotnet
Query.Limit(25)
```
```server-go
query.Limit(25)
```
```server-dart
Query.limit(25)
```
```server-swift
Query.limit(25)
```
```server-kotlin
Query.limit(25)
```
```server-rust
Query::limit(25).to_string()
```
```bash
appwrite vectorsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"limit","values":[25]}'
```
```http
{"method":"limit","values":[25]}
```

##### Offset

Offset the results returned by skipping some of the results. Used for [pagination](/docs/products/databases/vectorsdb/pagination).

```client-web
Query.offset(0)
```
```client-flutter
Query.offset(0)
```
```client-react-native
Query.offset(0)
```
```client-apple
Query.offset(0)
```
```client-android-kotlin
Query.offset(0)
```
```client-android-java
Query.offset(0)
```
```server-python
Query.offset(0)
```
```server-ruby
Query.offset(0)
```
```server-deno
Query.offset(0)
```
```server-nodejs
Query.offset(0)
```
```server-php
Query::offset(0)
```
```server-dotnet
Query.Offset(0)
```
```server-go
query.Offset(0)
```
```server-dart
Query.offset(0)
```
```server-swift
Query.offset(0)
```
```server-kotlin
Query.offset(0)
```
```server-rust
Query::offset(0).to_string()
```
```bash
appwrite vectorsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"offset","values":[0]}'
```
```http
{"method":"offset","values":[0]}
```

##### Cursor after

Places the cursor after the specified document ID. Used for [pagination](/docs/products/databases/vectorsdb/pagination).

```client-web
Query.cursorAfter("62a7...f620")
```
```client-flutter
Query.cursorAfter("62a7...f620")
```
```client-react-native
Query.cursorAfter("62a7...f620")
```
```client-apple
Query.cursorAfter("62a7...f620")
```
```client-android-kotlin
Query.cursorAfter("62a7...f620")
```
```client-android-java
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-nodejs
Query.cursorAfter("62a7...f620")
```
```server-php
Query::cursorAfter("62a7...f620")
```
```server-dotnet
Query.CursorAfter("62a7...f620")
```
```server-go
query.CursorAfter("62a7...f620")
```
```server-dart
Query.cursorAfter("62a7...f620")
```
```server-swift
Query.cursorAfter("62a7...f620")
```
```server-kotlin
Query.cursorAfter("62a7...f620")
```
```server-rust
Query::cursor_after("62a7...f620").to_string()
```
```bash
appwrite vectorsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"cursorAfter","values":["62a7...f620"]}'
```
```http
{"method":"cursorAfter","values":["62a7...f620"]}
```

##### Cursor before

Places the cursor before the specified document ID. Used for [pagination](/docs/products/databases/vectorsdb/pagination).

```client-web
Query.cursorBefore("62a7...a600")
```
```client-flutter
Query.cursorBefore("62a7...a600")
```
```client-react-native
Query.cursorBefore("62a7...a600")
```
```client-apple
Query.cursorBefore("62a7...a600")
```
```client-android-kotlin
Query.cursorBefore("62a7...a600")
```
```client-android-java
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-nodejs
Query.cursorBefore("62a7...a600")
```
```server-php
Query::cursorBefore("62a7...a600")
```
```server-dotnet
Query.CursorBefore("62a7...a600")
```
```server-go
query.CursorBefore("62a7...a600")
```
```server-dart
Query.cursorBefore("62a7...a600")
```
```server-swift
Query.cursorBefore("62a7...a600")
```
```server-kotlin
Query.cursorBefore("62a7...a600")
```
```server-rust
Query::cursor_before("62a7...a600").to_string()
```
```bash
appwrite vectorsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"cursorBefore","values":["62a7...a600"]}'
```
```http
{"method":"cursorBefore","values":["62a7...a600"]}
```

### Complex queries

You can create complex queries by combining AND and OR operations. For example, to find documents that are either sci-fi released after 2010 or romance released before 2005:

```client-web
const results = await vectorsDB.listDocuments({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    queries: [
        Query.or([
            Query.and([
                Query.equal('metadata.genre', ['sci-fi']),
                Query.greaterThan('metadata.year', '2010')
            ]),
            Query.and([
                Query.equal('metadata.genre', ['romance']),
                Query.lessThan('metadata.year', '2005')
            ])
        ])
    ]
});
```
```client-flutter
final results = await vectorsDB.listDocuments(
    '<DATABASE_ID>',
    '<COLLECTION_ID>',
    [
        Query.or([
            Query.and([
                Query.equal('metadata.genre', ['sci-fi']),
                Query.greaterThan('metadata.year', '2010')
            ]),
            Query.and([
                Query.equal('metadata.genre', ['romance']),
                Query.lessThan('metadata.year', '2005')
            ])
        ])
    ]
);
```
```server-nodejs
const results = await vectorsDB.listDocuments({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    queries: [
        Query.or([
            Query.and([
                Query.equal('metadata.genre', ['sci-fi']),
                Query.greaterThan('metadata.year', '2010')
            ]),
            Query.and([
                Query.equal('metadata.genre', ['romance']),
                Query.lessThan('metadata.year', '2005')
            ])
        ])
    ]
});
```
```server-python
results = vectorsDB.list_documents(
    database_id='<DATABASE_ID>',
    collection_id='<COLLECTION_ID>',
    queries=[
        Query.or_queries([
            Query.and_queries([
                Query.equal('metadata.genre', ['sci-fi']),
                Query.greater_than('metadata.year', '2010')
            ]),
            Query.and_queries([
                Query.equal('metadata.genre', ['romance']),
                Query.less_than('metadata.year', '2005')
            ])
        ])
    ]
)
```
```server-go
documents, err := vectorsDB.ListDocuments(
    "<DATABASE_ID>",
    "<COLLECTION_ID>",
    vectorsDB.WithListDocumentsQueries([]string{
        query.Or([]string{
            query.And([]string{
                query.Equal("metadata.genre", []string{"sci-fi"}),
                query.GreaterThan("metadata.year", "2010"),
            }),
            query.And([]string{
                query.Equal("metadata.genre", []string{"romance"}),
                query.LessThan("metadata.year", "2005"),
            }),
        }),
    }),
)
if err != nil {
    log.Fatal(err)
}
```
```server-rust
let documents = vectors_db.list_documents(
    "<DATABASE_ID>",
    "<COLLECTION_ID>",
    Some(vec![
        Query::or(vec![
            Query::and(vec![
                Query::equal("metadata.genre", Value::Array(vec![Value::String("sci-fi".to_string())])).to_string(),
                Query::greater_than("metadata.year", "2010").to_string(),
            ]).to_string(),
            Query::and(vec![
                Query::equal("metadata.genre", Value::Array(vec![Value::String("romance".to_string())])).to_string(),
                Query::less_than("metadata.year", "2005").to_string(),
            ]).to_string(),
        ]).to_string(),
    ]),
    None,
    None,
    None,
).await?;
```
```bash
appwrite vectorsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"or","values":[{"method":"and","values":[{"method":"equal","attribute":"metadata.genre","values":["sci-fi"]},{"method":"greaterThan","attribute":"metadata.year","values":["2010"]}]},{"method":"and","values":[{"method":"equal","attribute":"metadata.genre","values":["romance"]},{"method":"lessThan","attribute":"metadata.year","values":["2005"]}]}]}'
```
```http
{"method":"or","values":[{"method":"and","values":[{"method":"equal","attribute":"metadata.genre","values":["sci-fi"]},{"method":"greaterThan","attribute":"metadata.year","values":["2010"]}]},{"method":"and","values":[{"method":"equal","attribute":"metadata.genre","values":["romance"]},{"method":"lessThan","attribute":"metadata.year","values":["2005"]}]}]}
```

This example demonstrates how to combine `OR` and `AND` operations. The query uses `Query.or()` to match either condition: sci-fi released after 2010 OR romance released before 2005.
Each condition within the OR is composed of two AND conditions, one for the genre and one for the year. The database returns documents that match either of these combined conditions.

[Learn about vector search](/docs/products/databases/vectorsdb/vector-search)

---

## Start with VectorsDB

URL: https://appwrite.io/docs/products/databases/vectorsdb/quick-start

An embedding is a list of numbers that represents the meaning of a piece of text. Text that means similar things gets similar numbers, even when the wording is different. VectorsDB stores those numbers for you and finds the closest ones to a question you ask, so you can search by meaning instead of by keyword.

In this guide you store three sentences about Appwrite as embeddings, then ask a question in plain English and get back the sentence that answers it. The question and the answer share no words.

These steps use a [Server SDK](/docs/sdks#server), which requires an [API key](/docs/advanced/platform/api-keys).

#### 1. Create database

![Create database type selection](/images/docs/products/databases/vectorsdb/create-database-type.avif)

1. In your project, go to **Databases**.
2. Click **Create database**.
3. Under **Choose database type**, select **VectorsDB** from the **Appwrite databases** group.
4. Name the database `Knowledge base`, and optionally add a custom database ID.
5. Under **Specifications**, select your preferred tier.
6. Review the database summary and click **Create database**.

#### 2. Create collection

![Create collection dialog](/images/docs/products/databases/vectorsdb/create-collection.avif)

1. Open the `Knowledge base` database and click **Create collection**.
2. Name the collection `Articles`, and optionally add a custom collection ID.
3. Under **Embedding model**, keep the default `nomic-embed-text`.
4. Click **Create**.

Every model produces vectors of one fixed length, called the dimension. A collection is locked to a single dimension, so all its documents hold vectors of the same length. Picking the model in the Console sets that dimension for you. `nomic-embed-text` produces 768 values, which is why the SDK examples below pass `dimension: 768`.

![Embedding model list](/images/docs/products/databases/vectorsdb/create-collection-model.avif)

The list also holds a **Custom dimension** option for a model you run yourself. `embedding-gemma` and `bge-small` are coming soon. Read what each one suits in [embeddings](/docs/products/databases/vectorsdb/embeddings).

There are no columns to define. Every VectorsDB collection is provisioned with the same shape: an `embeddings` vector and an optional `metadata` object.

Add a **Read** permission for the **Any** role so anyone can read documents.

```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 vectorsDB = new sdk.VectorsDB(client);

const result = await vectorsDB.createCollection({
    databaseId: '<DATABASE_ID>',
    collectionId: sdk.ID.unique(),
    name: 'Articles',
    dimension: 768,
    permissions: [sdk.Permission.read(sdk.Role.any())] // 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('<YOUR_PROJECT_ID>') // Your project ID
    .setKey('<YOUR_API_KEY>'); // Your secret API key

const vectorsDB = new sdk.VectorsDB(client);

const result = await vectorsDB.createCollection({
    databaseId: '<DATABASE_ID>',
    collectionId: sdk.ID.unique(),
    name: 'Articles',
    dimension: 768,
    permissions: [sdk.Permission.read(sdk.Role.any())] // optional
});
```
```php
<?php

use Appwrite\Client;
use Appwrite\Services\VectorsDB;
use Appwrite\ID;
use Appwrite\Permission;
use Appwrite\Role;

$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

$vectorsDB = new VectorsDB($client);

$result = $vectorsDB->createCollection(
    databaseId: '<DATABASE_ID>',
    collectionId: ID::unique(),
    name: 'Articles',
    dimension: 768,
    permissions: [Permission::read(Role::any())] // optional
);
```
```python
from appwrite.client import Client
from appwrite.services.vectors_db import VectorsDB
from appwrite.id import ID
from appwrite.permission import Permission
from appwrite.role import Role

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

vectors_db = VectorsDB(client)

result = vectors_db.create_collection(
    database_id = '<DATABASE_ID>',
    collection_id = ID.unique(),
    name = 'Articles',
    dimension = 768,
    permissions = [Permission.read(Role.any())] # optional
)
```
```ruby
require 'appwrite'

include Appwrite
include Appwrite::Permission
include Appwrite::Role

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

vectors_db = VectorsDB.new(client)

result = vectors_db.create_collection(
    database_id: '<DATABASE_ID>',
    collection_id: ID.unique(),
    name: 'Articles',
    dimension: 768,
    permissions: [Permission.read(Role.any())] # optional
)
```
```csharp
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

VectorsDB vectorsDB = new VectorsDB(client);

Collection result = await vectorsDB.CreateCollection(
    databaseId: "<DATABASE_ID>",
    collectionId: ID.Unique(),
    name: "Articles",
    dimension: 768,
    permissions: new List<string> { Permission.Read(Role.Any()) } // optional
);
```
```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

VectorsDB vectorsDB = VectorsDB(client);

Collection result = await vectorsDB.createCollection(
    databaseId: '<DATABASE_ID>',
    collectionId: ID.unique(),
    name: 'Articles',
    dimension: 768,
    permissions: [Permission.read(Role.any())], // optional
);
```
```kotlin
import io.appwrite.Client
import io.appwrite.ID
import io.appwrite.Permission
import io.appwrite.Role
import io.appwrite.services.VectorsDB

val client = Client(context)
    .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 vectorsDB = VectorsDB(client)

val response = vectorsDB.createCollection(
    databaseId = "<DATABASE_ID>",
    collectionId = ID.unique(),
    name = "Articles",
    dimension = 768,
    permissions = listOf(Permission.read(Role.any())), // optional
)
```
```java
import io.appwrite.Client;
import io.appwrite.ID;
import io.appwrite.Permission;
import io.appwrite.Role;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.VectorsDB;
import java.util.List;

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

VectorsDB vectorsDB = new VectorsDB(client);

vectorsDB.createCollection(
    "<DATABASE_ID>",
    ID.unique(),
    "Articles",
    768,
    List.of(Permission.read(Role.any())),
    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("<YOUR_PROJECT_ID>") // Your project ID
    .setKey("<YOUR_API_KEY>") // Your secret API key

let vectorsDB = VectorsDB(client)

let collection = try await vectorsDB.createCollection(
    databaseId: "<DATABASE_ID>",
    collectionId: ID.unique(),
    name: "Articles",
    dimension: 768,
    permissions: [Permission.read(Role.any())] // optional
)
```
```server-rust
use appwrite::Client;
use appwrite::services::VectorsDB;
use appwrite::id::ID;
use appwrite::permission::Permission;
use appwrite::role::Role;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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

    let vectors_db = VectorsDB::new(&client);

    let result = vectors_db.create_collection(
        "<DATABASE_ID>",
        ID::unique(),
        "Articles",
        768,
        Some(vec![Permission::read(Role::any()).to_string()]), // permissions (optional)
        None, // documentSecurity (optional)
        None, // enabled (optional)
    ).await?;

    println!("{:?}", result);
    Ok(())
}
```
```bash
appwrite vectorsdb create-collection \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --name "Articles" \
    --dimension 768 \
    --permissions 'read("any")'
```

#### 3. Store documents

Turn each sentence into an embedding with the [Embeddings](/docs/products/databases/vectorsdb/embeddings) service, then store the embedding in a document.

A document holds the vector in `embeddings` and anything else you want in `metadata`, which is free-form JSON. Store the original sentence there. A search returns documents, and without the text a document is just a list of numbers.

```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 embeddings = new sdk.Embeddings(client);
const vectorsDB = new sdk.VectorsDB(client);

const texts = [
    'Appwrite Functions run your code on demand.',
    'Appwrite Storage keeps your files safe.',
    'Appwrite Authentication signs users in and manages sessions.'
];

const generated = await embeddings.createTextEmbeddings({ texts });

for (let i = 0; i < texts.length; i++) {
    await vectorsDB.createDocument({
        databaseId: '<DATABASE_ID>',
        collectionId: '<COLLECTION_ID>',
        documentId: sdk.ID.unique(),
        data: {
            embeddings: generated.embeddings[i].embedding,
            metadata: { text: texts[i] }
        }
    });
}
```
```deno
import * as sdk from "npm: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 embeddings = new sdk.Embeddings(client);
const vectorsDB = new sdk.VectorsDB(client);

const texts = [
    'Appwrite Functions run your code on demand.',
    'Appwrite Storage keeps your files safe.',
    'Appwrite Authentication signs users in and manages sessions.'
];

const generated = await embeddings.createTextEmbeddings({ texts });

for (let i = 0; i < texts.length; i++) {
    await vectorsDB.createDocument({
        databaseId: '<DATABASE_ID>',
        collectionId: '<COLLECTION_ID>',
        documentId: sdk.ID.unique(),
        data: {
            embeddings: generated.embeddings[i].embedding,
            metadata: { text: texts[i] }
        }
    });
}
```
```php
<?php

use Appwrite\Client;
use Appwrite\Services\Embeddings;
use Appwrite\Services\VectorsDB;
use Appwrite\ID;

$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

$embeddings = new Embeddings($client);
$vectorsDB = new VectorsDB($client);

$texts = [
    'Appwrite Functions run your code on demand.',
    'Appwrite Storage keeps your files safe.',
    'Appwrite Authentication signs users in and manages sessions.'
];

$generated = $embeddings->createTextEmbeddings(texts: $texts);

foreach ($texts as $i => $text) {
    $vectorsDB->createDocument(
        databaseId: '<DATABASE_ID>',
        collectionId: '<COLLECTION_ID>',
        documentId: ID::unique(),
        data: [
            'embeddings' => $generated['embeddings'][$i]['embedding'],
            'metadata' => ['text' => $text]
        ]
    );
}
```
```python
from appwrite.client import Client
from appwrite.services.embeddings import Embeddings
from appwrite.services.vectors_db import VectorsDB
from appwrite.id import ID

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

embeddings = Embeddings(client)
vectors_db = VectorsDB(client)

texts = [
    'Appwrite Functions run your code on demand.',
    'Appwrite Storage keeps your files safe.',
    'Appwrite Authentication signs users in and manages sessions.'
]

generated = embeddings.create_text_embeddings(texts = texts)

for i, text in enumerate(texts):
    vectors_db.create_document(
        database_id = '<DATABASE_ID>',
        collection_id = '<COLLECTION_ID>',
        document_id = ID.unique(),
        data = {
            "embeddings": generated["embeddings"][i]["embedding"],
            "metadata": { "text": text }
        }
    )
```
```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

embeddings = Embeddings.new(client)
vectors_db = VectorsDB.new(client)

texts = [
    'Appwrite Functions run your code on demand.',
    'Appwrite Storage keeps your files safe.',
    'Appwrite Authentication signs users in and manages sessions.'
]

generated = embeddings.create_text_embeddings(texts: texts)

texts.each_with_index do |text, i|
    vectors_db.create_document(
        database_id: '<DATABASE_ID>',
        collection_id: '<COLLECTION_ID>',
        document_id: ID.unique(),
        data: {
            "embeddings" => generated.embeddings[i].embedding,
            "metadata" => { "text" => text }
        }
    )
end
```
```csharp
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

Embeddings embeddings = new Embeddings(client);
VectorsDB vectorsDB = new VectorsDB(client);

List<string> texts = new List<string> {
    "Appwrite Functions run your code on demand.",
    "Appwrite Storage keeps your files safe.",
    "Appwrite Authentication signs users in and manages sessions."
};

EmbeddingList generated = await embeddings.CreateTextEmbeddings(texts: texts);

for (int i = 0; i < texts.Count; i++)
{
    await vectorsDB.CreateDocument(
        databaseId: "<DATABASE_ID>",
        collectionId: "<COLLECTION_ID>",
        documentId: ID.Unique(),
        data: new {
            embeddings = generated.Embeddings[i].XEmbedding,
            metadata = new { text = texts[i] }
        }
    );
}
```
```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

Embeddings embeddings = Embeddings(client);
VectorsDB vectorsDB = VectorsDB(client);

final texts = [
    'Appwrite Functions run your code on demand.',
    'Appwrite Storage keeps your files safe.',
    'Appwrite Authentication signs users in and manages sessions.',
];

EmbeddingList generated = await embeddings.createTextEmbeddings(texts: texts);

for (var i = 0; i < texts.length; i++) {
    await vectorsDB.createDocument(
        databaseId: '<DATABASE_ID>',
        collectionId: '<COLLECTION_ID>',
        documentId: ID.unique(),
        data: {
            "embeddings": generated.embeddings[i].embedding,
            "metadata": { "text": texts[i] }
        },
    );
}
```
```kotlin
import io.appwrite.Client
import io.appwrite.ID
import io.appwrite.services.Embeddings
import io.appwrite.services.VectorsDB

val client = Client(context)
    .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 embeddings = Embeddings(client)
val vectorsDB = VectorsDB(client)

val texts = listOf(
    "Appwrite Functions run your code on demand.",
    "Appwrite Storage keeps your files safe.",
    "Appwrite Authentication signs users in and manages sessions."
)

val generated = embeddings.createTextEmbeddings(texts = texts)

texts.forEachIndexed { i, text ->
    vectorsDB.createDocument(
        databaseId = "<DATABASE_ID>",
        collectionId = "<COLLECTION_ID>",
        documentId = ID.unique(),
        data = mapOf(
            "embeddings" to generated.embeddings[i].embedding,
            "metadata" to mapOf("text" to text)
        ),
    )
}
```
```java
import io.appwrite.Client;
import io.appwrite.ID;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Embeddings;
import io.appwrite.services.VectorsDB;
import java.util.List;
import java.util.Map;

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

Embeddings embeddings = new Embeddings(client);
VectorsDB vectorsDB = new VectorsDB(client);

List<String> texts = List.of(
    "Appwrite Functions run your code on demand.",
    "Appwrite Storage keeps your files safe.",
    "Appwrite Authentication signs users in and manages sessions."
);

embeddings.createTextEmbeddings(
    texts,
    new CoroutineCallback<>((generated, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        for (int i = 0; i < texts.size(); i++) {
            vectorsDB.createDocument(
                "<DATABASE_ID>",
                "<COLLECTION_ID>",
                ID.unique(),
                Map.of(
                    "embeddings", generated.getEmbeddings().get(i).getEmbedding(),
                    "metadata", Map.of("text", texts.get(i))
                ),
                new CoroutineCallback<>((result, err) -> {
                    if (err != null) {
                        err.printStackTrace();
                        return;
                    }

                    System.out.println(result);
                })
            );
        }
    })
);
```
```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 embeddings = Embeddings(client)
let vectorsDB = VectorsDB(client)

let texts = [
    "Appwrite Functions run your code on demand.",
    "Appwrite Storage keeps your files safe.",
    "Appwrite Authentication signs users in and manages sessions."
]

let generated = try await embeddings.createTextEmbeddings(texts: texts)

for (i, text) in texts.enumerated() {
    _ = try await vectorsDB.createDocument(
        databaseId: "<DATABASE_ID>",
        collectionId: "<COLLECTION_ID>",
        documentId: ID.unique(),
        data: [
            "embeddings": generated.embeddings[i].embedding,
            "metadata": ["text": text]
        ]
    )
}
```
```server-rust
use appwrite::Client;
use appwrite::services::{Embeddings, VectorsDB};
use appwrite::id::ID;
use serde_json::json;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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

    let embeddings = Embeddings::new(&client);
    let vectors_db = VectorsDB::new(&client);

    let texts = vec![
        "Appwrite Functions run your code on demand.",
        "Appwrite Storage keeps your files safe.",
        "Appwrite Authentication signs users in and manages sessions.",
    ];

    let generated = embeddings.create_text_embeddings(
        texts.clone(),
        None, // model (optional)
    ).await?;

    for (i, text) in texts.iter().enumerate() {
        vectors_db.create_document(
            "<DATABASE_ID>",
            "<COLLECTION_ID>",
            ID::unique(),
            json!({
                "embeddings": generated.embeddings[i].embedding,
                "metadata": { "text": text }
            }),
            None, // permissions (optional)
        ).await?;
    }

    Ok(())
}
```
```bash
appwrite embeddings create-text-embeddings \
    --texts 'Appwrite Authentication signs users in and manages sessions.'

appwrite vectorsdb create-document \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --document-id 'unique()' \
    --data '{ "embeddings": <EMBEDDING_FROM_FIRST_COMMAND>, "metadata": { "text": "Appwrite Authentication signs users in and manages sessions." } }'
```

You now have three documents. Each one carries the sentence it was generated from.

#### 4. Read documents

To read documents back from your collection, use the `listDocuments` method.

```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 vectorsDB = new sdk.VectorsDB(client);

const result = await vectorsDB.listDocuments({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    queries: [
        sdk.Query.limit(10)
    ]
});
```
```deno
import * as sdk from "npm: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 vectorsDB = new sdk.VectorsDB(client);

const result = await vectorsDB.listDocuments({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    queries: [
        sdk.Query.limit(10)
    ]
});
```
```php
<?php

use Appwrite\Client;
use Appwrite\Services\VectorsDB;
use Appwrite\Query;

$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

$vectorsDB = new VectorsDB($client);

$result = $vectorsDB->listDocuments(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    queries: [
        Query::limit(10)
    ]
);
```
```python
from appwrite.client import Client
from appwrite.services.vectors_db import VectorsDB
from appwrite.query import Query

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

vectors_db = VectorsDB(client)

result = vectors_db.list_documents(
    database_id = '<DATABASE_ID>',
    collection_id = '<COLLECTION_ID>',
    queries = [
        Query.limit(10)
    ]
)
```
```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

vectors_db = VectorsDB.new(client)

result = vectors_db.list_documents(
    database_id: '<DATABASE_ID>',
    collection_id: '<COLLECTION_ID>',
    queries: [
        Query.limit(10)
    ]
)
```
```csharp
using Appwrite;
using Appwrite.Models;
using Appwrite.Services;
using Appwrite.Queries;

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

VectorsDB vectorsDB = new VectorsDB(client);

DocumentList result = await vectorsDB.ListDocuments(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    queries: new List<string> {
        Query.Limit(10)
    }
);
```
```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

VectorsDB vectorsDB = VectorsDB(client);

DocumentList result = await vectorsDB.listDocuments(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    queries: [
        Query.limit(10)
    ],
);
```
```kotlin
import io.appwrite.Client
import io.appwrite.Query
import io.appwrite.services.VectorsDB

val client = Client(context)
    .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 vectorsDB = VectorsDB(client)

val response = vectorsDB.listDocuments(
    databaseId = "<DATABASE_ID>",
    collectionId = "<COLLECTION_ID>",
    queries = listOf(
        Query.limit(10)
    ),
)
```
```java
import io.appwrite.Client;
import io.appwrite.Query;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.VectorsDB;
import java.util.List;

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

VectorsDB vectorsDB = new VectorsDB(client);

vectorsDB.listDocuments(
    "<DATABASE_ID>",
    "<COLLECTION_ID>",
    List.of(
        Query.limit(10)
    ),
    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("<YOUR_PROJECT_ID>") // Your project ID
    .setKey("<YOUR_API_KEY>") // Your secret API key

let vectorsDB = VectorsDB(client)

let documents = try await vectorsDB.listDocuments(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    queries: [
        Query.limit(10)
    ]
)
```
```server-rust
use appwrite::Client;
use appwrite::services::VectorsDB;
use appwrite::query::Query;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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

    let vectors_db = VectorsDB::new(&client);

    let result = vectors_db.list_documents(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
        Some(vec![Query::limit(10).to_string()]),
        None, // transactionId (optional)
        None, // total (optional)
        None, // ttl (optional)
    ).await?;

    println!("{:?}", result);
    Ok(())
}
```
```bash
appwrite vectorsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"limit","values":[10]}'
```

#### 5. Search documents

A search starts with a question, not a vector. Turn the question into an embedding the same way you turned your documents into embeddings, then ask for the documents closest to it.

Use the same model for both. Two models describe meaning in their own way, so an embedding from one model tells you nothing about an embedding from another.

```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 embeddings = new sdk.Embeddings(client);
const vectorsDB = new sdk.VectorsDB(client);

const generated = await embeddings.createTextEmbeddings({
    texts: ['How do I handle passwords?']
});

const result = await vectorsDB.listDocuments({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    queries: [
        sdk.Query.vectorCosine('embeddings', generated.embeddings[0].embedding),
        sdk.Query.limit(3)
    ]
});
```
```deno
import * as sdk from "npm: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 embeddings = new sdk.Embeddings(client);
const vectorsDB = new sdk.VectorsDB(client);

const generated = await embeddings.createTextEmbeddings({
    texts: ['How do I handle passwords?']
});

const result = await vectorsDB.listDocuments({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    queries: [
        sdk.Query.vectorCosine('embeddings', generated.embeddings[0].embedding),
        sdk.Query.limit(3)
    ]
});
```
```php
<?php

use Appwrite\Client;
use Appwrite\Services\Embeddings;
use Appwrite\Services\VectorsDB;
use Appwrite\Query;

$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

$embeddings = new Embeddings($client);
$vectorsDB = new VectorsDB($client);

$generated = $embeddings->createTextEmbeddings(
    texts: ['How do I handle passwords?']
);

$result = $vectorsDB->listDocuments(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    queries: [
        Query::vectorCosine('embeddings', $generated['embeddings'][0]['embedding']),
        Query::limit(3)
    ]
);
```
```python
from appwrite.client import Client
from appwrite.services.embeddings import Embeddings
from appwrite.services.vectors_db import VectorsDB
from appwrite.query import Query

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

embeddings = Embeddings(client)
vectors_db = VectorsDB(client)

generated = embeddings.create_text_embeddings(
    texts = ['How do I handle passwords?']
)

result = vectors_db.list_documents(
    database_id = '<DATABASE_ID>',
    collection_id = '<COLLECTION_ID>',
    queries = [
        Query.vector_cosine('embeddings', generated["embeddings"][0]["embedding"]),
        Query.limit(3)
    ]
)
```
```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

embeddings = Embeddings.new(client)
vectors_db = VectorsDB.new(client)

generated = embeddings.create_text_embeddings(
    texts: ['How do I handle passwords?']
)

result = vectors_db.list_documents(
    database_id: '<DATABASE_ID>',
    collection_id: '<COLLECTION_ID>',
    queries: [
        Query.vector_cosine('embeddings', generated.embeddings[0].embedding),
        Query.limit(3)
    ]
)
```
```csharp
using Appwrite;
using Appwrite.Models;
using Appwrite.Services;
using Appwrite.Queries;

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

Embeddings embeddings = new Embeddings(client);
VectorsDB vectorsDB = new VectorsDB(client);

EmbeddingList generated = await embeddings.CreateTextEmbeddings(
    texts: new List<string> { "How do I handle passwords?" }
);

DocumentList result = await vectorsDB.ListDocuments(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    queries: new List<string> {
        Query.VectorCosine("embeddings", generated.Embeddings[0].XEmbedding),
        Query.Limit(3)
    }
);
```
```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

Embeddings embeddings = Embeddings(client);
VectorsDB vectorsDB = VectorsDB(client);

EmbeddingList generated = await embeddings.createTextEmbeddings(
    texts: ['How do I handle passwords?'],
);

DocumentList result = await vectorsDB.listDocuments(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    queries: [
        Query.vectorCosine('embeddings', generated.embeddings[0].embedding),
        Query.limit(3),
    ],
);
```
```kotlin
import io.appwrite.Client
import io.appwrite.Query
import io.appwrite.ID
import io.appwrite.services.Embeddings
import io.appwrite.services.VectorsDB

val client = Client(context)
    .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 embeddings = Embeddings(client)
val vectorsDB = VectorsDB(client)

val generated = embeddings.createTextEmbeddings(
    texts = listOf("How do I handle passwords?")
)

val response = vectorsDB.listDocuments(
    databaseId = "<DATABASE_ID>",
    collectionId = "<COLLECTION_ID>",
    queries = listOf(
        Query.vectorCosine("embeddings", generated.embeddings[0].embedding),
        Query.limit(3)
    )
)
```
```java
import io.appwrite.Client;
import io.appwrite.Query;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Embeddings;
import io.appwrite.services.VectorsDB;
import java.util.List;

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

Embeddings embeddings = new Embeddings(client);
VectorsDB vectorsDB = new VectorsDB(client);

embeddings.createTextEmbeddings(
    List.of("How do I handle passwords?"),
    new CoroutineCallback<>((generated, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        vectorsDB.listDocuments(
            "<DATABASE_ID>",
            "<COLLECTION_ID>",
            List.of(
                Query.vectorCosine("embeddings", generated.getEmbeddings().get(0).getEmbedding()),
                Query.limit(3)
            ),
            new CoroutineCallback<>((result, err) -> {
                if (err != null) {
                    err.printStackTrace();
                    return;
                }

                System.out.println(result);
            })
        );
    })
);
```
```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 embeddings = Embeddings(client)
let vectorsDB = VectorsDB(client)

let generated = try await embeddings.createTextEmbeddings(
    texts: ["How do I handle passwords?"]
)

let documentList = try await vectorsDB.listDocuments(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    queries: [
        Query.vectorCosine("embeddings", vector: generated.embeddings[0].embedding),
        Query.limit(3)
    ]
)
```
```server-rust
use appwrite::Client;
use appwrite::services::{Embeddings, VectorsDB};
use appwrite::query::Query;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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

    let embeddings = Embeddings::new(&client);
    let vectors_db = VectorsDB::new(&client);

    let generated = embeddings.create_text_embeddings(
        vec!["How do I handle passwords?"],
        None, // model (optional)
    ).await?;

    let result = vectors_db.list_documents(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
        Some(vec![
            Query::vector_cosine("embeddings", generated.embeddings[0].embedding.clone()).to_string(),
            Query::limit(3).to_string(),
        ]),
        None, // transactionId (optional)
        None, // total (optional)
        None, // ttl (optional)
    ).await?;

    println!("{:?}", result);
    Ok(())
}
```
```bash
appwrite embeddings create-text-embeddings \
    --texts 'How do I handle passwords?'

appwrite vectorsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"vectorCosine","attribute":"embeddings","values":[<EMBEDDING_FROM_FIRST_COMMAND>]}' \
    --queries '{"method":"limit","values":[3]}'
```

The response ranks every document by distance from the question, closest first:

```json
{
    "total": 3,
    "documents": [
        {
            "$distance": 0.42710475406362236,
            "metadata": { "text": "Appwrite Authentication signs users in and manages sessions." }
        },
        {
            "$distance": 0.5083307502161881,
            "metadata": { "text": "Appwrite Storage keeps your files safe." }
        },
        {
            "$distance": 0.5342290087037317,
            "metadata": { "text": "Appwrite Functions run your code on demand." }
        }
    ]
}
```

Nothing in your stored text contains the word "passwords", and the sentence about signing users in still comes back first. A keyword search would have found nothing at all.

A collection this small searches fine without an index. To keep searches fast as the collection grows, and to rank by dot product or Euclidean distance instead of cosine, see [vector search](/docs/products/databases/vectorsdb/vector-search).

#### 6. Next steps

You now have a database, a collection, three documents holding embeddings with their text, and a search that ranks them by meaning. From here:

- Pick a different model, or embed several texts in one call, with [embeddings](/docs/products/databases/vectorsdb/embeddings).
- Add an index, choose a distance type, and filter results with [vector search](/docs/products/databases/vectorsdb/vector-search).

[Go deeper on vector search](/docs/products/databases/vectorsdb/vector-search)

---

## Timestamp overrides

URL: https://appwrite.io/docs/products/databases/vectorsdb/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.

**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).

### 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

When working with individual documents, you can set custom timestamps during create, update, and upsert operations.

##### Create with custom timestamps

```server-nodejs
const sdk = require('node-appwrite');

const client = new sdk.Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<YOUR_PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const vectorsDB = new sdk.VectorsDB(client);

await vectorsDB.createDocument({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: sdk.ID.unique(),
    data: {
        '$createdAt': new Date('2025-08-10T12:34:56.000Z').toISOString(),
        '$updatedAt': new Date('2025-08-10T12:34:56.000Z').toISOString(),
        embeddings: [0.12, 0.84, 0.33, 0.57],
        metadata: { title: 'Hamlet' }
    }
});
```
```server-php
use Appwrite\Client;
use Appwrite\ID;
use Appwrite\Services\VectorsDB;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<YOUR_PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$vectorsDB = new VectorsDB($client);

$vectorsDB->createDocument(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: ID::unique(),
    data: [
        '$createdAt' => (new DateTime('<CUSTOM_DATE>'))->format(DATE_ATOM),
        '$updatedAt' => (new DateTime('<CUSTOM_DATE>'))->format(DATE_ATOM),
        'embeddings' => [0.12, 0.84, 0.33, 0.57],
        'metadata' => ['title' => 'Hamlet']
    ]
);
```
```server-swift
import Appwrite
import Foundation

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<YOUR_PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let vectorsDB = VectorsDB(client)

let isoFormatter = ISO8601DateFormatter()
isoFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
let customDate = isoFormatter.date(from: "<CUSTOM_DATE>") ?? Date()
let createdAt = isoFormatter.string(from: customDate)
let updatedAt = isoFormatter.string(from: customDate)

do {
    let created = try await vectorsDB.createDocument(
        databaseId: "<DATABASE_ID>",
        collectionId: "<COLLECTION_ID>",
        documentId: "<DOCUMENT_ID>",
        data: [
            "$createdAt": createdAt,
            "$updatedAt": updatedAt,
            "embeddings": [0.12, 0.84, 0.33, 0.57],
            "metadata": ["title": "Hamlet"]
        ]
    )
    print("Created:", created)
} catch {
    print("Create error:", error)
}
```
```server-python
from appwrite.client import Client
from appwrite.services.vectors_db import VectorsDB
from appwrite.id import ID
from datetime import datetime, timezone

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<YOUR_PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

vectors_db = VectorsDB(client)

iso = datetime(2025, 8, 10, 12, 34, 56, tzinfo=timezone.utc).isoformat()

vectors_db.create_document(
        database_id='<DATABASE_ID>',
        collection_id='<COLLECTION_ID>',
        document_id=ID.unique(),
        data={
                '$createdAt': iso,
                '$updatedAt': iso,
                'embeddings': [0.12, 0.84, 0.33, 0.57],
                'metadata': { 'title': 'Hamlet' }
        }
)
```
```server-ruby
require 'appwrite'
require 'time'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<YOUR_PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

vectors_db = VectorsDB.new(client)

custom_date = Time.parse('2025-08-10T12:34:56.000Z').iso8601

vectors_db.create_document(
    database_id: '<DATABASE_ID>',
    collection_id: '<COLLECTION_ID>',
    document_id: ID.unique(),
    data: {
        '$createdAt' => custom_date,
        '$updatedAt' => custom_date,
        'embeddings' => [0.12, 0.84, 0.33, 0.57],
        'metadata' => { 'title' => 'Hamlet' }
    }
)
```
```server-dotnet
using Appwrite;
using Appwrite.Models;
using Appwrite.Services;

Client client = new Client()
    .SetEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<YOUR_PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

VectorsDB vectorsDB = new VectorsDB(client);

string customDate = DateTimeOffset.Parse("2025-08-10T12:34:56.000Z").ToString("O");

await vectorsDB.CreateDocument(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    documentId: ID.Unique(),
    data: new Dictionary<string, object>
    {
        ["$createdAt"] = customDate,
        ["$updatedAt"] = customDate,
        ["embeddings"] = new List<double> { 0.12, 0.84, 0.33, 0.57 },
        ["metadata"] = new Dictionary<string, object> { ["title"] = "Hamlet" }
    }
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<YOUR_PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

VectorsDB vectorsDB = VectorsDB(client);

String customDate = DateTime.parse('2025-08-10T12:34:56.000Z').toIso8601String();

await vectorsDB.createDocument(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: ID.unique(),
    data: {
        '\$createdAt': customDate,
        '\$updatedAt': customDate,
        'embeddings': [0.12, 0.84, 0.33, 0.57],
        'metadata': { 'title': 'Hamlet' }
    },
);
```
```rust
use appwrite::Client;
use appwrite::services::VectorsDB;
use appwrite::id::ID;
use serde_json::json;

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

    let vectors_db = VectorsDB::new(&client);

    let result = vectors_db.create_document(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
        ID::unique(),
        json!({
            "$createdAt": "2025-08-10T12:34:56.000Z",
            "$updatedAt": "2025-08-10T12:34:56.000Z",
            "embeddings": [0.12, 0.84, 0.33, 0.57],
            "metadata": { "title": "Hamlet" }
        }),
        None, // permissions (optional)
    ).await?;

    println!("Created: {:?}", result);
    Ok(())
}
```

##### Update with custom timestamps

When updating documents, you can also set a custom `$updatedAt` timestamp. The existing `$createdAt` is preserved unless you provide a new one:

```server-nodejs
await vectorsDB.updateDocument({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>',
    data: {
        '$updatedAt': new Date('2025-08-10T12:34:56.000Z').toISOString(),
        metadata: { title: 'Hamlet, revised' }
    }
});
```
```server-php
$vectorsDB->updateDocument(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>',
    data: [
        '$updatedAt' => (new DateTime('<CUSTOM_DATE>'))->format(DATE_ATOM),
        'metadata' => ['title' => 'Hamlet, revised']
    ]
);
```
```server-python
from datetime import datetime, timezone

vectors_db.update_document(
    database_id='<DATABASE_ID>',
    collection_id='<COLLECTION_ID>',
    document_id='<DOCUMENT_ID>',
    data={
        '$updatedAt': datetime(2025, 8, 10, 12, 34, 56, tzinfo=timezone.utc).isoformat(),
        'metadata': { 'title': 'Hamlet, revised' }
    }
)
```
```server-swift
import Appwrite
import Foundation

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<YOUR_PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let vectorsDB = VectorsDB(client)

let isoFormatter = ISO8601DateFormatter()
isoFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
let updatedAt = isoFormatter.string(from: isoFormatter.date(from: "<CUSTOM_DATE>") ?? Date())

do {
    let updated = try await vectorsDB.updateDocument(
        databaseId: "<DATABASE_ID>",
        collectionId: "<COLLECTION_ID>",
        documentId: "<DOCUMENT_ID>",
        data: [
            "$updatedAt": updatedAt,
            "metadata": ["title": "Hamlet, revised"]
        ]
    )
    print("Updated:", updated)
} catch {
    print("Update error:", error)
}
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<YOUR_PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

vectors_db = VectorsDB.new(client)

custom_date = Time.parse('<CUSTOM_DATE>').iso8601

vectors_db.update_document(
  database_id: '<DATABASE_ID>',
  collection_id: '<COLLECTION_ID>',
  document_id: '<DOCUMENT_ID>',
  data: {
    '$updatedAt' => custom_date,
    'metadata' => { 'title' => 'Hamlet, revised' }
  }
)
```
```server-dotnet
using Appwrite;
using Appwrite.Models;
using Appwrite.Services;

Client client = new Client()
    .SetEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<YOUR_PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

VectorsDB vectorsDB = new VectorsDB(client);

string customDate = DateTimeOffset.Parse("<CUSTOM_DATE>").ToString("O");

await vectorsDB.UpdateDocument(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    documentId: "<DOCUMENT_ID>",
    data: new Dictionary<string, object>
    {
        ["$updatedAt"] = customDate,
        ["metadata"] = new Dictionary<string, object> { ["title"] = "Hamlet, revised" }
    }
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<YOUR_PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

VectorsDB vectorsDB = VectorsDB(client);

String customDate = DateTime.parse('<CUSTOM_DATE>').toIso8601String();

await vectorsDB.updateDocument(
  databaseId: '<DATABASE_ID>',
  collectionId: '<COLLECTION_ID>',
  documentId: '<DOCUMENT_ID>',
  data: {
    '\$updatedAt': customDate,
    'metadata': { 'title': 'Hamlet, revised' }
  },
);
```
```rust
use appwrite::Client;
use appwrite::services::VectorsDB;
use serde_json::json;

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

    let vectors_db = VectorsDB::new(&client);

    let result = vectors_db.update_document(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
        "<DOCUMENT_ID>",
        Some(json!({
            "$updatedAt": "2025-08-10T12:34:56.000Z",
            "metadata": { "title": "Hamlet, revised" }
        })),
        None, // permissions (optional)
        None, // transactionId (optional)
    ).await?;

    println!("Updated: {:?}", result);
    Ok(())
}
```

#### Bulk operations

Custom timestamps also work with bulk operations, allowing you to set different timestamps for each document in the batch:

##### Bulk create

```server-nodejs
await vectorsDB.createDocuments({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documents: [
        {
            '$id': sdk.ID.unique(),
            '$createdAt': new Date('2024-01-01T00:00:00.000Z').toISOString(),
            '$updatedAt': new Date('2024-01-01T00:00:00.000Z').toISOString(),
            embeddings: [0.1, 0.1, 0.1, 0.1],
            metadata: { batch: 1 }
        },
        {
            '$id': sdk.ID.unique(),
            '$createdAt': new Date('2024-02-01T00:00:00.000Z').toISOString(),
            '$updatedAt': new Date('2024-02-01T00:00:00.000Z').toISOString(),
            embeddings: [0.2, 0.2, 0.2, 0.2],
            metadata: { batch: 2 }
        }
    ]
});
```
```server-python
vectors_db.create_documents(
        database_id='<DATABASE_ID>',
        collection_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(),
                'embeddings': [0.1, 0.1, 0.1, 0.1],
                'metadata': { 'batch': 1 }
            },
            {
                '$id': ID.unique(),
                '$createdAt': datetime(2024, 2, 1, tzinfo=timezone.utc).isoformat(),
                '$updatedAt': datetime(2024, 2, 1, tzinfo=timezone.utc).isoformat(),
                'embeddings': [0.2, 0.2, 0.2, 0.2],
                'metadata': { 'batch': 2 }
            }
        ]
)
```
```server-php
use Appwrite\Client;
use Appwrite\ID;
use Appwrite\Services\VectorsDB;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<YOUR_PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$vectorsDB = new VectorsDB($client);

$vectorsDB->createDocuments(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documents: [
        [
            '$id' => ID::unique(),
            '$createdAt' => (new DateTime('<CUSTOM_DATE>'))->format(DATE_ATOM),
            '$updatedAt' => (new DateTime('<CUSTOM_DATE>'))->format(DATE_ATOM),
            'embeddings' => [0.1, 0.1, 0.1, 0.1],
            'metadata' => ['batch' => 1]
        ],
        [
            '$id' => ID::unique(),
            '$createdAt' => (new DateTime('<CUSTOM_DATE>'))->format(DATE_ATOM),
            '$updatedAt' => (new DateTime('<CUSTOM_DATE>'))->format(DATE_ATOM),
            'embeddings' => [0.2, 0.2, 0.2, 0.2],
            'metadata' => ['batch' => 2]
        ],
    ]
);
```
```server-swift
import Appwrite
import Foundation

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<YOUR_PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let vectorsDB = VectorsDB(client)

let isoFormatter = ISO8601DateFormatter()
isoFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]

let first = isoFormatter.string(from: isoFormatter.date(from: "<CUSTOM_DATE>") ?? Date())
let second = isoFormatter.string(from: isoFormatter.date(from: "<CUSTOM_DATE>") ?? Date())

do {
    let bulkCreated = try await vectorsDB.createDocuments(
        databaseId: "<DATABASE_ID>",
        collectionId: "<COLLECTION_ID>",
        documents: [
            [
                "$id": ID.unique(),
                "$createdAt": first,
                "$updatedAt": first,
                "embeddings": [0.1, 0.1, 0.1, 0.1],
                "metadata": ["batch": 1]
            ],
            [
                "$id": ID.unique(),
                "$createdAt": second,
                "$updatedAt": second,
                "embeddings": [0.2, 0.2, 0.2, 0.2],
                "metadata": ["batch": 2]
            ]
        ]
    )
    print("Bulk create:", bulkCreated)
} catch {
    print("Bulk create error:", error)
}
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<YOUR_PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

vectors_db = VectorsDB.new(client)

first = Time.parse('<CUSTOM_DATE>').iso8601
second = Time.parse('<CUSTOM_DATE>').iso8601

vectors_db.create_documents(
  database_id: '<DATABASE_ID>',
  collection_id: '<COLLECTION_ID>',
  documents: [
    {
      '$id' => ID.unique(),
      '$createdAt' => first,
      '$updatedAt' => first,
      'embeddings' => [0.1, 0.1, 0.1, 0.1],
      'metadata' => { 'batch' => 1 }
    },
    {
      '$id' => ID.unique(),
      '$createdAt' => second,
      '$updatedAt' => second,
      'embeddings' => [0.2, 0.2, 0.2, 0.2],
      'metadata' => { 'batch' => 2 }
    }
  ]
)
```
```server-dotnet
using Appwrite;
using Appwrite.Models;
using Appwrite.Services;

Client client = new Client()
    .SetEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<YOUR_PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

VectorsDB vectorsDB = new VectorsDB(client);

string first = DateTimeOffset.Parse("<CUSTOM_DATE>").ToString("O");
string second = DateTimeOffset.Parse("<CUSTOM_DATE>").ToString("O");

await vectorsDB.CreateDocuments(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    documents: new List<object>
    {
        new Dictionary<string, object>
        {
            ["$id"] = ID.Unique(),
            ["$createdAt"] = first,
            ["$updatedAt"] = first,
            ["embeddings"] = new List<double> { 0.1, 0.1, 0.1, 0.1 },
            ["metadata"] = new Dictionary<string, object> { ["batch"] = 1 }
        },
        new Dictionary<string, object>
        {
            ["$id"] = ID.Unique(),
            ["$createdAt"] = second,
            ["$updatedAt"] = second,
            ["embeddings"] = new List<double> { 0.2, 0.2, 0.2, 0.2 },
            ["metadata"] = new Dictionary<string, object> { ["batch"] = 2 }
        }
    }
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<YOUR_PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

VectorsDB vectorsDB = VectorsDB(client);

String first = DateTime.parse('<CUSTOM_DATE>').toIso8601String();
String second = DateTime.parse('<CUSTOM_DATE>').toIso8601String();

await vectorsDB.createDocuments(
  databaseId: '<DATABASE_ID>',
  collectionId: '<COLLECTION_ID>',
  documents: [
    {
      '\$id': ID.unique(),
      '\$createdAt': first,
      '\$updatedAt': first,
      'embeddings': [0.1, 0.1, 0.1, 0.1],
      'metadata': { 'batch': 1 }
    },
    {
      '\$id': ID.unique(),
      '\$createdAt': second,
      '\$updatedAt': second,
      'embeddings': [0.2, 0.2, 0.2, 0.2],
      'metadata': { 'batch': 2 }
    }
  ],
);
```
```rust
use appwrite::Client;
use appwrite::services::VectorsDB;
use appwrite::id::ID;
use serde_json::json;

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

    let vectors_db = VectorsDB::new(&client);

    let result = vectors_db.create_documents(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
        vec![
            json!({
                "$id": ID::unique(),
                "$createdAt": "2024-01-01T00:00:00.000Z",
                "$updatedAt": "2024-01-01T00:00:00.000Z",
                "embeddings": [0.1, 0.1, 0.1, 0.1],
                "metadata": { "batch": 1 }
            }),
            json!({
                "$id": ID::unique(),
                "$createdAt": "2024-02-01T00:00:00.000Z",
                "$updatedAt": "2024-02-01T00:00:00.000Z",
                "embeddings": [0.2, 0.2, 0.2, 0.2],
                "metadata": { "batch": 2 }
            }),
        ],
    ).await?;

    println!("Bulk create: {:?}", result);
    Ok(())
}
```

##### Bulk upsert

```server-nodejs
await vectorsDB.upsertDocuments({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documents: [
        {
            '$id': '<DOCUMENT_ID_OR_NEW_ID>',
            '$createdAt': new Date('2024-01-01T00:00:00.000Z').toISOString(),
            '$updatedAt': new Date('2025-01-01T00:00:00.000Z').toISOString(),
            embeddings: [0.3, 0.3, 0.3, 0.3],
            metadata: { source: 'sync' }
        }
    ]
});
```
```server-python
vectors_db.upsert_documents(
    database_id='<DATABASE_ID>',
    collection_id='<COLLECTION_ID>',
    documents=[
        {
            '$id': '<DOCUMENT_ID_OR_NEW_ID>',
            '$createdAt': datetime(2024, 1, 1, tzinfo=timezone.utc).isoformat(),
            '$updatedAt': datetime(2025, 1, 1, tzinfo=timezone.utc).isoformat(),
            'embeddings': [0.3, 0.3, 0.3, 0.3],
            'metadata': { 'source': 'sync' }
        }
    ]
)
```
```server-php
use Appwrite\Client;
use Appwrite\ID;
use Appwrite\Services\VectorsDB;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<YOUR_PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$vectorsDB = new VectorsDB($client);

$vectorsDB->upsertDocuments(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documents: [
        [
            '$id' => '<DOCUMENT_ID_OR_NEW_ID>',
            '$createdAt' => (new DateTime('<CUSTOM_DATE>'))->format(DATE_ATOM),
            '$updatedAt' => (new DateTime('<CUSTOM_DATE>'))->format(DATE_ATOM),
            'embeddings' => [0.3, 0.3, 0.3, 0.3],
            'metadata' => ['source' => 'sync']
        ],
    ]
);
```
```server-swift
import Appwrite
import Foundation

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<YOUR_PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let vectorsDB = VectorsDB(client)

let isoFormatter = ISO8601DateFormatter()
isoFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
let createdAt = isoFormatter.string(from: isoFormatter.date(from: "<CUSTOM_DATE>") ?? Date())
let updatedAt = isoFormatter.string(from: isoFormatter.date(from: "<CUSTOM_DATE>") ?? Date())

do {
    let bulkUpserted = try await vectorsDB.upsertDocuments(
        databaseId: "<DATABASE_ID>",
        collectionId: "<COLLECTION_ID>",
        documents: [
            [
                "$id": "<DOCUMENT_ID_OR_NEW_ID>",
                "$createdAt": createdAt,
                "$updatedAt": updatedAt,
                "embeddings": [0.3, 0.3, 0.3, 0.3],
                "metadata": ["source": "sync"]
            ]
        ]
    )
    print("Bulk upsert:", bulkUpserted)
} catch {
    print("Bulk upsert error:", error)
}
```
```server-ruby
require 'appwrite'
require 'time'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<YOUR_PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

vectors_db = VectorsDB.new(client)

custom_date = Time.parse('<CUSTOM_DATE>').iso8601

vectors_db.upsert_documents(
  database_id: '<DATABASE_ID>',
  collection_id: '<COLLECTION_ID>',
  documents: [
    {
      '$id' => '<DOCUMENT_ID_OR_NEW_ID>',
      '$createdAt' => custom_date,
      '$updatedAt' => custom_date,
      'embeddings' => [0.3, 0.3, 0.3, 0.3],
      'metadata' => { 'source' => 'sync' }
    }
  ]
)
```
```server-dotnet
using Appwrite;
using Appwrite.Models;
using Appwrite.Services;

Client client = new Client()
    .SetEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<YOUR_PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

VectorsDB vectorsDB = new VectorsDB(client);

string createdAt = DateTimeOffset.Parse("<CUSTOM_DATE>").ToString("O");
string updatedAt = DateTimeOffset.Parse("<CUSTOM_DATE>").ToString("O");

await vectorsDB.UpsertDocuments(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    documents: new List<object>
    {
        new Dictionary<string, object>
        {
            ["$id"] = "<DOCUMENT_ID_OR_NEW_ID>",
            ["$createdAt"] = createdAt,
            ["$updatedAt"] = updatedAt,
            ["embeddings"] = new List<double> { 0.3, 0.3, 0.3, 0.3 },
            ["metadata"] = new Dictionary<string, object> { ["source"] = "sync" }
        }
    }
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<YOUR_PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

VectorsDB vectorsDB = VectorsDB(client);

String createdAt = DateTime.parse('<CUSTOM_DATE>').toIso8601String();
String updatedAt = DateTime.parse('<CUSTOM_DATE>').toIso8601String();

await vectorsDB.upsertDocuments(
  databaseId: '<DATABASE_ID>',
  collectionId: '<COLLECTION_ID>',
  documents: [
    {
      '\$id': '<DOCUMENT_ID_OR_NEW_ID>',
      '\$createdAt': createdAt,
      '\$updatedAt': updatedAt,
      'embeddings': [0.3, 0.3, 0.3, 0.3],
      'metadata': { 'source': 'sync' }
    }
  ],
);
```
```rust
use appwrite::Client;
use appwrite::services::VectorsDB;
use serde_json::json;

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

    let vectors_db = VectorsDB::new(&client);

    let result = vectors_db.upsert_documents(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
        vec![
            json!({
                "$id": "<DOCUMENT_ID_OR_NEW_ID>",
                "$createdAt": "2024-01-01T00:00:00.000Z",
                "$updatedAt": "2025-01-01T00:00:00.000Z",
                "embeddings": [0.3, 0.3, 0.3, 0.3],
                "metadata": { "source": "sync" }
            }),
        ],
        None, // transactionId (optional)
    ).await?;

    println!("Bulk upsert: {:?}", result);
    Ok(())
}
```

### Common use cases

Custom timestamps are particularly useful in several scenarios:

#### Data migration
When migrating existing vectors from another system, you can preserve the original
creation and modification times:

```server-nodejs
await vectorsDB.createDocument({
  databaseId: '<DATABASE_ID>',
  collectionId: '<COLLECTION_ID>',
  documentId: sdk.ID.unique(),
  data: {
    '$createdAt': '<ORIGINAL_CREATED_AT_ISO>',
    '$updatedAt': '<LAST_MODIFIED_ISO>',
    embeddings: [0.12, 0.84, 0.33, 0.57],
    metadata: { title: 'Imported post' }
  }
});
```
```server-php
$vectorsDB->createDocument(
  databaseId: '<DATABASE_ID>',
  collectionId: '<COLLECTION_ID>',
  documentId: ID::unique(),
  data: [
    '$createdAt' => '<ORIGINAL_CREATED_AT_ISO>',
    '$updatedAt' => '<LAST_MODIFIED_ISO>',
    'embeddings' => [0.12, 0.84, 0.33, 0.57],
    'metadata' => ['title' => 'Imported post']
  ]
);
```
```server-swift
let _ = try await vectorsDB.createDocument(
  databaseId: "<DATABASE_ID>",
  collectionId: "<COLLECTION_ID>",
  documentId: ID.unique(),
  data: [
    "$createdAt": "<ORIGINAL_CREATED_AT_ISO>",
    "$updatedAt": "<LAST_MODIFIED_ISO>",
    "embeddings": [0.12, 0.84, 0.33, 0.57],
    "metadata": ["title": "Imported post"]
  ]
)
```
```server-python
vectors_db.create_document(
  database_id='<DATABASE_ID>',
  collection_id='<COLLECTION_ID>',
  document_id=ID.unique(),
  data={
    '$createdAt': '<ORIGINAL_CREATED_AT_ISO>',
    '$updatedAt': '<LAST_MODIFIED_ISO>',
    'embeddings': [0.12, 0.84, 0.33, 0.57],
    'metadata': { 'title': 'Imported post' }
  }
)
```
```server-ruby
vectors_db.create_document(
  database_id: '<DATABASE_ID>',
  collection_id: '<COLLECTION_ID>',
  document_id: ID.unique(),
  data: {
    '$createdAt' => '<ORIGINAL_CREATED_AT_ISO>',
    '$updatedAt' => '<LAST_MODIFIED_ISO>',
    'embeddings' => [0.12, 0.84, 0.33, 0.57],
    'metadata' => { 'title' => 'Imported post' }
  }
)
```
```server-dotnet
await vectorsDB.CreateDocument(
  databaseId: "<DATABASE_ID>",
  collectionId: "<COLLECTION_ID>",
  documentId: ID.Unique(),
  data: new Dictionary<string, object>
  {
    ["$createdAt"] = "<ORIGINAL_CREATED_AT_ISO>",
    ["$updatedAt"] = "<LAST_MODIFIED_ISO>",
    ["embeddings"] = new List<double> { 0.12, 0.84, 0.33, 0.57 },
    ["metadata"] = new Dictionary<string, object> { ["title"] = "Imported post" }
  }
);
```
```server-dart
await vectorsDB.createDocument(
  databaseId: '<DATABASE_ID>',
  collectionId: '<COLLECTION_ID>',
  documentId: ID.unique(),
  data: {
    '\$createdAt': '<ORIGINAL_CREATED_AT_ISO>',
    '\$updatedAt': '<LAST_MODIFIED_ISO>',
    'embeddings': [0.12, 0.84, 0.33, 0.57],
    'metadata': { 'title': 'Imported post' }
  },
);
```
```rust
use appwrite::Client;
use appwrite::services::VectorsDB;
use appwrite::id::ID;
use serde_json::json;

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

    let vectors_db = VectorsDB::new(&client);

    let result = vectors_db.create_document(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
        ID::unique(),
        json!({
            "$createdAt": "<ORIGINAL_CREATED_AT_ISO>",
            "$updatedAt": "<LAST_MODIFIED_ISO>",
            "embeddings": [0.12, 0.84, 0.33, 0.57],
            "metadata": { "title": "Imported post" }
        }),
        None, // permissions (optional)
    ).await?;

    println!("Created: {:?}", result);
    Ok(())
}
```

#### Backdating records
For historical data entry or when creating records that represent past events:

```server-nodejs
await vectorsDB.createDocument({
  databaseId: '<DATABASE_ID>',
  collectionId: '<COLLECTION_ID>',
  documentId: sdk.ID.unique(),
  data: {
    '$createdAt': '2023-12-31T23:59:59.000Z',
    '$updatedAt': '2023-12-31T23:59:59.000Z',
    embeddings: [0.5, 0.5, 0.5, 0.5],
    metadata: { type: 'year-end-bonus', amount: 1000 }
  }
});
```
```server-php
$vectorsDB->createDocument(
  databaseId: '<DATABASE_ID>',
  collectionId: '<COLLECTION_ID>',
  documentId: ID::unique(),
  data: [
    '$createdAt' => '2023-12-31T23:59:59.000Z',
    '$updatedAt' => '2023-12-31T23:59:59.000Z',
    'embeddings' => [0.5, 0.5, 0.5, 0.5],
    'metadata' => ['type' => 'year-end-bonus', 'amount' => 1000]
  ]
);
```
```server-swift
let _ = try await vectorsDB.createDocument(
  databaseId: "<DATABASE_ID>",
  collectionId: "<COLLECTION_ID>",
  documentId: ID.unique(),
  data: [
    "$createdAt": "2023-12-31T23:59:59.000Z",
    "$updatedAt": "2023-12-31T23:59:59.000Z",
    "embeddings": [0.5, 0.5, 0.5, 0.5],
    "metadata": ["type": "year-end-bonus", "amount": 1000]
  ]
)
```
```server-python
vectors_db.create_document(
  database_id='<DATABASE_ID>',
  collection_id='<COLLECTION_ID>',
  document_id=ID.unique(),
  data={
    '$createdAt': '2023-12-31T23:59:59.000Z',
    '$updatedAt': '2023-12-31T23:59:59.000Z',
    'embeddings': [0.5, 0.5, 0.5, 0.5],
    'metadata': { 'type': 'year-end-bonus', 'amount': 1000 }
  }
)
```
```server-ruby
vectors_db.create_document(
  database_id: '<DATABASE_ID>',
  collection_id: '<COLLECTION_ID>',
  document_id: ID.unique(),
  data: {
    '$createdAt' => '2023-12-31T23:59:59.000Z',
    '$updatedAt' => '2023-12-31T23:59:59.000Z',
    'embeddings' => [0.5, 0.5, 0.5, 0.5],
    'metadata' => { 'type' => 'year-end-bonus', 'amount' => 1000 }
  }
)
```
```server-dotnet
await vectorsDB.CreateDocument(
  databaseId: "<DATABASE_ID>",
  collectionId: "<COLLECTION_ID>",
  documentId: ID.Unique(),
  data: new Dictionary<string, object>
  {
    ["$createdAt"] = "2023-12-31T23:59:59.000Z",
    ["$updatedAt"] = "2023-12-31T23:59:59.000Z",
    ["embeddings"] = new List<double> { 0.5, 0.5, 0.5, 0.5 },
    ["metadata"] = new Dictionary<string, object> { ["type"] = "year-end-bonus", ["amount"] = 1000 }
  }
);
```
```server-dart
await vectorsDB.createDocument(
  databaseId: '<DATABASE_ID>',
  collectionId: '<COLLECTION_ID>',
  documentId: ID.unique(),
  data: {
    '\$createdAt': '2023-12-31T23:59:59.000Z',
    '\$updatedAt': '2023-12-31T23:59:59.000Z',
    'embeddings': [0.5, 0.5, 0.5, 0.5],
    'metadata': { 'type': 'year-end-bonus', 'amount': 1000 }
  },
);
```
```rust
use appwrite::Client;
use appwrite::services::VectorsDB;
use appwrite::id::ID;
use serde_json::json;

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

    let vectors_db = VectorsDB::new(&client);

    let result = vectors_db.create_document(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
        ID::unique(),
        json!({
            "$createdAt": "2023-12-31T23:59:59.000Z",
            "$updatedAt": "2023-12-31T23:59:59.000Z",
            "embeddings": [0.5, 0.5, 0.5, 0.5],
            "metadata": { "type": "year-end-bonus", "amount": 1000 }
        }),
        None, // permissions (optional)
    ).await?;

    println!("Created: {:?}", result);
    Ok(())
}
```

#### Synchronization
When synchronizing data between systems while maintaining timestamp consistency:

```server-nodejs
await vectorsDB.upsertDocument({
  databaseId: '<DATABASE_ID>',
  collectionId: '<COLLECTION_ID>',
  documentId: '<DOCUMENT_ID_OR_NEW_ID>',
  data: {
    '$updatedAt': '<EXTERNAL_LAST_MODIFIED_ISO>',
    embeddings: [0.6, 0.6, 0.6, 0.6],
    metadata: { profile: 'external' }
  }
});
```
```server-php
$vectorsDB->upsertDocument(
  databaseId: '<DATABASE_ID>',
  collectionId: '<COLLECTION_ID>',
  documentId: '<DOCUMENT_ID_OR_NEW_ID>',
  data: [
    '$updatedAt' => '<EXTERNAL_LAST_MODIFIED_ISO>',
    'embeddings' => [0.6, 0.6, 0.6, 0.6],
    'metadata' => ['profile' => 'external']
  ]
);
```
```server-swift
let _ = try await vectorsDB.upsertDocument(
  databaseId: "<DATABASE_ID>",
  collectionId: "<COLLECTION_ID>",
  documentId: "<DOCUMENT_ID_OR_NEW_ID>",
  data: [
    "$updatedAt": "<EXTERNAL_LAST_MODIFIED_ISO>",
    "embeddings": [0.6, 0.6, 0.6, 0.6],
    "metadata": ["profile": "external"]
  ]
)
```
```server-python
vectors_db.upsert_document(
  database_id='<DATABASE_ID>',
  collection_id='<COLLECTION_ID>',
  document_id='<DOCUMENT_ID_OR_NEW_ID>',
  data={
    '$updatedAt': '<EXTERNAL_LAST_MODIFIED_ISO>',
    'embeddings': [0.6, 0.6, 0.6, 0.6],
    'metadata': { 'profile': 'external' }
  }
)
```
```server-ruby
vectors_db.upsert_document(
  database_id: '<DATABASE_ID>',
  collection_id: '<COLLECTION_ID>',
  document_id: '<DOCUMENT_ID_OR_NEW_ID>',
  data: {
    '$updatedAt' => '<EXTERNAL_LAST_MODIFIED_ISO>',
    'embeddings' => [0.6, 0.6, 0.6, 0.6],
    'metadata' => { 'profile' => 'external' }
  }
)
```
```server-dotnet
await vectorsDB.UpsertDocument(
  databaseId: "<DATABASE_ID>",
  collectionId: "<COLLECTION_ID>",
  documentId: "<DOCUMENT_ID_OR_NEW_ID>",
  data: new Dictionary<string, object>
  {
    ["$updatedAt"] = "<EXTERNAL_LAST_MODIFIED_ISO>",
    ["embeddings"] = new List<double> { 0.6, 0.6, 0.6, 0.6 },
    ["metadata"] = new Dictionary<string, object> { ["profile"] = "external" }
  }
);
```
```server-dart
await vectorsDB.upsertDocument(
  databaseId: '<DATABASE_ID>',
  collectionId: '<COLLECTION_ID>',
  documentId: '<DOCUMENT_ID_OR_NEW_ID>',
  data: {
    '\$updatedAt': '<EXTERNAL_LAST_MODIFIED_ISO>',
    'embeddings': [0.6, 0.6, 0.6, 0.6],
    'metadata': { 'profile': 'external' }
  },
);
```
```rust
use appwrite::Client;
use appwrite::services::VectorsDB;
use serde_json::json;

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

    let vectors_db = VectorsDB::new(&client);

    let result = vectors_db.upsert_document(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
        "<DOCUMENT_ID_OR_NEW_ID>",
        Some(json!({
            "$updatedAt": "<EXTERNAL_LAST_MODIFIED_ISO>",
            "embeddings": [0.6, 0.6, 0.6, 0.6],
            "metadata": { "profile": "external" }
        })),
        None, // permissions (optional)
        None, // transactionId (optional)
    ).await?;

    println!("Upserted: {:?}", result);
    Ok(())
}
```

**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.

---

## Transactions

URL: https://appwrite.io/docs/products/databases/vectorsdb/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 collections.

### 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 document and bulk methods. You can stage many operations at once with the [createOperations](#create-operations) method.
3. Call the [updateTransaction](#commit-or-rollback) 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 document changed outside your transaction, the commit fails with a conflict.

**Scope and limitations**

You can stage operations across any database and collection within the same transaction. Schema operations (for example, creating or deleting indexes) are not included in transactions.

### 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 |

### Create a transaction

Call the `createTransaction` method to begin. It returns a transaction model that includes `$id`, a `status` of `pending`, and an `expiresAt` timestamp. Pass the ID as `transactionId` to subsequent operations. A transaction expires 300 seconds after it is created unless you pass a different `ttl`. Appwrite [Server SDKs](/docs/sdks#server) require an [API key](/docs/advanced/platform/api-keys).

```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 vectorsDB = new sdk.VectorsDB(client);

const tx = await vectorsDB.createTransaction();
// tx.$id is your transactionId
```
```deno
import * as sdk from "npm: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 vectorsDB = new sdk.VectorsDB(client);

const tx = await vectorsDB.createTransaction();
// tx.$id is your transactionId
```
```php
<?php

use Appwrite\Client;
use Appwrite\Services\VectorsDB;

$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

$vectorsDB = new VectorsDB($client);

$tx = $vectorsDB->createTransaction();
// $tx['$id'] is your transactionId
```
```python
from appwrite.client import Client
from appwrite.services.vectors_db import VectorsDB

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

vectors_db = VectorsDB(client)

tx = vectors_db.create_transaction()
### tx['$id'] is your transactionId
```
```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

vectors_db = VectorsDB.new(client)

tx = vectors_db.create_transaction
### tx['$id'] is your transactionId
```
```csharp
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

VectorsDB vectorsDB = new VectorsDB(client);

var tx = await vectorsDB.CreateTransaction();
// tx.Id is your transactionId
```
```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

VectorsDB vectorsDB = VectorsDB(client);

final tx = await vectorsDB.createTransaction();
// tx.$id is your transactionId
```
```kotlin
import io.appwrite.Client
import io.appwrite.services.VectorsDB

val client = Client(context)
    .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 vectorsDB = VectorsDB(client)

val tx = vectorsDB.createTransaction()
// tx.id is your transactionId
```
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.VectorsDB;

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

VectorsDB vectorsDB = new VectorsDB(client);

vectorsDB.createTransaction(new CoroutineCallback<>((tx, error) -> {
    if (error != null) {
        error.printStackTrace();
        return;
    }

    System.out.println(tx);
}));
```
```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 vectorsDB = VectorsDB(client)

let tx = try await vectorsDB.createTransaction()
// tx.id is your transactionId
```
```server-rust
use appwrite::Client;
use appwrite::services::VectorsDB;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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

    let vectors_db = VectorsDB::new(&client);

    let tx = vectors_db.create_transaction(None).await?;
    // tx.id is your transaction_id

    Ok(())
}
```
```bash
appwrite vectorsdb create-transaction
```

### 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 collection is not modified until you commit the transaction.

#### Stage single operations

Update, upsert, and delete operations accept `transactionId`, as well as their bulk versions (`updateDocuments`, `upsertDocuments`, `deleteDocuments`). To stage the first write to a brand new document, use `upsertDocument` with `transactionId`.

```server-nodejs
// Update inside a transaction
await vectorsDB.updateDocument({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>',
    data: { metadata: { title: 'Hamlet', year: 1602 } },
    transactionId: tx.$id
});

// Delete inside a transaction
await vectorsDB.deleteDocument({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>',
    transactionId: tx.$id
});
```
```deno
// Update inside a transaction
await vectorsDB.updateDocument({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>',
    data: { metadata: { title: 'Hamlet', year: 1602 } },
    transactionId: tx.$id
});

// Delete inside a transaction
await vectorsDB.deleteDocument({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>',
    transactionId: tx.$id
});
```
```php
// Update inside a transaction
$vectorsDB->updateDocument(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>',
    data: ['metadata' => ['title' => 'Hamlet', 'year' => 1602]],
    transactionId: $tx['$id']
);

// Delete inside a transaction
$vectorsDB->deleteDocument(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>',
    transactionId: $tx['$id']
);
```
```python
### Update inside a transaction
vectors_db.update_document(
    database_id = '<DATABASE_ID>',
    collection_id = '<COLLECTION_ID>',
    document_id = '<DOCUMENT_ID>',
    data = { "metadata": { "title": "Hamlet", "year": 1602 } },
    transaction_id = tx['$id']
)

### Delete inside a transaction
vectors_db.delete_document(
    database_id = '<DATABASE_ID>',
    collection_id = '<COLLECTION_ID>',
    document_id = '<DOCUMENT_ID>',
    transaction_id = tx['$id']
)
```
```ruby
### Update inside a transaction
vectors_db.update_document(
    database_id: '<DATABASE_ID>',
    collection_id: '<COLLECTION_ID>',
    document_id: '<DOCUMENT_ID>',
    data: { "metadata" => { "title" => "Hamlet", "year" => 1602 } },
    transaction_id: tx['$id']
)

### Delete inside a transaction
vectors_db.delete_document(
    database_id: '<DATABASE_ID>',
    collection_id: '<COLLECTION_ID>',
    document_id: '<DOCUMENT_ID>',
    transaction_id: tx['$id']
)
```
```csharp
// Update inside a transaction
await vectorsDB.UpdateDocument(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    documentId: "<DOCUMENT_ID>",
    data: new Dictionary<string, object> {
        { "metadata", new Dictionary<string, object> { { "title", "Hamlet" }, { "year", 1602 } } }
    },
    transactionId: tx.Id
);

// Delete inside a transaction
await vectorsDB.DeleteDocument(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    documentId: "<DOCUMENT_ID>",
    transactionId: tx.Id
);
```
```dart
// Update inside a transaction
await vectorsDB.updateDocument(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>',
    data: { "metadata": { "title": "Hamlet", "year": 1602 } },
    transactionId: tx.$id
);

// Delete inside a transaction
await vectorsDB.deleteDocument(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>',
    transactionId: tx.$id
);
```
```kotlin
// Update inside a transaction
vectorsDB.updateDocument(
    databaseId = "<DATABASE_ID>",
    collectionId = "<COLLECTION_ID>",
    documentId = "<DOCUMENT_ID>",
    data = mapOf("metadata" to mapOf("title" to "Hamlet", "year" to 1602)),
    transactionId = tx.id
)

// Delete inside a transaction
vectorsDB.deleteDocument(
    databaseId = "<DATABASE_ID>",
    collectionId = "<COLLECTION_ID>",
    documentId = "<DOCUMENT_ID>",
    transactionId = tx.id
)
```
```java
// Update inside a transaction (asynchronous)
vectorsDB.updateDocument(
    "<DATABASE_ID>",
    "<COLLECTION_ID>",
    "<DOCUMENT_ID>",
    Map.of("metadata", Map.of("title", "Hamlet", "year", 1602)),
    listOf(),
    "<TRANSACTION_ID>",
    new CoroutineCallback<>((document, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        System.out.println(document);
    })
);
```
```swift
// Update inside a transaction
try await vectorsDB.updateDocument(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    documentId: "<DOCUMENT_ID>",
    data: ["metadata": ["title": "Hamlet", "year": 1602]],
    transactionId: tx.id
)

// Delete inside a transaction
try await vectorsDB.deleteDocument(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    documentId: "<DOCUMENT_ID>",
    transactionId: tx.id
)
```
```server-rust
use appwrite::Client;
use appwrite::services::VectorsDB;
use serde_json::json;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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

    let vectors_db = VectorsDB::new(&client);

    let tx = vectors_db.create_transaction(None).await?;

    // Update inside a transaction
    vectors_db.update_document(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
        "<DOCUMENT_ID>",
        Some(json!({ "metadata": { "title": "Hamlet", "year": 1602 } })),
        None,
        Some(&tx.id),
    ).await?;

    // Delete inside a transaction
    vectors_db.delete_document(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
        "<DOCUMENT_ID>",
        Some(&tx.id),
    ).await?;

    Ok(())
}
```
```bash
appwrite vectorsdb update-document \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --document-id <DOCUMENT_ID> \
    --data '{ "metadata": { "title": "Hamlet", "year": 1602 } }' \
    --transaction-id <TRANSACTION_ID>
```

You can read uncommitted changes back inside the same transaction by passing `transactionId` to `getDocument`. A plain read without `transactionId` still returns the committed document.

```server-nodejs
// Read your own staged write
const staged = await vectorsDB.getDocument({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>',
    transactionId: tx.$id
});
```
```deno
// Read your own staged write
const staged = await vectorsDB.getDocument({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>',
    transactionId: tx.$id
});
```
```php
// Read your own staged write
$staged = $vectorsDB->getDocument(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>',
    transactionId: $tx['$id']
);
```
```python
### Read your own staged write
staged = vectors_db.get_document(
    database_id = '<DATABASE_ID>',
    collection_id = '<COLLECTION_ID>',
    document_id = '<DOCUMENT_ID>',
    transaction_id = tx['$id']
)
```
```ruby
### Read your own staged write
staged = vectors_db.get_document(
    database_id: '<DATABASE_ID>',
    collection_id: '<COLLECTION_ID>',
    document_id: '<DOCUMENT_ID>',
    transaction_id: tx['$id']
)
```
```csharp
// Read your own staged write
var staged = await vectorsDB.GetDocument(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    documentId: "<DOCUMENT_ID>",
    transactionId: tx.Id
);
```
```dart
// Read your own staged write
final staged = await vectorsDB.getDocument(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    documentId: '<DOCUMENT_ID>',
    transactionId: tx.$id
);
```
```kotlin
// Read your own staged write
val staged = vectorsDB.getDocument(
    databaseId = "<DATABASE_ID>",
    collectionId = "<COLLECTION_ID>",
    documentId = "<DOCUMENT_ID>",
    transactionId = tx.id
)
```
```java
// Read your own staged write (asynchronous)
vectorsDB.getDocument(
    "<DATABASE_ID>",
    "<COLLECTION_ID>",
    "<DOCUMENT_ID>",
    listOf(),
    "<TRANSACTION_ID>",
    new CoroutineCallback<>((document, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        System.out.println(document);
    })
);
```
```swift
// Read your own staged write
let staged = try await vectorsDB.getDocument(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    documentId: "<DOCUMENT_ID>",
    transactionId: tx.id
)
```
```server-rust
use appwrite::Client;
use appwrite::services::VectorsDB;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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

    let vectors_db = VectorsDB::new(&client);

    let tx = vectors_db.create_transaction(None).await?;

    // Read your own staged write
    let staged = vectors_db.get_document(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
        "<DOCUMENT_ID>",
        None,
        Some(&tx.id),
    ).await?;

    Ok(())
}
```
```bash
appwrite vectorsdb get-document \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --document-id <DOCUMENT_ID> \
    --transaction-id <TRANSACTION_ID>
```

#### Stage many with createOperations

Use the `createOperations` method to stage multiple operations across databases and collections in a single request. Provide an array of operation objects:

```json
[
  {
    "action": "create|update|upsert|delete",
    "databaseId": "<DATABASE_ID>",
    "collectionId": "<COLLECTION_ID>",
    "documentId": "<DOCUMENT_ID>",
    "data": {}
  }
]
```

Each action takes a different `data` shape.

##### Create and upsert
Pass the full document data: an `embeddings` vector of the collection's `dimension` and optional `metadata`.
```json
{ "embeddings": [0.12, -0.08, 0.45, 0.33], "metadata": { "title": "Hamlet" } }
```

##### Update
Pass only the fields to change. To replace the vector, pass a new `embeddings` array of the same length.
```json
{ "metadata": { "title": "Macbeth" } }
```

##### Delete
Pass the `documentId` and omit `data`.
```json
{ "action": "delete", "databaseId": "<DATABASE_ID>", "collectionId": "<COLLECTION_ID>", "documentId": "<DOCUMENT_ID>" }
```

```server-nodejs
// Stage multiple operations at once
await vectorsDB.createOperations({
    transactionId: tx.$id,
    operations: [
        {
            action: 'update',
            databaseId: '<DATABASE_ID>',
            collectionId: '<COLLECTION_ID>',
            documentId: '<DOCUMENT_ID_1>',
            data: { metadata: { title: 'Macbeth' } }
        },
        {
            action: 'delete',
            databaseId: '<DATABASE_ID>',
            collectionId: '<COLLECTION_ID>',
            documentId: '<DOCUMENT_ID_2>'
        }
    ]
});
```
```deno
// Stage multiple operations at once
await vectorsDB.createOperations({
    transactionId: tx.$id,
    operations: [
        {
            action: 'update',
            databaseId: '<DATABASE_ID>',
            collectionId: '<COLLECTION_ID>',
            documentId: '<DOCUMENT_ID_1>',
            data: { metadata: { title: 'Macbeth' } }
        },
        {
            action: 'delete',
            databaseId: '<DATABASE_ID>',
            collectionId: '<COLLECTION_ID>',
            documentId: '<DOCUMENT_ID_2>'
        }
    ]
});
```
```php
$vectorsDB->createOperations(
    transactionId: $tx['$id'],
    operations: [
        [
            'action' => 'update',
            'databaseId' => '<DATABASE_ID>',
            'collectionId' => '<COLLECTION_ID>',
            'documentId' => '<DOCUMENT_ID_1>',
            'data' => [ 'metadata' => [ 'title' => 'Macbeth' ] ]
        ],
        [
            'action' => 'delete',
            'databaseId' => '<DATABASE_ID>',
            'collectionId' => '<COLLECTION_ID>',
            'documentId' => '<DOCUMENT_ID_2>'
        ]
    ]
);
```
```python
vectors_db.create_operations(
    transaction_id = tx['$id'],
    operations = [
        {
            'action': 'update',
            'databaseId': '<DATABASE_ID>',
            'collectionId': '<COLLECTION_ID>',
            'documentId': '<DOCUMENT_ID_1>',
            'data': { 'metadata': { 'title': 'Macbeth' } }
        },
        {
            'action': 'delete',
            'databaseId': '<DATABASE_ID>',
            'collectionId': '<COLLECTION_ID>',
            'documentId': '<DOCUMENT_ID_2>'
        }
    ]
)
```
```ruby
vectors_db.create_operations(
    transaction_id: tx['$id'],
    operations: [
        {
            'action' => 'update',
            'databaseId' => '<DATABASE_ID>',
            'collectionId' => '<COLLECTION_ID>',
            'documentId' => '<DOCUMENT_ID_1>',
            'data' => { 'metadata' => { 'title' => 'Macbeth' } }
        },
        {
            'action' => 'delete',
            'databaseId' => '<DATABASE_ID>',
            'collectionId' => '<COLLECTION_ID>',
            'documentId' => '<DOCUMENT_ID_2>'
        }
    ]
)
```
```csharp
await vectorsDB.CreateOperations(
    transactionId: tx.Id,
    operations: new List<object>
    {
        new Dictionary<string, object>
        {
            ["action"] = "update",
            ["databaseId"] = "<DATABASE_ID>",
            ["collectionId"] = "<COLLECTION_ID>",
            ["documentId"] = "<DOCUMENT_ID_1>",
            ["data"] = new Dictionary<string, object> {
                ["metadata"] = new Dictionary<string, object> { ["title"] = "Macbeth" }
            }
        },
        new Dictionary<string, object>
        {
            ["action"] = "delete",
            ["databaseId"] = "<DATABASE_ID>",
            ["collectionId"] = "<COLLECTION_ID>",
            ["documentId"] = "<DOCUMENT_ID_2>"
        }
    }
);
```
```dart
await vectorsDB.createOperations(
    transactionId: tx.$id,
    operations: [
        {
            'action': 'update',
            'databaseId': '<DATABASE_ID>',
            'collectionId': '<COLLECTION_ID>',
            'documentId': '<DOCUMENT_ID_1>',
            'data': { 'metadata': { 'title': 'Macbeth' } }
        },
        {
            'action': 'delete',
            'databaseId': '<DATABASE_ID>',
            'collectionId': '<COLLECTION_ID>',
            'documentId': '<DOCUMENT_ID_2>'
        }
    ]
);
```
```kotlin
vectorsDB.createOperations(
    transactionId = tx.id,
    operations = listOf(
        mapOf(
            "action" to "update",
            "databaseId" to "<DATABASE_ID>",
            "collectionId" to "<COLLECTION_ID>",
            "documentId" to "<DOCUMENT_ID_1>",
            "data" to mapOf("metadata" to mapOf("title" to "Macbeth"))
        ),
        mapOf(
            "action" to "delete",
            "databaseId" to "<DATABASE_ID>",
            "collectionId" to "<COLLECTION_ID>",
            "documentId" to "<DOCUMENT_ID_2>"
        )
    )
)
```
```java
// Stage multiple operations at once (asynchronous)
List<Map<String, Object>> operations = Arrays.asList(
    Map.of(
        "action", "update",
        "databaseId", "<DATABASE_ID>",
        "collectionId", "<COLLECTION_ID>",
        "documentId", "<DOCUMENT_ID_1>",
        "data", Map.of("metadata", Map.of("title", "Macbeth"))
    ),
    Map.of(
        "action", "delete",
        "databaseId", "<DATABASE_ID>",
        "collectionId", "<COLLECTION_ID>",
        "documentId", "<DOCUMENT_ID_2>"
    )
);

vectorsDB.createOperations(
    "<TRANSACTION_ID>",
    operations,
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        System.out.println(result);
    })
);
```
```swift
try await vectorsDB.createOperations(
    transactionId: tx.id,
    operations: [
        [
            "action": "update",
            "databaseId": "<DATABASE_ID>",
            "collectionId": "<COLLECTION_ID>",
            "documentId": "<DOCUMENT_ID_1>",
            "data": ["metadata": ["title": "Macbeth"]]
        ],
        [
            "action": "delete",
            "databaseId": "<DATABASE_ID>",
            "collectionId": "<COLLECTION_ID>",
            "documentId": "<DOCUMENT_ID_2>"
        ]
    ]
)
```
```server-rust
use appwrite::Client;
use appwrite::services::VectorsDB;
use serde_json::json;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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

    let vectors_db = VectorsDB::new(&client);

    let tx = vectors_db.create_transaction(None).await?;

    // Stage multiple operations at once
    vectors_db.create_operations(
        &tx.id,
        Some(vec![
            json!({
                "action": "update",
                "databaseId": "<DATABASE_ID>",
                "collectionId": "<COLLECTION_ID>",
                "documentId": "<DOCUMENT_ID_1>",
                "data": { "metadata": { "title": "Macbeth" } }
            }),
            json!({
                "action": "delete",
                "databaseId": "<DATABASE_ID>",
                "collectionId": "<COLLECTION_ID>",
                "documentId": "<DOCUMENT_ID_2>"
            }),
        ]),
    ).await?;

    Ok(())
}
```

### Commit or roll back

When you are done staging operations, call the `updateTransaction` method to finalize the transaction. Set `commit` to `true` to apply the staged operations, or `rollback` to `true` to discard them. A transaction takes one or the other, so a second call on the same transaction fails with `transaction_not_ready`.

```server-nodejs
// Commit
await vectorsDB.updateTransaction({
    transactionId: tx.$id,
    commit: true
});

// Or roll back
await vectorsDB.updateTransaction({
    transactionId: tx.$id,
    rollback: true
});
```
```deno
// Commit
await vectorsDB.updateTransaction({
    transactionId: tx.$id,
    commit: true
});

// Or roll back
await vectorsDB.updateTransaction({
    transactionId: tx.$id,
    rollback: true
});
```
```php
// Commit
$vectorsDB->updateTransaction(
    transactionId: $tx['$id'],
    commit: true
);

// Or roll back
$vectorsDB->updateTransaction(
    transactionId: $tx['$id'],
    rollback: true
);
```
```python
### Commit
vectors_db.update_transaction(
    transaction_id = tx['$id'],
    commit = True
)

### Or roll back
vectors_db.update_transaction(
    transaction_id = tx['$id'],
    rollback = True
)
```
```ruby
### Commit
vectors_db.update_transaction(
    transaction_id: tx['$id'],
    commit: true
)

### Or roll back
vectors_db.update_transaction(
    transaction_id: tx['$id'],
    rollback: true
)
```
```csharp
// Commit
await vectorsDB.UpdateTransaction(
    transactionId: tx.Id,
    commit: true
);

// Or roll back
await vectorsDB.UpdateTransaction(
    transactionId: tx.Id,
    rollback: true
);
```
```dart
// Commit
await vectorsDB.updateTransaction(
    transactionId: tx.$id,
    commit: true
);

// Or roll back
await vectorsDB.updateTransaction(
    transactionId: tx.$id,
    rollback: true
);
```
```kotlin
// Commit
vectorsDB.updateTransaction(
    transactionId = tx.id,
    commit = true
)

// Or roll back
vectorsDB.updateTransaction(
    transactionId = tx.id,
    rollback = true
)
```
```java
// Commit (asynchronous)
vectorsDB.updateTransaction(
    "<TRANSACTION_ID>",
    true,
    false,
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        System.out.println(result);
    })
);

// Or roll back (asynchronous)
vectorsDB.updateTransaction(
    "<TRANSACTION_ID>",
    false,
    true,
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        System.out.println(result);
    })
);
```
```swift
// Commit
try await vectorsDB.updateTransaction(
    transactionId: tx.id,
    commit: true
)

// Or roll back
try await vectorsDB.updateTransaction(
    transactionId: tx.id,
    rollback: true
)
```
```server-rust
use appwrite::Client;
use appwrite::services::VectorsDB;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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

    let vectors_db = VectorsDB::new(&client);

    // Commit
    vectors_db.update_transaction(
        "<TRANSACTION_ID>",
        Some(true),
        None,
    ).await?;

    // Or roll back
    vectors_db.update_transaction(
        "<TRANSACTION_ID>",
        None,
        Some(true),
    ).await?;

    Ok(())
}
```
```bash
### Commit
appwrite vectorsdb update-transaction \
    --transaction-id <TRANSACTION_ID> \
    --commit true

### Or roll back
appwrite vectorsdb update-transaction \
    --transaction-id <TRANSACTION_ID> \
    --rollback true
```

### Inspect and delete transactions

Use `getTransaction` to check a transaction's status and operation count, `listTransactions` to list transactions across all databases, and `deleteTransaction` to discard a transaction without committing it.

```server-nodejs
// Get one transaction
const tx = await vectorsDB.getTransaction({ transactionId: '<TRANSACTION_ID>' });

// List transactions
const list = await vectorsDB.listTransactions();

// Delete a transaction
await vectorsDB.deleteTransaction({ transactionId: '<TRANSACTION_ID>' });
```
```deno
// Get one transaction
const tx = await vectorsDB.getTransaction({ transactionId: '<TRANSACTION_ID>' });

// List transactions
const list = await vectorsDB.listTransactions();

// Delete a transaction
await vectorsDB.deleteTransaction({ transactionId: '<TRANSACTION_ID>' });
```
```php
// Get one transaction
$tx = $vectorsDB->getTransaction(transactionId: '<TRANSACTION_ID>');

// List transactions
$list = $vectorsDB->listTransactions();

// Delete a transaction
$vectorsDB->deleteTransaction(transactionId: '<TRANSACTION_ID>');
```
```python
### Get one transaction
tx = vectors_db.get_transaction(transaction_id = '<TRANSACTION_ID>')

### List transactions
list = vectors_db.list_transactions()

### Delete a transaction
vectors_db.delete_transaction(transaction_id = '<TRANSACTION_ID>')
```
```ruby
### Get one transaction
tx = vectors_db.get_transaction(transaction_id: '<TRANSACTION_ID>')

### List transactions
list = vectors_db.list_transactions

### Delete a transaction
vectors_db.delete_transaction(transaction_id: '<TRANSACTION_ID>')
```
```csharp
// Get one transaction
var tx = await vectorsDB.GetTransaction(transactionId: "<TRANSACTION_ID>");

// List transactions
var list = await vectorsDB.ListTransactions();

// Delete a transaction
await vectorsDB.DeleteTransaction(transactionId: "<TRANSACTION_ID>");
```
```dart
// Get one transaction
final tx = await vectorsDB.getTransaction(transactionId: '<TRANSACTION_ID>');

// List transactions
final list = await vectorsDB.listTransactions();

// Delete a transaction
await vectorsDB.deleteTransaction(transactionId: '<TRANSACTION_ID>');
```
```kotlin
// Get one transaction
val tx = vectorsDB.getTransaction(transactionId = "<TRANSACTION_ID>")

// List transactions
val list = vectorsDB.listTransactions()

// Delete a transaction
vectorsDB.deleteTransaction(transactionId = "<TRANSACTION_ID>")
```
```java
// Get one transaction (asynchronous)
vectorsDB.getTransaction(
    "<TRANSACTION_ID>",
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        System.out.println(result);
    })
);

// List transactions (asynchronous)
vectorsDB.listTransactions(new CoroutineCallback<>((result, error) -> {
    if (error != null) {
        error.printStackTrace();
        return;
    }

    System.out.println(result);
}));

// Delete a transaction (asynchronous)
vectorsDB.deleteTransaction(
    "<TRANSACTION_ID>",
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        System.out.println(result);
    })
);
```
```swift
// Get one transaction
let tx = try await vectorsDB.getTransaction(transactionId: "<TRANSACTION_ID>")

// List transactions
let list = try await vectorsDB.listTransactions()

// Delete a transaction
try await vectorsDB.deleteTransaction(transactionId: "<TRANSACTION_ID>")
```
```server-rust
use appwrite::Client;
use appwrite::services::VectorsDB;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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

    let vectors_db = VectorsDB::new(&client);

    // Get one transaction
    let tx = vectors_db.get_transaction("<TRANSACTION_ID>").await?;

    // List transactions
    let list = vectors_db.list_transactions(None).await?;

    // Delete a transaction
    vectors_db.delete_transaction("<TRANSACTION_ID>").await?;

    Ok(())
}
```
```bash
### Get one transaction
appwrite vectorsdb get-transaction \
    --transaction-id <TRANSACTION_ID>

### List transactions
appwrite vectorsdb list-transactions

### Delete a transaction
appwrite vectorsdb delete-transaction \
    --transaction-id <TRANSACTION_ID>
```

### Handle conflicts

On commit, Appwrite verifies that documents 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.

**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 collections.

[Explore the API references](/docs/references)

---

## Vector search

URL: https://appwrite.io/docs/products/databases/vectorsdb/vector-search

Vector search finds the documents whose `embeddings` are closest to a query vector.
Instead of matching exact values, it ranks documents by similarity, so you can build features like semantic search, recommendations, and retrieval for AI applications.

There are two steps: create an [index](#create-an-index) on the `embeddings` field so searches are fast, then pass a vector query to `listDocuments` to get documents ranked by similarity.

### Create an index
Before you search, create an HNSW index on the `embeddings` field with `createIndex`. HNSW (Hierarchical Navigable Small World) is an approximate nearest neighbor index that keeps similarity search fast as your collection grows.

The index `type` decides how similarity is measured. Use the `VectorsDBIndexType` enum to pick one:

| Index type | Enum | Use when |
| --- | --- | --- |
| `hnsw_cosine` | `VectorsDBIndexType.HnswCosine` | You care about the direction of the vectors, not their magnitude. A common default for text embeddings. |
| `hnsw_dot` | `VectorsDBIndexType.HnswDot` | You want the dot product, which factors in both direction and magnitude. |
| `hnsw_euclidean` | `VectorsDBIndexType.HnswEuclidean` | You want the straight-line distance between vectors. |

Match the index type to the search query you plan to run. A `hnsw_cosine` index serves `Query.vectorCosine` searches, `hnsw_dot` serves `Query.vectorDot`, and `hnsw_euclidean` serves `Query.vectorEuclidean`.

```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 vectorsDB = new sdk.VectorsDB(client);

const result = await vectorsDB.createIndex({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    key: 'embeddings_index',
    type: sdk.VectorsDBIndexType.HnswCosine,
    attributes: ['embeddings']
});
```
```deno
import * as sdk from "npm: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 vectorsDB = new sdk.VectorsDB(client);

const result = await vectorsDB.createIndex({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    key: 'embeddings_index',
    type: sdk.VectorsDBIndexType.HnswCosine,
    attributes: ['embeddings']
});
```
```php
<?php

use Appwrite\Client;
use Appwrite\Services\VectorsDB;
use Appwrite\Enums\VectorsDBIndexType;

$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

$vectorsDB = new VectorsDB($client);

$result = $vectorsDB->createIndex(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    key: 'embeddings_index',
    type: VectorsDBIndexType::HNSWCOSINE(),
    attributes: ['embeddings']
);
```
```python
from appwrite.client import Client
from appwrite.services.vectors_db import VectorsDB
from appwrite.enums import VectorsDBIndexType

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

vectors_db = VectorsDB(client)

result = vectors_db.create_index(
    database_id = '<DATABASE_ID>',
    collection_id = '<COLLECTION_ID>',
    key = 'embeddings_index',
    type = VectorsDBIndexType.HNSW_COSINE,
    attributes = ['embeddings']
)
```
```ruby
require 'appwrite'

include Appwrite
include Appwrite::Enums

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

vectors_db = VectorsDB.new(client)

result = vectors_db.create_index(
    database_id: '<DATABASE_ID>',
    collection_id: '<COLLECTION_ID>',
    key: 'embeddings_index',
    type: VectorsDBIndexType::HNSW_COSINE,
    attributes: ['embeddings']
)
```
```csharp
using Appwrite;
using Appwrite.Enums;
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

VectorsDB vectorsDB = new VectorsDB(client);

Index result = await vectorsDB.CreateIndex(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    key: "embeddings_index",
    type: VectorsDBIndexType.HnswCosine,
    attributes: new List<string> { "embeddings" }
);
```
```dart
import 'package:dart_appwrite/dart_appwrite.dart';
import 'package:dart_appwrite/enums.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

VectorsDB vectorsDB = VectorsDB(client);

Index result = await vectorsDB.createIndex(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    key: 'embeddings_index',
    type: VectorsDBIndexType.hnswCosine,
    attributes: ['embeddings'],
);
```
```kotlin
import io.appwrite.Client
import io.appwrite.enums.VectorsDBIndexType
import io.appwrite.services.VectorsDB

val client = Client(context)
    .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 vectorsDB = VectorsDB(client)

val result = vectorsDB.createIndex(
    databaseId = "<DATABASE_ID>",
    collectionId = "<COLLECTION_ID>",
    key = "embeddings_index",
    type = VectorsDBIndexType.HNSW_COSINE,
    attributes = listOf("embeddings"),
)
```
```java
import io.appwrite.Client;
import io.appwrite.enums.VectorsDBIndexType;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.VectorsDB;
import java.util.List;

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

VectorsDB vectorsDB = new VectorsDB(client);

vectorsDB.createIndex(
    "<DATABASE_ID>",
    "<COLLECTION_ID>",
    "embeddings_index",
    VectorsDBIndexType.HNSW_COSINE,
    List.of("embeddings"),
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        System.out.println(result);
    })
);
```
```swift
import Appwrite
import AppwriteEnums

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 vectorsDB = VectorsDB(client)

let index = try await vectorsDB.createIndex(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    key: "embeddings_index",
    type: .hnswCosine,
    attributes: ["embeddings"]
)
```
```server-rust
use appwrite::Client;
use appwrite::services::VectorsDB;
use appwrite::enums::VectorsDBIndexType;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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

    let vectors_db = VectorsDB::new(&client);

    let result = vectors_db.create_index(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
        "embeddings_index",
        VectorsDBIndexType::HnswCosine,
        vec!["embeddings"],
        None, // orders (optional)
        None, // lengths (optional)
    ).await?;

    println!("{:?}", result);
    Ok(())
}
```
```bash
appwrite vectorsdb create-index \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --key 'embeddings_index' \
    --type 'hnsw_cosine' \
    --attributes 'embeddings'
```

The index is built in the background. New documents are added to the index as you create them, so you can keep writing while it builds.

### Run a similarity search
To search, pass a vector query to `listDocuments`. Build the query with one of the `Query` vector methods, passing the field name `embeddings` and the query vector. The response returns documents ranked from most to least similar.

| Query method | Use with index type |
| --- | --- |
| `Query.vectorCosine('embeddings', vector)` | `hnsw_cosine` |
| `Query.vectorDot('embeddings', vector)` | `hnsw_dot` |
| `Query.vectorEuclidean('embeddings', vector)` | `hnsw_euclidean` |

The query vector must have the same `dimension` as the collection. You can combine the vector query with other queries, such as `Query.limit()` to cap how many results you get back.

```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 vectorsDB = new sdk.VectorsDB(client);

const result = await vectorsDB.listDocuments({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    queries: [
        sdk.Query.vectorCosine('embeddings', [0.11, 0.21, 0.30, 0.40]),
        sdk.Query.limit(3)
    ]
});
```
```deno
import * as sdk from "npm: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 vectorsDB = new sdk.VectorsDB(client);

const result = await vectorsDB.listDocuments({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    queries: [
        sdk.Query.vectorCosine('embeddings', [0.11, 0.21, 0.30, 0.40]),
        sdk.Query.limit(3)
    ]
});
```
```php
<?php

use Appwrite\Client;
use Appwrite\Services\VectorsDB;
use Appwrite\Query;

$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

$vectorsDB = new VectorsDB($client);

$result = $vectorsDB->listDocuments(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    queries: [
        Query::vectorCosine('embeddings', [0.11, 0.21, 0.30, 0.40]),
        Query::limit(3)
    ]
);
```
```python
from appwrite.client import Client
from appwrite.services.vectors_db import VectorsDB
from appwrite.query import Query

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

vectors_db = VectorsDB(client)

result = vectors_db.list_documents(
    database_id = '<DATABASE_ID>',
    collection_id = '<COLLECTION_ID>',
    queries = [
        Query.vector_cosine('embeddings', [0.11, 0.21, 0.30, 0.40]),
        Query.limit(3)
    ]
)
```
```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

vectors_db = VectorsDB.new(client)

result = vectors_db.list_documents(
    database_id: '<DATABASE_ID>',
    collection_id: '<COLLECTION_ID>',
    queries: [
        Query.vector_cosine('embeddings', [0.11, 0.21, 0.30, 0.40]),
        Query.limit(3)
    ]
)
```
```csharp
using Appwrite;
using Appwrite.Models;
using Appwrite.Services;
using Appwrite.Queries;

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

VectorsDB vectorsDB = new VectorsDB(client);

DocumentList result = await vectorsDB.ListDocuments(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    queries: new List<string> {
        Query.VectorCosine("embeddings", new List<double> { 0.11, 0.21, 0.30, 0.40 }),
        Query.Limit(3)
    }
);
```
```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

VectorsDB vectorsDB = VectorsDB(client);

DocumentList result = await vectorsDB.listDocuments(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    queries: [
        Query.vectorCosine('embeddings', [0.11, 0.21, 0.30, 0.40]),
        Query.limit(3)
    ],
);
```
```kotlin
import io.appwrite.Client
import io.appwrite.Query
import io.appwrite.services.VectorsDB

val client = Client(context)
    .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 vectorsDB = VectorsDB(client)

val result = vectorsDB.listDocuments(
    databaseId = "<DATABASE_ID>",
    collectionId = "<COLLECTION_ID>",
    queries = listOf(
        Query.vectorCosine("embeddings", listOf(0.11, 0.21, 0.30, 0.40)),
        Query.limit(3)
    ),
)
```
```java
import io.appwrite.Client;
import io.appwrite.Query;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.VectorsDB;
import java.util.List;

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

VectorsDB vectorsDB = new VectorsDB(client);

vectorsDB.listDocuments(
    "<DATABASE_ID>",
    "<COLLECTION_ID>",
    List.of(
        Query.vectorCosine("embeddings", List.of(0.11, 0.21, 0.30, 0.40)),
        Query.limit(3)
    ),
    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("<YOUR_PROJECT_ID>") // Your project ID
    .setKey("<YOUR_API_KEY>") // Your secret API key

let vectorsDB = VectorsDB(client)

let result = try await vectorsDB.listDocuments(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    queries: [
        Query.vectorCosine("embeddings", vector: [0.11, 0.21, 0.30, 0.40]),
        Query.limit(3)
    ]
)
```
```server-rust
use appwrite::Client;
use appwrite::services::VectorsDB;
use appwrite::query::Query;
use serde_json::json;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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

    let vectors_db = VectorsDB::new(&client);

    let result = vectors_db.list_documents(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
        Some(vec![
            Query::vector_cosine("embeddings", json!([0.11, 0.21, 0.30, 0.40])).to_string(),
            Query::limit(3).to_string(),
        ]),
        None, // transaction_id
        None, // total
        None, // ttl
    ).await?;

    println!("{:?}", result);
    Ok(())
}
```
```bash
appwrite vectorsdb list-documents \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"vectorCosine","attribute":"embeddings","values":[[0.11,0.21,0.30,0.40]]}' \
    --queries '{"method":"limit","values":[3]}'
```

To rank by dot product or Euclidean distance instead, swap `vectorCosine` for `vectorDot` or `vectorEuclidean`, and make sure your index uses the matching type.

#### Read the distance
A vector query adds a `$distance` field to every document it returns, and the results come back sorted by it, closest first. Documents returned without a vector query have no `$distance`.

```json
{
    "$id": "6a86ebc8002fb1efb979",
    "$distance": 0.1308663759554639,
    "embeddings": [0.12, 0.84, 0.33, 0.57],
    "metadata": { "title": "Getting started with Appwrite" }
}
```

The scale depends on the query method, so compare `$distance` values only within one set of results:

| Query method | What `$distance` measures | Closest value |
| --- | --- | --- |
| `Query.vectorCosine` | Cosine distance | `0` |
| `Query.vectorDot` | Negative inner product | Most negative |
| `Query.vectorEuclidean` | Straight-line distance | `0` |

Use `$distance` to drop weak matches, for example by keeping only the results below a cutoff you pick from your own data.

**One vector query per request**

A single call accepts at most one vector query. You can still add non-vector queries such as `Query.limit()` to the same request. Passing two vector queries fails with `Cannot use multiple vector queries in a single request`.

### Send queries in the request body
`listDocuments` puts your queries in the URL, so a long query vector makes for a long URL. A 768-dimension vector serializes to roughly 9 KB of query string and a 2,000-dimension vector to roughly 40 KB, both of which the server accepts. Past a few thousand dimensions the URL grows beyond what the server will read and the request fails with a `400`.

`createQuery` takes the same queries in the request body instead, so the size of the query vector no longer matters. It returns the same document list as `listDocuments`, and it accepts the same `transactionId` and `ttl` options.

```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 vectorsDB = new sdk.VectorsDB(client);

const result = await vectorsDB.createQuery({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    queries: [
        sdk.Query.vectorCosine('embeddings', [0.11, 0.21, 0.30, 0.40]),
        sdk.Query.limit(3)
    ]
});
```
```deno
import * as sdk from "npm: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 vectorsDB = new sdk.VectorsDB(client);

const result = await vectorsDB.createQuery({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    queries: [
        sdk.Query.vectorCosine('embeddings', [0.11, 0.21, 0.30, 0.40]),
        sdk.Query.limit(3)
    ]
});
```
```php
<?php

use Appwrite\Client;
use Appwrite\Services\VectorsDB;
use Appwrite\Query;

$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

$vectorsDB = new VectorsDB($client);

$result = $vectorsDB->createQuery(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    queries: [
        Query::vectorCosine('embeddings', [0.11, 0.21, 0.30, 0.40]),
        Query::limit(3)
    ]
);
```
```python
from appwrite.client import Client
from appwrite.services.vectors_db import VectorsDB
from appwrite.query import Query

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

vectors_db = VectorsDB(client)

result = vectors_db.create_query(
    database_id = '<DATABASE_ID>',
    collection_id = '<COLLECTION_ID>',
    queries = [
        Query.vector_cosine('embeddings', [0.11, 0.21, 0.30, 0.40]),
        Query.limit(3)
    ]
)
```
```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

vectors_db = VectorsDB.new(client)

result = vectors_db.create_query(
    database_id: '<DATABASE_ID>',
    collection_id: '<COLLECTION_ID>',
    queries: [
        Query.vector_cosine('embeddings', [0.11, 0.21, 0.30, 0.40]),
        Query.limit(3)
    ]
)
```
```csharp
using Appwrite;
using Appwrite.Models;
using Appwrite.Services;
using Appwrite.Queries;

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

VectorsDB vectorsDB = new VectorsDB(client);

DocumentList result = await vectorsDB.CreateQuery(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    queries: new List<string> {
        Query.VectorCosine("embeddings", new List<double> { 0.11, 0.21, 0.30, 0.40 }),
        Query.Limit(3)
    }
);
```
```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

VectorsDB vectorsDB = VectorsDB(client);

DocumentList result = await vectorsDB.createQuery(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    queries: [
        Query.vectorCosine('embeddings', [0.11, 0.21, 0.30, 0.40]),
        Query.limit(3)
    ],
);
```
```kotlin
import io.appwrite.Client
import io.appwrite.Query
import io.appwrite.services.VectorsDB

val client = Client(context)
    .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 vectorsDB = VectorsDB(client)

val result = vectorsDB.createQuery(
    databaseId = "<DATABASE_ID>",
    collectionId = "<COLLECTION_ID>",
    queries = listOf(
        Query.vectorCosine("embeddings", listOf(0.11, 0.21, 0.30, 0.40)),
        Query.limit(3)
    ),
)
```
```java
import io.appwrite.Client;
import io.appwrite.Query;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.VectorsDB;
import java.util.List;

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

VectorsDB vectorsDB = new VectorsDB(client);

vectorsDB.createQuery(
    "<DATABASE_ID>",
    "<COLLECTION_ID>",
    List.of(
        Query.vectorCosine("embeddings", List.of(0.11, 0.21, 0.30, 0.40)),
        Query.limit(3)
    ),
    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("<YOUR_PROJECT_ID>") // Your project ID
    .setKey("<YOUR_API_KEY>") // Your secret API key

let vectorsDB = VectorsDB(client)

let result = try await vectorsDB.createQuery(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    queries: [
        Query.vectorCosine("embeddings", vector: [0.11, 0.21, 0.30, 0.40]),
        Query.limit(3)
    ]
)
```
```server-rust
use appwrite::Client;
use appwrite::services::VectorsDB;
use appwrite::query::Query;
use serde_json::json;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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

    let vectors_db = VectorsDB::new(&client);

    let result = vectors_db.create_query(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
        Some(vec![
            Query::vector_cosine("embeddings", json!([0.11, 0.21, 0.30, 0.40])).to_string(),
            Query::limit(3).to_string(),
        ]),
        None, // transaction_id
        None, // total
        None, // ttl
    ).await?;

    println!("{:?}", result);
    Ok(())
}
```
```bash
appwrite vectorsdb create-query \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --queries '{"method":"vectorCosine","attribute":"embeddings","values":[[0.11,0.21,0.30,0.40]]}' \
    --queries '{"method":"limit","values":[3]}'
```

Use `createQuery` when you store high-dimension vectors or send many queries at once. For the dimensions the built-in [embedding models](/docs/products/databases/vectorsdb/embeddings) produce, either method works.

### Manage indexes
A collection starts with an `object` index on `metadata`, and `listIndexes` returns it alongside any index you create. Use these methods to see which indexes a collection has, check whether one has finished building, and remove indexes you no longer query against.

#### List indexes
Each entry has a `key`, a `type`, the attributes it covers, and a `status`: `available`, `processing`, `deleting`, `stuck`, or `failed`.

```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 vectorsDB = new sdk.VectorsDB(client);

const result = await vectorsDB.listIndexes({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>'
});
```
```deno
import * as sdk from "npm: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 vectorsDB = new sdk.VectorsDB(client);

const result = await vectorsDB.listIndexes({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>'
});
```
```php
<?php

use Appwrite\Client;
use Appwrite\Services\VectorsDB;

$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

$vectorsDB = new VectorsDB($client);

$result = $vectorsDB->listIndexes(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>'
);
```
```python
from appwrite.client import Client
from appwrite.services.vectors_db import VectorsDB

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

vectors_db = VectorsDB(client)

result = vectors_db.list_indexes(
    database_id = '<DATABASE_ID>',
    collection_id = '<COLLECTION_ID>'
)
```
```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

vectors_db = VectorsDB.new(client)

result = vectors_db.list_indexes(
    database_id: '<DATABASE_ID>',
    collection_id: '<COLLECTION_ID>'
)
```
```csharp
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

VectorsDB vectorsDB = new VectorsDB(client);

IndexList result = await vectorsDB.ListIndexes(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>"
);
```
```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

VectorsDB vectorsDB = VectorsDB(client);

IndexList result = await vectorsDB.listIndexes(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
);
```
```kotlin
import io.appwrite.Client
import io.appwrite.services.VectorsDB

val client = Client(context)
    .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 vectorsDB = VectorsDB(client)

val response = vectorsDB.listIndexes(
    databaseId = "<DATABASE_ID>",
    collectionId = "<COLLECTION_ID>"
)
```
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.VectorsDB;

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

VectorsDB vectorsDB = new VectorsDB(client);

vectorsDB.listIndexes(
    "<DATABASE_ID>", // databaseId
    "<COLLECTION_ID>", // collectionId
    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("<YOUR_PROJECT_ID>") // Your project ID
    .setKey("<YOUR_API_KEY>") // Your secret API key

let vectorsDB = VectorsDB(client)

let indexList = try await vectorsDB.listIndexes(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>"
)
```
```server-rust
use appwrite::Client;
use appwrite::services::VectorsDB;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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

    let vectors_db = VectorsDB::new(&client);

    let result = vectors_db.list_indexes(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
        None, // queries (optional)
        None, // total (optional)
    ).await?;

    println!("{:?}", result);
    Ok(())
}
```
```bash
appwrite vectorsdb list-indexes \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID>
```

#### Get index
Read a single index by its key. The `status` field tells you whether the index is ready, and `error` holds the reason when creating or deleting an index fails.

```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 vectorsDB = new sdk.VectorsDB(client);

const result = await vectorsDB.getIndex({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    key: 'embeddings_index'
});
```
```deno
import * as sdk from "npm: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 vectorsDB = new sdk.VectorsDB(client);

const result = await vectorsDB.getIndex({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    key: 'embeddings_index'
});
```
```php
<?php

use Appwrite\Client;
use Appwrite\Services\VectorsDB;

$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

$vectorsDB = new VectorsDB($client);

$result = $vectorsDB->getIndex(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    key: 'embeddings_index'
);
```
```python
from appwrite.client import Client
from appwrite.services.vectors_db import VectorsDB

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

vectors_db = VectorsDB(client)

result = vectors_db.get_index(
    database_id = '<DATABASE_ID>',
    collection_id = '<COLLECTION_ID>',
    key = 'embeddings_index'
)
```
```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

vectors_db = VectorsDB.new(client)

result = vectors_db.get_index(
    database_id: '<DATABASE_ID>',
    collection_id: '<COLLECTION_ID>',
    key: 'embeddings_index'
)
```
```csharp
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

VectorsDB vectorsDB = new VectorsDB(client);

Index result = await vectorsDB.GetIndex(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    key: "embeddings_index"
);
```
```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

VectorsDB vectorsDB = VectorsDB(client);

Index result = await vectorsDB.getIndex(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    key: 'embeddings_index',
);
```
```kotlin
import io.appwrite.Client
import io.appwrite.services.VectorsDB

val client = Client(context)
    .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 vectorsDB = VectorsDB(client)

val response = vectorsDB.getIndex(
    databaseId = "<DATABASE_ID>",
    collectionId = "<COLLECTION_ID>",
    key = "embeddings_index"
)
```
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.VectorsDB;

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

VectorsDB vectorsDB = new VectorsDB(client);

vectorsDB.getIndex(
    "<DATABASE_ID>", // databaseId
    "<COLLECTION_ID>", // collectionId
    "embeddings_index", // key
    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("<YOUR_PROJECT_ID>") // Your project ID
    .setKey("<YOUR_API_KEY>") // Your secret API key

let vectorsDB = VectorsDB(client)

let index = try await vectorsDB.getIndex(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    key: "embeddings_index"
)
```
```server-rust
use appwrite::Client;
use appwrite::services::VectorsDB;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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

    let vectors_db = VectorsDB::new(&client);

    let result = vectors_db.get_index(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
        "embeddings_index",
    ).await?;

    println!("{:?}", result);
    Ok(())
}
```
```bash
appwrite vectorsdb get-index \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --key 'embeddings_index'
```

#### Delete index
Deleting an index leaves your documents untouched. Searches that relied on it fall back to a slower scan, so replace an index before you drop it if the collection is serving traffic.

```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 vectorsDB = new sdk.VectorsDB(client);

const result = await vectorsDB.deleteIndex({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    key: 'embeddings_index'
});
```
```deno
import * as sdk from "npm: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 vectorsDB = new sdk.VectorsDB(client);

const result = await vectorsDB.deleteIndex({
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    key: 'embeddings_index'
});
```
```php
<?php

use Appwrite\Client;
use Appwrite\Services\VectorsDB;

$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

$vectorsDB = new VectorsDB($client);

$result = $vectorsDB->deleteIndex(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    key: 'embeddings_index'
);
```
```python
from appwrite.client import Client
from appwrite.services.vectors_db import VectorsDB

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

vectors_db = VectorsDB(client)

result = vectors_db.delete_index(
    database_id = '<DATABASE_ID>',
    collection_id = '<COLLECTION_ID>',
    key = 'embeddings_index'
)
```
```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

vectors_db = VectorsDB.new(client)

result = vectors_db.delete_index(
    database_id: '<DATABASE_ID>',
    collection_id: '<COLLECTION_ID>',
    key: 'embeddings_index'
)
```
```csharp
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

VectorsDB vectorsDB = new VectorsDB(client);

await vectorsDB.DeleteIndex(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    key: "embeddings_index"
);
```
```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

VectorsDB vectorsDB = VectorsDB(client);

await vectorsDB.deleteIndex(
    databaseId: '<DATABASE_ID>',
    collectionId: '<COLLECTION_ID>',
    key: 'embeddings_index',
);
```
```kotlin
import io.appwrite.Client
import io.appwrite.services.VectorsDB

val client = Client(context)
    .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 vectorsDB = VectorsDB(client)

val response = vectorsDB.deleteIndex(
    databaseId = "<DATABASE_ID>",
    collectionId = "<COLLECTION_ID>",
    key = "embeddings_index"
)
```
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.VectorsDB;

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

VectorsDB vectorsDB = new VectorsDB(client);

vectorsDB.deleteIndex(
    "<DATABASE_ID>", // databaseId
    "<COLLECTION_ID>", // collectionId
    "embeddings_index", // key
    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("<YOUR_PROJECT_ID>") // Your project ID
    .setKey("<YOUR_API_KEY>") // Your secret API key

let vectorsDB = VectorsDB(client)

try await vectorsDB.deleteIndex(
    databaseId: "<DATABASE_ID>",
    collectionId: "<COLLECTION_ID>",
    key: "embeddings_index"
)
```
```server-rust
use appwrite::Client;
use appwrite::services::VectorsDB;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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

    let vectors_db = VectorsDB::new(&client);

    vectors_db.delete_index(
        "<DATABASE_ID>",
        "<COLLECTION_ID>",
        "embeddings_index",
    ).await?;

    Ok(())
}
```
```bash
appwrite vectorsdb delete-index \
    --database-id <DATABASE_ID> \
    --collection-id <COLLECTION_ID> \
    --key 'embeddings_index'
```

### Next steps
Vector search ranks documents by similarity. To filter those results by the data stored alongside each vector, combine search with metadata queries.

[Learn about querying metadata](/docs/products/databases/vectorsdb/queries)

---

## Domains

URL: https://appwrite.io/docs/products/domains

Appwrite **Domains** is organization-level domain management on Appwrite Cloud. You can register new names, transfer existing registrations, delegate DNS for domains you own elsewhere, and connect hostnames to [Sites](/docs/products/sites), [Functions](/docs/products/functions), and [custom API endpoints](/docs/advanced/platform/custom-domains).

Appwrite acts as your registrar for purchases and transfers. Billing, renewal, and registrant details are tied to your organization. You can also [change organization](/docs/products/domains/change-organization) to move a domain between orgs you control without transferring registration away from Appwrite.

### How it works

Domain setup on Appwrite has two layers:

1. **Organization domain** (the apex zone, for example `example.com`) proves your organization controls the name and can host its authoritative DNS zone under **Organization** > **Domains**.
2. **Product hostname** (for example `www.example.com`, `api.example.com`, or `hooks.example.com`) is added on a Site, Function, or project **Custom domains** settings. Appwrite verifies DNS, issues TLS, and routes HTTPS traffic through the [Appwrite Network](/docs/products/network).

You can register or transfer the apex through Appwrite, or [add an external domain](/docs/products/domains/external) and point nameservers to `ns1.appwrite.zone` and `ns2.appwrite.zone`. Subdomains and apex hostnames can also be verified with CNAME records at your current DNS provider. See [Connect to products](/docs/products/domains/connect) for apex vs subdomain patterns and multi-product layouts.

**Appwrite Cloud**

Domains is available on Appwrite Cloud. Self-hosted deployments do not include domain registration or organization-level domain management.

### Getting started

Register or add your first domain and verify DNS in a few steps. The quick start covers all three entry paths: buy, transfer in, or add external.

[Quick start](/docs/products/domains/quick-start)

### Concepts

Core ideas behind registration, billing, and DNS for organization domains.

- [Registration](/docs/products/domains/registration): How Appwrite registers domains, registrant contacts, and what happens after checkout.
- [Renewal](/docs/products/domains/renewal): Auto-renewal, expiration, and keeping domains active on your organization.
- [DNS records](/docs/products/domains/dns): Zones, record types, locked entries, and how organization DNS relates to product hostnames.
- [DNS presets](/docs/products/domains/presets): One-click MX and SPF record sets for Google Workspace, Outlook, Mailgun, and other email providers.
- [Pricing](/docs/products/domains/pricing): Registration, transfer, and renewal costs billed through your organization.

### Guides

Step-by-step guides for common domain tasks in the Console.

- [Register a domain](/docs/products/domains/register): Search availability, complete checkout, and open the new domain in your organization.
- [Transfer a domain](/docs/products/domains/transfer): Move registration into Appwrite from another registrar using an authorization code.
- [Add external domain](/docs/products/domains/external): Delegate DNS to Appwrite while keeping registration at your current registrar.
- [Manage DNS records](/docs/products/domains/manage-dns): Add, edit, import, and use presets for email and third-party services on organization domains.
- [Connect to products](/docs/products/domains/connect): Attach apex and subdomains to Sites, Functions, and project APIs with verification and TLS.
- [Change organization](/docs/products/domains/change-organization): Reassign a domain to another organization you own without a registrar transfer.
- [Delete a domain](/docs/products/domains/delete): Remove a domain from your organization and understand impact on DNS and product hostnames.

### Platform integration

Organization **Domains** manages the zone. Product docs cover routing behavior, generated URLs, and advanced DNS patterns shared across Appwrite.

- [Appwrite DNS](/docs/products/network/dns): Apex domains, CNAME flattening, and nameserver delegation across the platform.
- [Sites domains](/docs/products/sites/domains): Custom domains, branch URLs, and domain rule types for web apps.
- [Functions domains](/docs/products/functions/domains): Custom and generated domains for HTTP-triggered serverless functions.
- [Custom API domains](/docs/advanced/platform/custom-domains): Branded project API endpoints and first-party cookie configuration.

---

## Change organization

URL: https://appwrite.io/docs/products/domains/change-organization

You can reassign a domain to a different Appwrite organization from the domain **Settings** tab. The domain keeps its DNS zone and registration state. Only which organization owns the domain in the Console changes.

**Not a registrar transfer**

**Change organization** updates which Appwrite organization manages the domain. It does not move registration to or from Appwrite at the registry. For that, use [Transfer a domain](/docs/products/domains/transfer).

### Before you change organization

- You must be a member of both the source and destination organizations.
- The destination organization must have capacity under its [domain plan limit](/docs/products/domains/registration#plan-limits).
- [Product connections](/docs/products/domains/connect) (Sites, Functions, API custom domains) in the source organization may need to be updated if they reference the domain. Plan cutover before changing production traffic.

### Change organization

1. Open the domain in your organization.
2. Go to the **Settings** tab.
3. In **Change organization**, select the destination organization from the dropdown.
4. Click **Move** and confirm in the dialog.

After the change completes, the domain appears under **Domains** in the destination organization and is removed from the source organization's list.

### Access after reassignment

Organization members who were not already in the destination organization do not automatically gain access to the domain. Invite them to the destination organization if they need to manage DNS or settings.

Domain write access follows organization roles when [organization roles](/docs/advanced/platform/roles) are enabled (owners and developers with the domains write scope).

### Auto-renewal and registration

For Appwrite-registered domains, auto-renewal and billing are tied to the organization that owns the domain after reassignment. Ensure the destination organization has a valid payment method if auto-renewal is enabled. See [Renewal](/docs/products/domains/renewal).

External domains keep registration at the external registrar. Only DNS management in Appwrite follows the organization change.

---

## Connect to products

URL: https://appwrite.io/docs/products/domains/connect

Custom domains on Appwrite work in two layers:

1. **Organization domain** (apex zone, for example `example.com`) proves your organization controls the name and can host its DNS zone.
2. **Product domain** (any hostname, for example `www.example.com` or `api.example.com`) is a proxy rule that routes HTTPS traffic to a Site, Function, or project API.

This guide walks through apex vs subdomain setup, what to configure in each product, and how organization **Domains** fits together with project-level **Add domain** flows.

### Before you connect

#### Organization ownership

On Appwrite Cloud, the project's organization must own the **apex** domain before a hostname can be verified on Sites, Functions, or **Custom domains** in project settings.

When you add a hostname in a project, Appwrite automatically registers the apex in organization **Domains** if it is not there already. You still need to complete DNS verification for that apex (nameservers, CNAME, or A/AAAA depending on your setup).

If the apex is registered under a **different** organization, verification fails. Move the domain with [Change organization](/docs/products/domains/change-organization) or use a domain owned by the same org as the project.

#### Apex vs subdomain

| Hostname type | Example | Typical DNS method |
| ------------- | ------- | ------------------ |
| Apex (root) | `example.com` | NS delegation to Appwrite, or [CNAME flattening](/docs/products/network/dns#using-cname-flattening) (ALIAS/ANAME) at your DNS host |
| Subdomain | `www.example.com`, `api.example.com` | CNAME to Appwrite |

Apex domains cannot use a standard CNAME at the zone root (DNS RFC limits). Subdomains always use CNAME unless the full zone is delegated to Appwrite nameservers.

#### Two ways to manage DNS

**Full zone on Appwrite (recommended for apex + many subdomains)**

- Register, transfer, or [add an external domain](/docs/products/domains/external) under **Organization** > **Domains**.
- Point nameservers to `ns1.appwrite.zone` and `ns2.appwrite.zone`.
- Manage records on the domain **Records** tab. See [Manage DNS records](/docs/products/domains/manage-dns).
- Add product hostnames in Sites, Functions, or project settings. Verification can complete faster when Appwrite already hosts the zone.

**DNS stays at your current provider**

- Keep nameservers at your registrar or DNS host.
- Add the CNAME (and [CAA](/docs/products/network/caa-records) when shown) that the Console provides for each product hostname.
- For apex without changing nameservers, use CNAME flattening if your provider supports it. See [Sites domains](/docs/products/sites/domains#add-an-apex-domain-without-changing-nameservers).

**CNAME targets differ by product**

- **Sites and Functions** (edge routing): CNAME to `appwrite.network`.
- **Project API** (custom endpoint): CNAME to the project endpoint host shown in the Console (not `appwrite.network`).

The Console always shows the exact records for your hostname. Copy them from the verification step rather than guessing.

### Connect a subdomain

Subdomains such as `www`, `api`, `app`, or `staging` follow the same pattern for every product:

1. Open the target resource (Site, Function, or project **Settings** > **Custom domains**).
2. Click **Add domain** (or **Create domain**) and enter the full subdomain (for example `api.example.com`).
3. Complete any product-specific options (Sites: [domain rule type](#sites); Functions and API: route to the active deployment or API).
4. Copy the **CNAME** record (and **CAA** if listed) from the verification screen.
5. Add those records at your DNS provider, or on the organization domain **Records** tab if the zone is on Appwrite.
6. Return to the Console and click **Verify**. Wait until **Verification status** and **Certificate status** are both successful.

DNS propagation can take up to 48 hours. Retry verification after records are live.

### Connect an apex domain

For `example.com` (no subdomain prefix), choose one approach:

##### Nameserver delegation

1. Verify the apex in **Organization** > **Domains** (register, transfer, or [external](/docs/products/domains/external)).
2. Add the product hostname `example.com` on the Site, Function, or API as in the subdomain flow.
3. If the zone is not already on Appwrite nameservers, set NS records at your registrar to `ns1.appwrite.zone` and `ns2.appwrite.zone`.
4. Recreate non-Appwrite records (MX for email, TXT for verification, and so on) on the organization **Records** tab before cutting over production traffic.

See [Sites domains](/docs/products/sites/domains#add-an-apex-domain-with-ns-records) for why Appwrite uses NS delegation instead of fixed A records on apex.

##### CNAME flattening (keep your nameservers)

1. Add `example.com` on the product.
2. Create an ALIAS, ANAME, or apex CNAME (provider-dependent) pointing to the Appwrite hostname from the Console.
3. Add the [CAA record](/docs/products/network/caa-records) at the apex if prompted. Keep existing CAA records and add Appwrite's alongside them.
4. Verify in the Console.

Supported on many providers (for example Cloudflare, DNSimple, Route 53). If your provider does not support flattening, use nameserver delegation instead.

### Sites

Serve your web app on a branded hostname.

#### Steps

1. Open **Sites** and select your site.
2. Open the **Domains** tab and click **Add domain**.
3. Enter the hostname (`www.example.com`, `example.com`, or `staging.example.com`).
4. Choose a [domain rule type](/docs/products/sites/domains#domain-rule-types):
   - **Active deployment**: production; always serves the latest successful deployment.
   - **Git branch**: staging or preview; pins to a branch from your connected repository.
   - **Redirect**: HTTP redirect to another URL (301, 302, and other status codes).
5. Follow the verification screen (CNAME, nameservers, or flattening for apex).
6. Click **Verify** and wait for the TLS certificate to issue.

#### Common hostnames

| Hostname | Typical use |
| -------- | ----------- |
| `www.example.com` | Public marketing site or app (subdomain + CNAME) |
| `example.com` | Apex site (NS or CNAME flattening) |
| `staging.example.com` | Branch-pinned preview (Git branch rule) |

Appwrite also provides generated `*.appwrite.network` URLs and branch/commit URLs without custom DNS. See [Sites domains](/docs/products/sites/domains).

### Functions

Expose a function over HTTPS with standard HTTP methods (GET, POST, and others).

#### Steps

1. Open **Functions** and select your function.
2. Open the **Domains** tab and click **Add domain** (or **Create domain**).
3. Enter the hostname (for example `api.example.com` or `hooks.example.com`).
4. Add the CNAME (and CAA if shown) at your DNS provider or on the organization **Records** tab.
5. Verify in the Console.

#### Generated and edge domains

Every function gets a region-specific `*.appwrite.run` URL after deployment. You can also attach an `*.appwrite.network` edge domain from the **Domains** tab for routing to the nearest region. Custom domains use the same verification flow as Sites (CNAME to `appwrite.network` on Cloud).

See [Functions domains](/docs/products/functions/domains) for generated domains and edge setup.

### Project API

Use your own hostname as the Appwrite API endpoint for a project (for example `appwrite.example.com` or `api.example.com`).

#### Why use a custom API domain

Browsers treat cookies from a different hostname than your app as third-party cookies and may block them. Pointing the API to a subdomain of your app (for example app on `www.example.com`, API on `api.example.com`) keeps sessions on first-party cookies. See [Custom domains](/docs/advanced/platform/custom-domains#third-party-cookies).

#### Steps

1. Open the project **Settings** sidebar.
2. Open **Custom domains** and click **Create domain**.
3. Enter the subdomain (for example `api.example.com`). Apex API domains follow the same [apex](#connect-an-apex-domain) options as Sites.
4. Copy the **CNAME** and any **CAA** records to your DNS provider.
5. Verify and wait for the certificate.
6. Update your SDK client to use the new endpoint URL.
7. Add the hostname under **Allowed domains** in project settings if API calls from that origin are blocked.

See [Custom domains](/docs/advanced/platform/custom-domains) for provider-specific DNS links and allowed-domain configuration.

### Example: one domain, multiple products

A typical production layout on `example.com`:

| Hostname | Product | Purpose |
| -------- | ------- | ------- |
| `example.com` or `www.example.com` | Site (active deployment) | User-facing web app |
| `api.example.com` | Project custom domain | Appwrite API / auth cookies |
| `hooks.example.com` | Function | Webhooks or serverless API |
| `staging.example.com` | Site (Git branch) | Staging environment |

With the full zone on Appwrite nameservers, add CNAME records on the organization **Records** tab for each subdomain pointing to `appwrite.network` (Sites/Functions) or the API CNAME target from project settings. Then add each hostname on the corresponding product and verify.

### Verification and TLS

After DNS is correct:

1. **Verification status** must show verified. Click **Verify** or **Retry** in the Console if it stays pending.
2. **Certificate status** must show issued. Appwrite provisions and renews TLS automatically.
3. A **Verifying** state while the certificate issues is normal and usually completes in minutes.

Locked records on organization **Domains** (created when you attach hostnames) must not be deleted. They are required for routing and certificate renewal.

All connected hostnames use Appwrite [Network](/docs/products/network) features (routing, DDoS mitigation, [TLS](/docs/products/network/tls)). Protect API, Functions, and Sites traffic with [Firewall](/docs/products/firewall).

### Troubleshooting

**Verification fails after adding CNAME**

- Confirm the record name matches the Console (`www` vs `@` for apex flattening).
- Use the CNAME target shown for that product (API vs `appwrite.network`).
- Wait for propagation and use an external DNS checker before retrying.

**Apex works at registrar but not on Appwrite**

- Switch to the **Nameservers** tab on the verification screen if CNAME flattening is not supported.
- Ensure NS records point only to Appwrite when delegating the full zone.

**Domain owned by another organization**

- The apex must belong to the same organization as the project. Use [Change organization](/docs/products/domains/change-organization) or a different domain.

**Site shows old content after verification**

- DNS and browser caches can lag. Test in a private window or from another network.

**Email stopped working after NS cutover**

- Recreate MX and related records on the organization **Records** tab. Use [DNS presets](/docs/products/domains/presets) for common providers or see [Manage DNS records](/docs/products/domains/manage-dns#configure-email).

For product-specific options (branch URLs, redirect rules, function HTTP behavior), see [Sites domains](/docs/products/sites/domains), [Functions domains](/docs/products/functions/domains), and [Custom domains](/docs/advanced/platform/custom-domains).

---

## Delete a domain

URL: https://appwrite.io/docs/products/domains/delete

Deleting a domain removes it from your organization and deletes all DNS records Appwrite hosted for that zone. This action cannot be undone.

This guide covers single and bulk delete and what happens for Appwrite-registered versus external domains.

### Before you delete

- Update or remove [product connections](/docs/products/domains/connect) (Sites, Functions, API custom domains) that use the hostname.
- For **external domains**, plan nameserver changes at your registrar if DNS should continue elsewhere. Deleting in Appwrite does not cancel registration at your external registrar.
- For **Appwrite-registered domains**, deleting from Appwrite does not cancel registry registration by itself. To release the name entirely, complete [transfer out](/docs/products/domains/transfer#transfer-out) or allow the registration to expire without renewal.

### Delete a single domain

1. Open the domain in your organization.
2. Go to the **Settings** tab.
3. In **Delete domain**, click **Delete domain**.
4. Type the domain name exactly as shown to confirm.
5. Confirm deletion.

The domain and its DNS zone are removed from Appwrite immediately.

### Bulk delete from the list

1. Open **Organization** > **Domains**.
2. Select the checkboxes on the domains to remove.
3. Use the bulk delete action in the selection bar.
4. Confirm deletion in the dialog.

Bulk delete uses the same permanent removal as single delete for each selected domain.

### After deletion

- DNS hosted by Appwrite for that zone stops resolving.
- The domain no longer counts toward your organization's [plan limit](/docs/products/domains/registration#plan-limits).
- Appwrite-registered names may still incur renewal obligations at the registry until transfer out or expiry if registration was not separately cancelled. Monitor billing if you delete shortly after registering.

To add the domain again later, use [Register a domain](/docs/products/domains/register), [Transfer a domain](/docs/products/domains/transfer), or [Add external domain](/docs/products/domains/external) depending on how you want to manage registration.

---

## DNS records

URL: https://appwrite.io/docs/products/domains/dns

When a domain is verified with Appwrite nameservers, Appwrite hosts the authoritative DNS zone for that domain. The zone contains all records for that domain and is managed under **Organization** > **Domains**.

This applies to [Appwrite-registered](/docs/products/domains/registration) domains and [external domains](/docs/products/domains/external) alike.

For platform-wide DNS behavior (apex domains, CNAME flattening, TLS), see [Appwrite DNS service](/docs/products/network/dns).

### Zones and verification

A verified domain uses Appwrite nameservers (`ns1.appwrite.zone` and `ns2.appwrite.zone`). The **Records** tab on the domain shows the full zone Appwrite serves.

Until verification completes, treat DNS changes in Appwrite as preparatory only. Product connections that require a verified zone should wait until status shows **Verified**.

### Record types

Appwrite supports the following record types on organization domains:

| Type | Purpose |
|------|---------|
| A | IPv4 address |
| AAAA | IPv6 address |
| CNAME | Alias to another hostname |
| MX | Mail servers |
| TXT | Text verification, SPF, DKIM, and similar |
| NS | Nameserver delegation within the zone |
| SRV | Service location |
| CAA | Certificate authority authorization |
| HTTPS | HTTPS service binding (SVCB/HTTPS) |
| ALIAS | Apex-friendly alias (similar to CNAME at root) |

### Locked records

When you add a domain, Appwrite creates records required for network integration (routing, TLS, and nameserver glue). Locked records:

- Cannot be edited or deleted from the Console.
- Ensure [Sites](/docs/products/sites/domains), [Functions](/docs/products/functions/domains), and [API custom domains](/docs/advanced/platform/custom-domains) can attach safely.

Add your own records alongside locked entries. Appwrite prevents conflicts with locked names when you save.

### TTL and propagation

Each record has a TTL (time to live) that controls how long resolvers cache the answer. Lower TTL values speed up changes during migrations but increase query volume. After stable configuration, many teams use 3600 seconds (1 hour) or higher for static records.

Record changes on Appwrite DNS typically propagate faster than registrar nameserver changes, but resolvers worldwide may cache old values until TTL expires.

### Product connections

Organization DNS is authoritative for the zone. When you attach a hostname on a Site, Function, or project API, Appwrite may add locked CNAME (or related) records in this zone automatically.

Product settings still control routing behavior (for example Sites [domain rule types](/docs/products/sites/domains#domain-rule-types)). Subdomains usually need a CNAME to `appwrite.network` (Sites/Functions) or the API target from project settings; apex domains use nameserver delegation or [CNAME flattening](/docs/products/network/dns#using-cname-flattening).

See [Connect to products](/docs/products/domains/connect) for step-by-step apex and subdomain setup across all products.

For inbound email, use [DNS presets](/docs/products/domains/presets) to add provider MX records without entering each value manually.

[Manage DNS records](/docs/products/domains/manage-dns)

---

## Add external domain

URL: https://appwrite.io/docs/products/domains/external

If you already registered a domain elsewhere, you can add it to Appwrite and delegate DNS without transferring registration. The Console shows an external registrar for these domains. Appwrite manages DNS once nameservers are verified.

This guide walks you through adding the domain, updating nameservers, and restoring DNS records.

### Add a domain

1. Open **Domains** in your organization.
2. Click **Add domain**.
3. Enter the domain name without a protocol (for example `example.com` or `www.example.com`).
4. Click **Add domain** to create the resource.

Appwrite creates the domain and pre-populates DNS records required for Appwrite network routing. Some of these records are **locked** and cannot be edited or deleted.

### Point nameservers to Appwrite

At your DNS host or registrar, replace the domain's nameservers with:

```text
ns1.appwrite.zone
ns2.appwrite.zone
```

This delegates DNS for the domain to Appwrite. Any existing DNS records at your previous host stop being authoritative once delegation propagates.

**DNS propagation**

Nameserver changes can take up to 48 hours to propagate globally. During propagation, verification may fail intermittently.

### Verify

1. Return to the domain in the Appwrite Console.
2. When nameservers are correct, status changes to **Verified** and nameservers show **Appwrite**.
3. If verification fails, click **Verify** to retry after you confirm nameserver updates at your registrar.

Until verification succeeds, you cannot rely on Appwrite-managed DNS for product connections that require a verified zone.

### Recreate existing records

Delegating nameservers to Appwrite replaces your previous DNS configuration for that domain. Recreate records Appwrite does not add automatically:

- **MX** and **TXT** for email (see [Configure email](/docs/products/domains/manage-dns#configure-email) in Manage DNS records)
- **TXT** for domain verification with third-party services
- **CNAME** or **A** records for non-Appwrite hosts

Use the **Records** tab or [import a zone file](/docs/products/domains/manage-dns#import-and-export-a-zone-file) if you exported records from your previous DNS provider.

### Apex vs subdomain

You can add apex domains (`example.com`) or subdomains (`app.example.com`). For apex domains used only with CNAME-based products (Sites, Functions) without full NS delegation, see [CNAME flattening](/docs/products/network/dns#using-cname-flattening) in the Network DNS docs. Organization **Domains** is intended for full-zone management via Appwrite nameservers.

### Connect to products

After verification, [connect to products](/docs/products/domains/connect) for Sites, Functions, and API custom domains.

### Delete a domain

Removing a domain from Appwrite deletes its DNS zone in Appwrite. It does **not** cancel registration at your external registrar. See [Delete a domain](/docs/products/domains/delete) for single and bulk delete and registry implications.

You can [change organization](/docs/products/domains/change-organization) from the **Settings** tab. See [Registration](/docs/products/domains/registration) for organization scope and plan limits.

---

## Manage DNS records

URL: https://appwrite.io/docs/products/domains/manage-dns

You manage DNS records for verified organization domains from the domain **Records** tab. This guide covers day-to-day record operations, zone import, and email provider presets.

For record types and locked entries, see [DNS records](/docs/products/domains/dns).

### Open the records table

1. Open **Organization** > **Domains**.
2. Select a verified domain.
3. Open the **DNS Records** tab.

The table lists all records in the zone, including locked records Appwrite created for network routing. Use search filters to narrow by type, name, or value.

### Create a record

1. Click **Create record**.
2. Select the record type (A, AAAA, CNAME, MX, TXT, and others supported on organization domains).
3. Enter the name, value, TTL, and any type-specific fields (for example priority on MX and SRV records).
4. Save the record.

Locked records cannot be edited or deleted. Appwrite prevents conflicts with locked names when you save.

### Update and delete records

Use the row actions menu on a record you created to update or delete it.

To delete multiple records:

1. Select the checkboxes on deletable (non-locked) rows.
2. Open the bulk action bar and choose delete.
3. Confirm the deletion.

### Import and export a zone file

To migrate many records at once:

1. On the **Records** tab, open **Import zone**.
2. Paste or upload BIND-style zone file content.
3. Confirm the import.

Import replaces the editable portion of the zone according to the file. Locked records remain. Export the current zone from the same area for backup before large changes.

**Validate before import**

Review zone file contents carefully. Incorrect MX or TXT records can interrupt email or verification for production domains.

### Configure email

Use **Add preset** on the **Records** tab to add MX (and SPF for iCloud) records for common email providers in one step.

See [DNS presets](/docs/products/domains/presets) for supported providers, exact record values, and provider-specific follow-up steps.

### Filters

The records table supports URL-backed filters (type, name, value, and more), similar to other Appwrite list views. Use filters to audit records before a migration or to confirm preset records were created.

When filters return no rows, clear or adjust filters rather than assuming the zone is empty.

### Connect to products

After DNS is configured, attach hostnames to [Sites](/docs/products/sites/domains), [Functions](/docs/products/functions/domains), or [API custom domains](/docs/advanced/platform/custom-domains). See [Connect to products](/docs/products/domains/connect).

---

## DNS presets

URL: https://appwrite.io/docs/products/domains/presets

**DNS presets** are curated record sets for common email providers. They add the MX (and in some cases TXT) records your provider expects at the apex of your domain (`@`), so you can route mail without typing each record manually.

Presets are available on verified organization domains where Appwrite hosts the DNS zone. They do not replace provider-specific setup such as domain verification TXT, DKIM, or DMARC. Add those records separately after applying a preset.

For manual record operations, see [Manage DNS records](/docs/products/domains/manage-dns). For record types and locked entries, see [DNS records](/docs/products/domains/dns).

### When to use presets

Use presets when:

- Your domain is **verified** under **Organization** > **Domains** with Appwrite nameservers.
- You want to receive email at the apex domain (for example `you@example.com`) through a supported provider.
- You are migrating MX records from another DNS host into Appwrite's zone.

Presets append new records to the zone. They do not remove existing MX or TXT entries. Review the records table before and after applying a preset to avoid duplicate MX records that can confuse mail routing.

**Product hostnames are separate**

Presets configure **email** at the zone apex. Connecting `www.example.com` or `api.example.com` to Sites, Functions, or APIs uses the [Connect to products](/docs/products/domains/connect) flow and different record types (CNAME, NS, or locked routing records).

### Apply a preset in the Console

1. Open **Organization** > **Domains** and select a verified domain.
2. Open the **DNS Records** tab.
3. Click **Add preset** and choose a provider.
4. Appwrite fetches the preset and creates each record in the zone.
5. Confirm the new rows appear in the records table.

On mobile, open **More** > **Preset** and select the provider.

Allow TTL-based propagation before sending production mail. Most preset records use a TTL of `3600` seconds (1 hour).

### Available presets

| Preset | Records | Record types | Purpose |
| ------ | ------- | ------------ | ------- |
| [Google Workspace](#google-workspace) | 5 | MX | Gmail and Google Workspace inbound mail |
| [Outlook](#outlook) | 1 | MX | Microsoft 365 / Outlook.com hosting |
| [Mailgun](#mailgun) | 2 | MX | Mailgun transactional email routing |
| [Zoho](#zoho) | 3 | MX | Zoho Mail inbound routing |
| [ProtonMail](#protonmail) | 2 | MX | ProtonMail custom domain mail |
| [iCloud](#icloud) | 3 | MX, TXT | iCloud Mail with SPF |

### Google Workspace

Adds five MX records at `@` pointing to Google's mail exchangers:

| Priority | Value |
| -------- | ----- |
| 1 | `aspmx.l.google.com` |
| 5 | `alt1.aspmx.l.google.com` |
| 5 | `alt2.aspmx.l.google.com` |
| 10 | `alt3.aspmx.l.google.com` |
| 10 | `alt4.aspmx.l.google.com` |

After applying the preset, complete Google Workspace domain verification and add any TXT or CNAME records Google Admin requires (for example DKIM and site verification). The preset covers inbound MX only.

### Outlook

Adds one MX record at `@`:

| Priority | Value |
| -------- | ----- |
| 10 | `outlook-com.olc.protection.outlook.com` |

Complete Microsoft 365 domain setup in the Microsoft admin center. You may need additional TXT records for verification and SPF/DKIM that are not part of this preset.

### Mailgun

Adds two MX records at `@`:

| Priority | Value |
| -------- | ----- |
| 10 | `mxa.mailgun.org` |
| 10 | `mxb.mailgun.org` |

Mailgun also requires domain-specific TXT records (SPF, DKIM) from the Mailgun dashboard. Add those manually on the **Records** tab after the preset.

### Zoho

Adds three MX records at `@`:

| Priority | Value |
| -------- | ----- |
| 10 | `mx.zoho.com` |
| 20 | `mx2.zoho.com` |
| 30 | `mx3.zoho.com` |

Complete Zoho domain verification in Zoho Mail admin. Add SPF and DKIM TXT records from Zoho if they are not already in your zone.

### ProtonMail

Adds two MX records at `@`:

| Priority | Value |
| -------- | ----- |
| 10 | `mail.protonmail.ch` |
| 20 | `mailsec.protonmail.ch` |

Follow ProtonMail custom domain setup for any additional TXT or DKIM records required by your plan.

### iCloud

Adds two MX records and one SPF TXT record at `@`:

**MX**

| Priority | Value |
| -------- | ----- |
| 10 | `mx01.mail.icloud.com` |
| 20 | `mx02.mail.icloud.com` |

**TXT (SPF)**

```text
v=spf1 redirect=icloud.com
```

iCloud is the only preset that includes an SPF TXT record. Other providers expect you to add SPF, DKIM, and verification TXT from their admin consoles.

### After applying a preset

1. **Check for duplicates**: If you already had MX records for another provider, remove or update conflicting rows.
2. **Add provider verification records**: TXT and CNAME values from your email provider's setup wizard are not included in presets.
3. **Consider DMARC**: Add a DMARC TXT record at `_dmarc` when you are ready to enforce SPF/DKIM alignment.
4. **Test delivery**: Send test messages to and from addresses on the domain after propagation.

Preset-created records are normal zone records. You can edit or delete them from the records table unless they are locked (presets create unlocked records).

### Troubleshooting

**Preset fails to add records**

- Confirm the domain is verified and you have permission to manage DNS.
- Check for name conflicts with locked records or duplicate MX at `@`.

**Mail does not arrive after applying a preset**

- Wait for DNS propagation (up to 48 hours in rare cases).
- Confirm the provider's domain verification and SPF/DKIM steps are complete.
- Ensure no conflicting MX records remain in the zone.

**Need records for a provider not listed**

- Use **Create record** on the **Records** tab or [import a zone file](/docs/products/domains/manage-dns#import-and-export-a-zone-file) with your provider's BIND export.

[Manage DNS records](/docs/products/domains/manage-dns)

---

## Pricing

URL: https://appwrite.io/docs/products/domains/pricing

Appwrite quotes domain prices before you register or transfer a domain. Prices depend on the TLD, whether the name is premium, and the registration period required by the registry.

### Registration and transfer quotes

When you search for a domain in the Console buy flow or start a transfer in, Appwrite fetches a price quote for each name. Quotes include:

- **Price**: total cost for the quoted registration or transfer period (in your organization's billing currency).
- **Available**: whether the name can be registered or transferred at that time.
- **Period years**: length of the registration period covered by the price (typically one year; some TLDs require longer minimums).
- **Premium**: whether the registry classifies the name as premium (higher than standard TLD pricing).

The checkout and transfer summaries also show an estimated **renewal price** for the same period when available, so you can plan ongoing costs before you pay.

[Learn about renewal](/docs/products/domains/renewal)

### Registration periods

Most TLDs register for one year by default. Some TLDs require a longer minimum period. For example, `.ai` domains register for two years. The buy wizard and order summary always show the exact period before you confirm payment.

Transfer-in quotes follow the same period rules as new registration for a given TLD.

### Premium domains

Premium names are offered at registry premium pricing, which can be significantly higher than standard TLD pricing. The buy wizard marks premium results and the checkout summary labels premium domains before you pay.

Premium status comes from the registry, not from Appwrite. Availability and price can change until registration completes.

### Search and suggestions

The buy wizard searches across many supported TLDs (for example `.com`, `.io`, `.dev`, `.app`, and others). You can search with or without a TLD:

- `myapp` returns suggestions such as `myapp.com` and `myapp.io`.
- `myapp.io` focuses results on the `.io` extension.

Prices load as suggestions appear. Unavailable names are marked as taken.

### Transfer pricing

Transfer in includes a registry transfer fee, quoted the same way as registration. The transfer wizard shows the transfer charge and renewal estimate before you submit payment.

If a domain cannot be transferred (locked, policy restriction, or recent registration), the quote reflects **not transferrable** status instead of a price. See [Transfer a domain](/docs/products/domains/transfer).

### Billing

Registration and transfer charges are invoiced to your organization immediately on successful payment. Renewal charges follow the [renewal](/docs/products/domains/renewal) schedule when auto-renewal runs or when you renew manually.

Prices on the domain resource (`renewalPrice`) reflect the renewal amount Appwrite uses for auto-renewal attempts on Appwrite-registered domains.

---

## Start with Domains

URL: https://appwrite.io/docs/products/domains/quick-start

You can register a new domain or add one you already own in minutes. Both paths end with a verified domain in your organization that you can connect to Appwrite products.

### Open Domains

1. Sign in to [Appwrite Cloud](https://cloud.appwrite.io).
2. Select your organization.
3. Open **Domains** in the sidebar.

If you do not see **Domains**, confirm your organization is on a Cloud plan that includes the feature.

### Register or add a domain

Choose the path that matches how you want to manage the domain.

##### Register

1. Click **Buy domain**.
2. Search for a name (for example `myapp` or `myapp.com`).
3. Review availability and pricing, then continue to checkout.
4. Enter registrant contact details, select a billing address and payment method, and complete payment.

After payment succeeds, Appwrite registers the domain and opens its detail page. See [Register a domain](/docs/products/domains/register) for the full flow.

##### Transfer in

1. Click **Transfer domain**.
2. Enter the full domain name and the authorization code from your current registrar.
3. Select a payment method and confirm the transfer fee.

Transfer progress appears on the domain detail page. See [Transfer a domain](/docs/products/domains/transfer) for status details and troubleshooting.

##### Add external

1. Click **Add domain**.
2. Enter the domain name (for example `example.com`).
3. At your current DNS host, set the domain nameservers to `ns1.appwrite.zone` and `ns2.appwrite.zone`.
4. In the Console, click **Verify** once nameserver changes have propagated.

See [Add external domain](/docs/products/domains/external) for record migration and apex-domain notes.

### Manage DNS records

After verification, open the domain and use the **Records** tab to add or edit DNS entries. Appwrite creates locked records required for network routing. You can add MX, TXT, and other records for email and third-party services.

See [DNS records](/docs/products/domains/dns) for supported record types and locked entries. To add or import records, see [Manage DNS records](/docs/products/domains/manage-dns).

### Connect to a product

Use your verified domain with Appwrite compute and APIs:

1. Choose a hostname (apex or subdomain) for each product.
2. Add the domain on the Site, Function, or project **Custom domains** settings.
3. Create the CNAME or nameserver records the Console shows, then verify.

See [Connect to products](/docs/products/domains/connect) for apex vs subdomain setup, example layouts (`www`, `api`, `staging`), and per-product steps.

---

## Register a domain

URL: https://appwrite.io/docs/products/domains/register

You can register domain names directly from the Appwrite Console. Appwrite is the registrar for these domains: registration, renewal, and billing run through your organization.

This guide walks you through search, checkout, and payment.

### Search

1. In your organization, open **Domains**.
2. Click **Buy domain**.
3. Enter a name in the search field.

The wizard shows suggestions across many TLDs (for example `.com`, `.io`, `.dev`, `.app`). Prices load as results appear. Each result indicates whether the name is available, the registration price, the registration period, and whether the name is **premium** (higher registry pricing).

See [Pricing](/docs/products/domains/pricing) for premium names, registration periods, and how quotes work.

**Registration period**

Most TLDs register for one year by default. Some TLDs require a longer minimum period (for example `.ai` registers for two years). The checkout summary shows the exact period and total before you pay.

You can search with or without a TLD:

- `myapp` suggests `myapp.com`, `myapp.io`, and other supported TLDs.
- `myapp.io` focuses pricing on the `.io` extension.

### Checkout

When you select an available domain, continue to checkout and provide:

| Field | Purpose |
|-------|---------|
| Registrant first and last name | WHOIS / registry contact |
| Email and phone | Registry contact and notifications |
| Company name (optional) | Organization registrant details |
| Billing address | Invoice and tax details |
| Payment method | Card on file for registration and renewals |

The order summary shows the registration charge, registration period, and estimated renewal price for the same period where available.

### Complete payment

Registration is charged immediately. If your card requires additional authentication (3D Secure), complete the verification step in the payment modal.

When payment succeeds:

1. Appwrite submits the registration.
2. You are redirected to the new domain's detail page.
3. The domain appears in your organization's domain list with registrar **Appwrite**.

If payment fails or registration cannot be completed, you can retry from the buy flow. No domain is added until registration succeeds.

**Premium domains**

Premium names are offered at registry premium pricing. The checkout summary marks premium domains before you confirm payment.

### After registration

Appwrite-registered domains:

- Use Appwrite nameservers automatically.
- Show **Verified** once DNS is active.
- Support [auto-renewal](/docs/products/domains/renewal) from domain settings.
- Can be [transferred out](/docs/products/domains/transfer#transfer-out) with an authorization code.

Configure [DNS records](/docs/products/domains/dns) on the **Records** tab using [Manage DNS records](/docs/products/domains/manage-dns), then [connect to products](/docs/products/domains/connect).

If your organization reaches its [domain plan limit](/docs/products/domains/registration#plan-limits), registration is disabled until you delete a domain or upgrade your plan.

---

## Registration

URL: https://appwrite.io/docs/products/domains/registration

In Appwrite Domains, **registration** describes who holds the domain at the registry and how the domain is added to your organization. DNS management in Appwrite is separate from registration: you can delegate DNS to Appwrite for domains registered elsewhere.

### Domain types

Appwrite distinguishes domains by how they are managed:

| Type | How it is added | Registrar in Console | Auto-renewal |
|------|-----------------|----------------------|--------------|
| **Appwrite-registered** | [Register](/docs/products/domains/register) or [transfer in](/docs/products/domains/transfer) through Appwrite | Appwrite | Available |
| **External** | [Add external domain](/docs/products/domains/external) and point nameservers to Appwrite | External registrar | Managed at your registrar |

Both types can use Appwrite DNS once nameservers point to `ns1.appwrite.zone` and `ns2.appwrite.zone` and verification completes.

[Learn about pricing](/docs/products/domains/pricing)

### Organization scope

Domains belong to an **organization**, not an individual project. Plan limits apply per organization. You can [change organization](/docs/products/domains/change-organization) from the domain **Settings** tab to reassign a domain to another organization you control.

When you connect a verified domain to a Site, Function, or API endpoint, Appwrite uses the same DNS zone you manage under **Organization** > **Domains**.

### Verification

External domains must be verified before Appwrite treats the zone as authoritative. Verification succeeds when the domain's nameservers delegate to Appwrite (`nameservers` shows **Appwrite** in the Console).

Appwrite-registered domains use Appwrite nameservers automatically and show **Verified** once DNS is active.

If verification fails after a nameserver change, use **Verify** on the domain to retry. Nameserver updates can take up to 48 hours to propagate globally.

### Plan limits

Each organization plan includes a maximum number of domains. Registration, transfer in, and adding external domains are blocked when you reach the limit until you delete a domain or [upgrade your plan](/docs/advanced/platform/billing).

Downgrading a plan may require removing domains to meet the new limit before the change completes.

### Transfer status

Domains being transferred into Appwrite show a transfer status until the registry completes the move. Status values include pending owner approval, pending admin approval, pending registry, completed, cancelled, and not transferrable.

See [Transfer a domain](/docs/products/domains/transfer) for the full transfer-in and transfer-out workflows.

### Access

On Appwrite Cloud with [organization roles](/docs/advanced/platform/roles) enabled, owners and developers with the domains write scope can manage domains, DNS records, and settings. Viewers cannot change domain configuration.

When [organization roles](/docs/advanced/platform/roles) are disabled (for example self-hosted profiles without org roles), all organization members can manage domains where the feature is available.

---

## Renewal

URL: https://appwrite.io/docs/products/domains/renewal

Renewal applies to domains where **Appwrite is the registrar** ([registered](/docs/products/domains/register) or [transferred in](/docs/products/domains/transfer) through Appwrite). [External domains](/docs/products/domains/external) renew at your external registrar. Appwrite does not charge renewal for those names.

### Expiry and renewal dates

On the domain detail page and **Settings** tab, Appwrite shows:

- **Expiry date**: when the current registration period ends if not renewed
- **Renewal date**: when the next renewal cycle applies (aligned with registry data)

Track these dates for domains with auto-renewal disabled.

### Auto-renewal

Auto-renewal is available only for Appwrite-registered domains.

1. Open the domain **Settings** tab.
2. Toggle **Auto renewal**.
3. Click **Update** to save.

When enabled, Appwrite attempts to renew the domain before expiry using your organization's default payment method. Renewal uses the price stored on the domain (`renewalPrice`) for the quoted period.

**Payment method required**

Auto-renewal requires a valid payment method on the organization. Update billing details under organization **Settings** > **Billing** if renewal fails.

### Renewal retry schedule

If a renewal charge fails, Appwrite retries on a schedule leading up to expiry (multiple attempts across roughly 30 days before the expiration date). You receive email notification when renewal fails or when action is required.

To avoid losing a domain:

- Keep a backup payment method on the organization.
- Monitor billing emails from Appwrite.
- Renew manually by ensuring a valid card before the expiry date if auto-renewal is off.

### Registration and transfer charges

[Registering](/docs/products/domains/register) or [transferring in](/docs/products/domains/transfer) a domain creates an invoice on your organization. Charges appear in organization billing history with the domain name and registration period.

Checkout shows the registration or transfer fee, premium pricing when applicable, and estimated renewal amount for the same period when available. See [Pricing](/docs/products/domains/pricing) for how quotes and premium names work.

### Invoices

Domain registration, transfer, and renewal generate invoices you can download from organization billing. Invoice line items include the domain, period length, and charge type (registration, transfer, or renewal).

### Tax and billing address

Registration checkout uses the billing address on your organization for tax calculation. Keep registrant contact and billing address current to avoid registry or payment issues.

### Plan limits

Organization plans cap how many domains you can hold. When [downgrading](/docs/advanced/platform/billing), you may need to remove domains to meet the new limit before the plan change completes.

Removing a domain from Appwrite does not automatically cancel registry registration for Appwrite-registered names unless you also complete [transfer out](/docs/products/domains/transfer#transfer-out) or allow expiry without renewal. See [Delete a domain](/docs/products/domains/delete).

---

## Transfer a domain

URL: https://appwrite.io/docs/products/domains/transfer

You can transfer domain registration **into** Appwrite from another registrar and **out** to another registrar when the domain is registered with Appwrite.

This guide covers transfer in, transfer status, and transfer out.

**Change organization instead?**

To reassign a domain between Appwrite organizations without changing registrar, see [Change organization](/docs/products/domains/change-organization).

### Transfer in

Use transfer in when you want Appwrite to become the registrar for a domain you already own elsewhere.

#### Before you start

- Unlock the domain at your current registrar.
- Request an **authorization code** (also called EPP or transfer code).
- Confirm the registrant email on the domain can receive approval messages.
- Ensure your organization has not reached its [domain plan limit](/docs/products/domains/registration#plan-limits).

#### Start the transfer

1. Open **Domains** in your organization.
2. Click **Transfer domain**.
3. Enter the full domain name (for example `example.com`).
4. Enter the authorization code from your current registrar.
5. Select a payment method and review the transfer fee shown in the summary.

Transfer pricing follows the same period rules as registration (for example `.ai` may require a two-year quote). Renewal pricing for after the transfer may appear in the summary when available.

#### Payment and confirmation

Transfer in requires payment before Appwrite submits the transfer to the registry. Complete any required card authentication in the payment modal.

After payment:

1. Appwrite initiates the transfer.
2. The domain appears in your list with a transfer status badge.
3. Status updates automatically while the transfer is in progress.

#### Transfer status

While a transfer is pending, the Console may show:

| Status | Meaning |
|--------|---------|
| Pending owner approval | Waiting for confirmation from the domain owner |
| Pending admin approval | Waiting for administrative approval at the registry |
| Pending registry | Registry is processing the transfer |
| Transfer completed | Domain is registered with Appwrite |
| Transfer cancelled | Transfer was cancelled before completion |
| Not transferrable | Domain cannot be transferred (locked, recent registration, or policy restriction) |
| Transfer unavailable | Transfer service is temporarily unavailable |

**How long transfers take**

Domain transfers usually take 5 to 7 days. ICANN allows the previous registrar up to 5 days to release the domain. Some TLDs (for example `.com` and `.net`) may need an extra 1 to 2 days to finalize.

When the transfer completes, the domain behaves like any [Appwrite-registered](/docs/products/domains/register) name: Appwrite is shown as registrar, auto-renewal is available, and DNS is managed in the Console.

### Transfer out

Transfer out moves registration from Appwrite to another registrar. This applies only to domains where Appwrite is the registrar.

1. Open the domain in your organization.
2. Go to the **Settings** tab.
3. In **Transfer to another registrar**, generate a transfer authorization code.
4. Copy the code and submit it at your new registrar to start the transfer away from Appwrite.

Keep the authorization code private until you use it at the receiving registrar. After transfer out completes, the domain is removed from Appwrite and DNS management in the Console ends for that registration.

**DNS after transfer out**

Plan DNS and product connections at the new registrar before transfer out completes. Sites, Functions, and API custom domains that relied on Appwrite DNS must be reconfigured at your new DNS host.

### Troubleshooting

**Transfer stuck in pending**

Confirm the domain is unlocked at the previous registrar, the authorization code is current, and owner approval emails were accepted. Status polling continues automatically in the Console.

**Not transferrable**

Common causes include a 60-day lock after registration, an incorrect authorization code, or registrar-specific restrictions. Resolve the issue at your current registrar and start a new transfer attempt.

**Plan limit reached**

You cannot complete transfer in if the organization is at its domain limit. [Delete a domain](/docs/products/domains/delete) or upgrade the plan, then retry.

---

## Firewall

URL: https://appwrite.io/docs/products/firewall

Appwrite **Firewall** is project-level traffic control. You define rules that match requests by attributes such as IP address, hostname, path, HTTP method, headers, query parameters, user agent, or location, then apply an action before traffic reaches your [API](/docs/apis/rest), [Functions](/docs/products/functions), or [Sites](/docs/products/sites).

You manage rules under **Firewall** in your project. A rule applies to the project API, to one function, or to one site. Before you save, the Console previews how many recent requests would match. After, the traffic overview shows request volume and each Firewall outcome.

![Firewall page with traffic overview and rules](/images/docs/firewall/traffic-overview.avif)

**Appwrite Cloud**

Firewall is available on Appwrite Cloud. Rule limits depend on your organization plan (see [Plan limits](/docs/products/firewall/rules#plan-limits)).

### How it works

Each Firewall rule has four parts:

1. **Resource type**: Which traffic the rule considers. Choose the project **API**, one **Function**, or one **Site**. See [Resource scopes](/docs/products/firewall/scopes).
2. **Conditions**: Filters on request attributes (for example IP equals a value, path starts with `/v1/account`, country equals `US`). All conditions on a rule must match (AND). A rule with no conditions matches every request in its scope.
3. **Action**: What happens when the conditions match. One of **Deny**, **Bypass**, **Challenge**, **Rate limit**, or **Redirect**.
4. **Priority**: Lower numbers are evaluated first. The first matching enabled rule decides the outcome for that request and stops evaluation.

### Console access

Firewall never blocks the Appwrite Console, so you cannot lock yourself out. Firewall checks everything else, including your own SDK calls and requests made with an API key.

Rules on a **Site** or a **Function** run at the network edge, which cannot tell your browser from anyone else's. A site challenge, or [Attack mode](/docs/products/firewall/attack-mode), challenges you too.

### Get started

Create your first API-scoped deny rule and confirm outcomes in traffic overview.

[Quick start](/docs/products/firewall/quick-start)

### Concepts

Core ideas behind Firewall rules, matching, and evaluation.

- [Rules](/docs/products/firewall/rules): What a rule contains, enabled state, plan limits, and how rules appear in the Console.
- [Actions](/docs/products/firewall/actions): Deny, bypass, challenge, rate limit, and redirect, including status codes and rate-limit keys.
- [Conditions](/docs/products/firewall/conditions): Request, client, and location attributes, operators, AND matching, and CIDR and geo details.
- [Resource scopes](/docs/products/firewall/scopes): API, Functions, and Sites scopes and where each is enforced.
- [Priority](/docs/products/firewall/priority): Evaluation order, first-match behavior, and tips for stacking rules safely.
- [Traffic overview](/docs/products/firewall/monitor): The metrics Firewall reports, and what the impact preview can estimate.

### Guides

Step-by-step guides for common Firewall policies in the Console.

- [Block traffic by country](/docs/products/firewall/block-countries): Deny project API traffic from specific countries, or serve only the countries you allow.
- [Allowlist trusted IP addresses](/docs/products/firewall/allowlist-ips): Pair a bypass rule with a deny rule so only trusted IP addresses reach a protected path.
- [Rate limit authentication traffic](/docs/products/firewall/rate-limit-auth): Set a request quota on authentication paths to slow brute-force attempts.
- [Challenge automated traffic](/docs/products/firewall/challenge-bots): Verify suspected bots with a challenge before their requests continue.
- [Redirect a site for maintenance](/docs/products/firewall/site-maintenance): Send visitors of a site to a maintenance page and back without a new deployment.
- [Attack mode](/docs/products/firewall/attack-mode): Challenge every visitor to a site with one click during an incident.

---

## Actions

URL: https://appwrite.io/docs/products/firewall/actions

When a request matches a rule's [conditions](/docs/products/firewall/conditions), Appwrite applies the rule **action**. Only one action runs per request, because evaluation stops at the first matching enabled rule. See [Priority](/docs/products/firewall/priority).

There is no separate **Allow** action. Use **Bypass** to allowlist traffic that should skip later deny or rate limit rules.

### Available actions

| Action | Client outcome |
|--------|----------------|
| **Deny** | `403` with an access-denied error |
| **Bypass** | Request continues, and later Firewall rules are skipped |
| **Challenge** | Client must pass a challenge before the request continues |
| **Rate limit** | Under quota: continue and stop further rules. Over quota: `429` with `Retry-After` |
| **Redirect** | HTTP redirect (`300` to `399`) to the configured location |

### Response headers

When Firewall stops a request, the response carries two headers that name the rule and the action it applied:

```
X-Appwrite-WAF-Rule: 6f2c1b9a
X-Appwrite-WAF-Action: deny
```

A challenge adds `X-Appwrite-WAF-Challenge-Type`. The headers arrive with the response. The traffic overview updates later.

Only API rules add the headers to allowed requests.

### Deny

Deny rejects matching requests before they reach your application logic. Use it to block abusive IPs, lock down sensitive paths, or reject traffic from specific countries.

### Bypass

Bypass allows the request and **stops** Firewall evaluation. Later rules (including deny and rate limit) do not run for that request.

Typical uses:

- Allowlist a trusted office IP before a broader deny rule
- Exempt health-check user agents or monitoring paths from rate limits

Give bypass rules a lower priority number than the deny or rate limit rules they override, so they run first.

### Challenge

Challenge verifies matching requests before allowing them through. Clients that pass the challenge reach your application. Clients that fail do not.

Use challenge instead of deny on paths that attract abuse, such as sign-in and sign-up, where a deny rule would also block legitimate users.

| Setting | Purpose | Limits |
|---------|---------|--------|
| **Difficulty** | Client-side proof-of-work cost. Higher values slow automated clients more, and also slow the challenge for legitimate users | `1` (easiest) to `5` (hardest), Console default `3` |
| **TTL (seconds)** | How long a visitor stays cleared after they pass the challenge | `900` (15 minutes) to `86400` (24 hours), Console default `1800` (30 minutes) |

#### Only browser navigation gets a challenge page

Appwrite serves the challenge page only for `GET` requests that accept HTML. Form posts, `fetch` calls, requests for a site's CSS and images, and every request matched by an **API** rule get `403` with a challenge-required error instead.

See [Challenge automated traffic](/docs/products/firewall/challenge-bots) for a worked example.

#### Known bots are denied, not challenged

Appwrite denies obvious automated clients before it issues a challenge, including requests with no `User-Agent` and those containing a marker such as `curl/`, `python-requests`, `go-http-client`, or `headlesschrome`.

They count as **Denied** and never as **Challenged**, so a challenge rule aimed at them does nothing. Use **Deny** for clients you already know are scripts, and **Challenge** for traffic that might be a browser.

#### Clearance lasts for the whole site

Once a visitor solves a challenge, they stay cleared for the length of the TTL across every challenge rule on that hostname, not just the rule that challenged them.

To challenge **every** visitor to a site without building a rule by hand, use [Attack mode](/docs/products/firewall/attack-mode).

### Rate limit

Rate limit throttles matching requests that go over a quota. Each rule counts its own quota, separately for every client.

| Setting | Purpose | Limits |
|---------|---------|--------|
| **Request limit** | Requests allowed per interval | `1` to `1000000` (Console default `100`) |
| **Interval (seconds)** | Length of the window the request limit applies over | `1` to `86400` (Console default `60`) |
| **Limit by** | What counts as one client | **IP address** (`ip`) or **User ID** (`userId`), Console default **IP address** |
| **Strategy** | How the quota is enforced over time (see [Rate limit strategies](#rate-limit-strategies)) | `fixedWindow`, `slidingWindow`, or `tokenBucket` (Console default `fixedWindow`) |
| **Max bucket size** | Token bucket only: the largest burst allowed. The other strategies ignore it | `1` to `1000000` (Console default `50`) |

Appwrite allows a matching request under the quota and skips later rules, the same as a bypass. A request over the quota gets `429` with a `Retry-After` header.

Treat that header as a minimum wait. With sliding window or token bucket, a client that retries the moment it expires may still be throttled.

If the rate limiter itself is unavailable, Appwrite allows the request. A rule can therefore look inactive during an outage.

You choose the strategy when you create the rule and cannot change it afterward. Through the API, an update that sends a different strategy is ignored without an error.

#### Per-user quotas

With **Limit by** set to **User ID**, each signed-in user gets their own quota, regardless of IP address.

An **API** rule uses the user already signed in on the request. A **Functions** or **Sites** rule reads a user JWT from the `x-appwrite-user-jwt` header, so your client has to send one.

A request with no signed-in user skips the rule and continues to the rules below it. Every other action stops evaluation on a match. This skip is the only exception.

If signed-out traffic also needs a quota, add a second rule limited by **IP address**.

### Rate limit strategies

All three strategies hold a client to the same rate over time. They differ in how they treat bursts.

#### Fixed window

Requests are counted in back-to-back windows of the interval length, and the count resets at each boundary. Windows follow the clock, not a client's first request, so every client resets at the same moment.

A client can send a full quota at the end of one window and another at the start of the next, so brief bursts of up to twice the limit get through. Choose fixed window when that is acceptable. It is the default.

#### Sliding window

The previous window's count fades out instead of resetting, so no boundary burst gets through. Choose sliding window when you want an even rate.

#### Token bucket

A bucket refills at **Request limit** per **Interval**, up to **Max bucket size**. Each request spends one token, and requests are throttled once the bucket is empty.

A client that has been quiet spends its saved tokens in a burst, while its sustained rate stays at the limit. Choose token bucket for clients that legitimately burst, such as batch writes or retries after a reconnect. Set **Max bucket size** to the largest burst you are willing to serve, since a misbehaving client can spend it too.

### Redirect

Redirect sends matching clients to another URL without running later Firewall rules.

| Setting | Purpose |
|---------|---------|
| **Redirect location** | Absolute or path location (for example `https://example.com` or `/maintenance`). Default `/`. |
| **Status code** | HTTP redirect status from `300` to `399` (default `302`) |

Use redirects for maintenance pages and deprecated paths, or to move traffic off a path without denying it.

[Rate limit authentication traffic](/docs/products/firewall/rate-limit-auth)

---

## Allowlist trusted IP addresses

URL: https://appwrite.io/docs/products/firewall/allowlist-ips

An allowlist uses two rules. A **bypass** rule with a low priority number lets trusted traffic through. A **deny** rule with a higher priority number blocks all other traffic to the same path.

The pair works because of [priority](/docs/products/firewall/priority). Appwrite evaluates rules with lower priority numbers first. A matching bypass rule stops evaluation, so the deny rule never runs for trusted traffic.

### Create the bypass rule

![Create firewall rule wizard with a bypass rule for an IP range](/images/docs/firewall/allowlist-ips.avif)

1. Open **Firewall** in your project.
2. Click **Create rule**.
3. Enter a **Rule name** (for example `Allow office IP addresses`).
4. Keep **Resource type** set to **API**.
5. Under conditions, set the attribute to **IP address**.
6. Keep the operator set to **Equals**.
7. Enter a single IP address (`203.0.113.10`) or a CIDR range (`198.51.100.0/24`).
8. Under **Then**, choose **Bypass**.
9. Set **Priority** to `10`.
10. Click **Create rule**.

The impact preview compares IP addresses as text, so a CIDR range shows zero matches. The rule still matches every address in the range.

To protect one path only, add a second condition to the same rule. For example, add **Path** **Starts with** `/v1/users`. Both conditions must match for the bypass to apply.

### Create the deny rule

1. Click **Create rule** again.
2. Enter a **Rule name** (for example `Deny all other traffic`).
3. Keep **Resource type** set to **API**.
4. Add the same path condition as the bypass rule, or use **Path** **Starts with** `/v1` for the whole API.
5. Under **Then**, choose **Deny**.
6. Set **Priority** to `100`.
7. Review the impact preview, then click **Create rule**.

The impact preview shows how much recent traffic the deny rule would match. If the count is higher than you expect, tighten the path condition.

### Verify the rules

1. Send a request from a trusted IP address with a client SDK or an API key. Confirm the request succeeds.
2. Send a request from a different IP address. Confirm the client receives a `403` response.
3. Open **Firewall** and confirm the **Denied** series rises in the traffic overview. Bypassed requests count under **Passed**.

[Priority](/docs/products/firewall/priority)

---

## Attack mode

URL: https://appwrite.io/docs/products/firewall/attack-mode

**Attack mode** is a one-click [challenge](/docs/products/firewall/actions#challenge) for the [site](/docs/products/sites) selected in Firewall. Use it during a traffic spike or an abuse incident to put a proof-of-work challenge in front of every visitor.

Attack mode is available only for **Sites**. It does not apply to the project API or to Functions. Select a site in the Firewall resource selector to see the **Attack mode** button.

### Turn it on

The first time you turn Attack mode on, Appwrite creates a rule. The project needs a free slot under its [plan limit](/docs/products/firewall/rules#plan-limits).

Attack mode runs at priority `0`, ahead of bypass rules at the wizard default of `10`. If you rely on bypass rules to allowlist trusted traffic, give them a negative priority before you turn Attack mode on.

1. Open **Firewall** in your project.
2. Select the site you want to protect.
3. Click **Attack mode**.
4. Confirm **Turn on**.

While Attack mode is on, a banner appears under the Firewall header: **Attack mode is on. Every visitor is challenged.**

### What it creates

Appwrite creates (or re-enables) a **Challenge** rule named **Attack mode** on that site. The rule:

- Matches **Path** **Starts with** `/`, so it matches every request to the site
- Uses [priority](/docs/products/firewall/priority) `0`, so it runs before every rule at the wizard default
- Uses difficulty `3` and a TTL of `1800` seconds

The rule appears in the site's rules list like any other rule.

### Turn it off

Click **Attack mode** again, or **Turn off** on the banner, and confirm. Appwrite disables the rule rather than deleting it, so you can turn it back on later. The rule stays in the project and counts toward [plan limits](/docs/products/firewall/rules#plan-limits).

[Challenge action](/docs/products/firewall/actions#challenge)

---

## Block traffic by country

URL: https://appwrite.io/docs/products/firewall/block-countries

A deny rule with a country condition blocks all matching traffic from that country. Use this pattern to comply with regional restrictions or to stop abuse that comes from one region.

Country conditions use the geo location of the client IP address, and work on every plan. Finer location attributes such as city need the [premium Geo DB addon](/docs/products/firewall/conditions#premium-geo-db).

### Create the deny rule

![Create firewall rule wizard with a country condition](/images/docs/firewall/block-countries.avif)

1. Open **Firewall** in your project.
2. Click **Create rule**.
3. Enter a **Rule name** (for example `Block traffic by country`).
4. Keep **Resource type** set to **API**.
5. Under conditions, set the attribute to **Country**.
6. Keep the operator set to **Equals**.
7. Select the country in the country picker.
8. Under **Then**, choose **Deny**.
9. Keep **Priority** at `100`.
10. Review the impact preview, then click **Create rule**.

Denied clients receive a `403` response. The denied requests appear in [traffic overview](/docs/products/firewall/monitor) under the **Denied** series.

To block more than one country, create one deny rule for each. Conditions on a single rule must all match, and a request comes from one country, so a rule with two **Country** **Equals** conditions never matches anything.

### Allow only specific countries

To serve only specific countries, invert the pattern. Create one deny rule, and add one **Country** **Not equal** condition for each allowed country. A request matches the rule only when its country differs from every allowed country, and the rule denies it.

Traffic whose country Appwrite cannot resolve also matches **Not equal**, so this rule denies it. To let it through, add **Country** **Not equal** `unresolved`. See [When Appwrite cannot resolve a location](/docs/products/firewall/conditions#unresolved-location).

### Verify the rule

1. Send a request from the blocked country with a client SDK or an API key. Use one of those, because Firewall never blocks the Console.
2. Confirm the client receives a `403` response.
3. Open **Firewall** and confirm the **Denied** series rises in the traffic overview.

To pause the rule, turn off its status toggle in the rules list.

[Traffic overview](/docs/products/firewall/monitor)

---

## Challenge automated traffic

URL: https://appwrite.io/docs/products/firewall/challenge-bots

A challenge rule makes a visitor's browser solve a small puzzle before the request continues. Browsers solve it and pass, usually without the visitor noticing. Scripts do not.

Use a challenge on pages that attract abuse, such as sign-up, sign-in, and password reset, where an IP or country block would also stop legitimate users.

This guide puts a challenge in front of the sign-up page of a site.

### Create the challenge rule

![Create firewall rule wizard with challenge settings](/images/docs/firewall/challenge-bots.avif)

1. Open **Firewall** in your project.
2. Click **Create rule**.
3. Enter a **Rule name**, for example `Challenge sign-up`.
4. Set **Resource type** to **Sites** and select your site.
5. Under conditions, set **Path** **Starts with** `/signup`.
6. Under **Then**, choose **Challenge**.
7. Keep **Difficulty** at `3`.
8. Keep **TTL (seconds)** at `1800`.
9. Click **Create rule**.

Scope challenge rules to a **Site** or a **Function**. An **API** rule rejects matching requests instead of challenging them, because an API client has no browser to solve the puzzle with. See [Challenge](/docs/products/firewall/actions#challenge).

### Choose the paths to cover

Every condition on a rule must match, so one rule covers one path prefix. Create one rule per path prefix.

Do not build a challenge rule on **User agent** contains `curl`. Appwrite denies known bots before a challenge is issued, so the rule produces denials and **Challenged** stays at zero. See [Known bots are denied](/docs/products/firewall/actions#known-bots-are-denied).

Cover the page a visitor loads, not the path the form posts to. Appwrite serves the challenge page only for browser navigation, so a rule on a `POST` target returns `403` rather than a challenge. A visitor who is cleared on the page carries that clearance through the form submission.

### Verify the rule

1. Open the protected path in a browser.
2. Confirm the challenge page appears before the page loads.
3. Reload the page. The challenge does not appear again, because you are now cleared.
4. Open **Firewall** and confirm the **Challenged** series rises in the traffic overview. The **Challenged** tile also reports solved challenges and the average solve time.

If automated traffic keeps getting through, raise the difficulty. If first page loads feel slow, lower it.

[Resource scopes](/docs/products/firewall/scopes)

---

## Conditions

URL: https://appwrite.io/docs/products/firewall/conditions

**Conditions** define which requests a Firewall rule matches. A condition compares one attribute of the request, such as its path or the client's country, against a value you choose.

Every condition on a rule must match for the rule to apply. Two conditions on one rule mean "this **and** that", never "this or that". To match either of two values, create one rule for each.

**A rule with no conditions matches everything**

The Console requires at least one condition. Through the API, a rule with no conditions matches every request in its scope, and the rules list shows **All requests** in its conditions column.

### Attributes

The condition builder groups attributes by what they describe. A further 13 attributes need the [premium Geo DB addon](#premium-geo-db).

#### Request

| Attribute | Matches | Typical use |
|-----------|---------|-------------|
| **Hostname** | Host serving the request | Separate policies per domain |
| **Path** | URL path only, query string stripped | Protect `/v1/...` prefixes or sensitive routes |
| **Method** | Method enum (`GET`, `POST`, and others), uppercased | Restrict mutating methods on a path |
| **Protocol** | `http` or `https` | Treat plaintext traffic differently |
| **Header** | A named request header | Require, block, or match custom headers |
| **Query parameter** | A named query string parameter | Match tokens or flags passed in the URL |
| **Query parameter name** | The parameter names present, ignoring their values | Match on the presence of a parameter |
| **Accept** | Raw `Accept` header | Tell browser navigation from API calls |
| **Accept-Language** | Raw `Accept-Language` header | Filter by the languages a client asks for |
| **Cookie** | Raw `Cookie` header | Match a cookie your application sets |

**Accept**, **Accept-Language**, and **Cookie** carry a value only for Functions and Sites traffic. On an API rule they are always empty.

#### Client

| Attribute | Matches | Typical use |
|-----------|---------|-------------|
| **IP address** | Client IP as seen by Appwrite. Single IPs or CIDR blocks | Block or allowlist addresses and ranges |
| **Operating system** | Operating system derived from the user agent | Filter traffic by client platform |
| **Browser** | Browser derived from the user agent | Filter specific browsers or embedded clients |
| **User agent** | Raw `User-Agent` header | Filter bots, scripts, or known clients |

#### Location

| Attribute | Matches | Typical use |
|-----------|---------|-------------|
| **Country** | ISO country code from geo lookup, uppercased | Geo allow or deny lists |
| **Continent** | Continent code from geo lookup, uppercased | Broad geo policies |

#### Premium Geo DB

With the premium Geo DB addon you can also match on **City**, **State**, **Postal code**, **Latitude**, **Longitude**, **Time zone**, **Weather code**, **ISP**, **AS number**, **AS organization**, **Connection type**, **Connection usage type**, and **Connection organization**. The network attributes tell data center traffic apart from residential and mobile connections.

**Operator limits on premium attributes**

**Latitude**, **Longitude**, and **AS number** support only equals, not equal, is empty, and is not empty. See [Operators](#operators) for the full list.

Without the addon the Console disables these attributes, and the API rejects them with a `waf_rule_premium_condition` error.

If the addon lapses on a project that already uses them, Appwrite treats those attributes as empty on API traffic. **Equals** conditions then never match, and **Not equal** conditions match every request, so a deny rule can start denying everything. Rewrite those rules before you drop the addon.

### Header and query parameter keys

**Header** and **Query parameter** conditions target a named key. Enter the header name (for example `x-api-client`) or parameter name (for example `token`) together with the operator and value.

Keys must be lowercase to match. The Console lowercases the key when it saves the rule. Through the API, write the key in lowercase yourself, or the condition never matches.

Appwrite strips these headers before it evaluates conditions. A **Header** condition on any of them never matches, even though you can save the rule:

`cookie`, `authorization`, `x-appwrite-key`, `x-appwrite-jwt`, `x-appwrite-user-jwt`, `x-appwrite-session`, and any `x-forwarded-*` header.

To match a cookie on Functions or Sites traffic, use the **Cookie** attribute instead.

### Matching details

- **IP address** accepts single IPs (`203.0.113.10`) and CIDR blocks (`10.0.0.0/8`). CIDR blocks match only with **Equals** and **Not equal**.
- **Path** conditions work best with **Starts with**, for prefixes such as `/v1/account`.
- **Operating system** and **Browser** are derived from the user agent and may not resolve for unusual clients. Match **User agent** directly instead.

Text comparison ignores case.

#### When Appwrite cannot resolve a location

Geo lookup fails for some addresses. **Country** and **Continent** then hold `unresolved` on an API rule, and are unset on a Functions or Sites rule.

Either way they match every **Not equal** condition, so a rule that allows only certain countries denies that traffic too. On an API rule, add **Country** **Not equal** `unresolved` to let it through.

### Operators

| Operator | Meaning | Needs a value |
|----------|---------|---------------|
| Equals | Exact match | Yes |
| Not equal | Does not equal | Yes |
| Contains | Value contains the text | Yes |
| Does not contain | Value does not contain the text | Yes |
| Starts with | Value begins with the text | Yes |
| Ends with | Value ends with the text | Yes |
| Is empty | Attribute is unset | No |
| Is not empty | Attribute is set | No |

Which operators appear depends on the attribute:

| Attribute | Operators |
|-----------|-----------|
| Most attributes | Equals, not equal, contains, starts with, ends with, is empty, is not empty |
| **Method** | Equals, not equal, is empty, is not empty |
| **Country**, **Continent** | Equals, not equal, contains, does not contain |
| **Latitude**, **Longitude**, **AS number** (Premium Geo DB) | Equals, not equal, is empty, is not empty |

**Premium Geo DB attributes**

**City**, **State**, **Postal code**, **Latitude**, **Longitude**, **Time zone**, **Weather code**, **ISP**, **AS number**, **AS organization**, **Connection type**, **Connection usage type**, and **Connection organization** require the [premium Geo DB addon](#premium-geo-db). All other attributes in this table are available on every plan.

**Latitude**, **Longitude**, and **AS number** support only equality and presence operators (equals, not equal, is empty, is not empty). The other premium attributes use the full operator set under **Most attributes**.

**Is empty** matches attributes the request did not send, such as a missing header. It does not match **User agent**: when that header is absent, Appwrite stores an empty string rather than leaving it unset.

**Operators the Console cannot show**

The API accepts more operators than the Console shows. When you open such a rule in the Console, it shows **Equals** in place of the unsupported operator. If you save, it writes **Equals** to the rule. Edit those rules through the API.

### Multiple conditions

Add several conditions on one rule when you need a narrow match. Examples:

- Path starts with `/v1/users` **and** method equals `DELETE`
- Country equals `US` **and** path starts with `/v1`
- IP not equal to your office range (`198.51.100.0/24`) **and** path starts with `/v1`
- Header `x-api-client` is empty **and** user agent contains `bot`

A rule holds at most 100 conditions.

The Console previews how many recent requests a rule would match. See [Impact preview](/docs/products/firewall/monitor#impact-preview).

### Attribute names for the API

The Console sends these names for you. Use them when you create rules through the API. Any other name returns a `400` error.

**Premium Geo DB API names**

From **City** through **Connection organization**, the API names in this table require the [premium Geo DB addon](#premium-geo-db).

| Console label | API name |
|---------------|----------|
| Hostname | `host` |
| Path | `path` |
| Method | `method` |
| Protocol | `protocol` |
| Header | `headers.<name>` |
| Query parameter | `query.<key>` |
| Query parameter name | `queryKeys` |
| Accept | `accept` |
| Accept-Language | `acceptLanguage` |
| Cookie | `cookie` |
| IP address | `ip` |
| Operating system | `os` |
| Browser | `browser` |
| User agent | `userAgent` |
| Country | `country` |
| Continent | `continent` |
| City | `city` |
| State | `state` |
| Postal code | `postalCode` |
| Latitude | `latitude` |
| Longitude | `longitude` |
| Time zone | `timeZone` |
| Weather code | `weatherCode` |
| ISP | `isp` |
| AS number | `autonomousSystemNumber` |
| AS organization | `autonomousSystemOrganization` |
| Connection type | `connectionType` |
| Connection usage type | `connectionUsageType` |
| Connection organization | `connectionOrganization` |

[Block traffic by country](/docs/products/firewall/block-countries)

---

## Traffic overview

URL: https://appwrite.io/docs/products/firewall/monitor

Firewall gives you two views of your rules. Use the **impact preview** to size a rule before you create it, and the **traffic overview** to see what it did after.

### Traffic overview

Open **Firewall** in your project. Above the rules list, the overview chart and metrics summarize recent traffic for the selected date range and interval.

![Firewall traffic overview chart and metrics](/images/docs/firewall/traffic-overview.avif)

Series include:

| Series | Meaning |
|--------|---------|
| **Passed** | Every request that reached your project API, whatever Firewall then did with it, plus edge requests Firewall allowed |
| **Denied** | Requests rejected by a deny action (`403`) |
| **Challenged** | Requests challenged by a [challenge action](/docs/products/firewall/actions#challenge), including [Attack mode](/docs/products/firewall/attack-mode) on a site |
| **Rate limited** | Requests throttled over a rate limit quota (`429`) |
| **Redirected** | Requests answered with a redirect action |

**Block rate** is denied plus rate limited, as a share of that count.

Bypassed requests and under-quota rate-limited requests have no series of their own. Both are allowed, so they appear under **Passed**.

**API rules are counted twice**

A request that an **API** rule denies, challenges, rate limits, or redirects still counts under **Passed**, because it reached your project before Firewall stopped it. The headline total counts that request in two series.

Rules on a **Function** or a **Site** stop the request at the edge, so Appwrite counts those requests once.

Use the date range picker and interval controls to zoom into an incident, or drag across the chart to select a range.

The chart follows the resource selector above the rules list: **API** shows project API traffic, and a selected **Site** or **Function** shows traffic for that resource only.

### What the metrics tell you

If **Passed** is high and **Denied** is flat, check that the rule is enabled, that no bypass rule with a lower priority number catches the traffic first, and that the rule's conditions match your live traffic.

If **Rate limited** climbs with normal load, raise the limit, lengthen the interval, or narrow the conditions.

### Impact preview

When you create or edit a rule, the wizard sidebar estimates how many recent requests would have matched. Use it to tighten a broad path or country filter before you turn on deny, and to size a rate limit quota against real volume.

The estimate covers IP address, path, method, country, continent, state, city, hostname, operating system, browser, and user agent. With the [premium Geo DB addon](/docs/products/firewall/conditions#premium-geo-db), it also covers ISP, AS number, AS organization, connection type, connection usage type, and connection organization.

When a condition uses an attribute that usage can break down, the wizard also charts the top values seen in recent traffic for that attribute, scoped to the rule's resource. Use these charts to pick country codes, ISP names, paths, and similar values. Premium city and network breakdowns require the addon; without it the charts show an upgrade prompt. Attributes with no usage breakdown, such as **Continent** and **State**, still contribute to the match estimate but do not get a distribution chart.

A rule that uses **Header**, **Query parameter**, **Query parameter name**, **Protocol**, **Accept**, **Accept-Language**, **Cookie**, or other attributes without usage data, a **does not contain** condition, or more than eight estimable conditions shows **Preview unavailable** instead of a number. The rule still enforces every condition. IP addresses are compared as text, so a CIDR range shows zero matches. The rule still matches every address in the range.

To confirm which rule handled a request once the rule is live, read the `X-Appwrite-WAF-Rule` response header. See [Find out which rule fired](/docs/products/firewall/actions#which-rule-fired).

[Rate limit authentication traffic](/docs/products/firewall/rate-limit-auth)

---

## Priority

URL: https://appwrite.io/docs/products/firewall/priority

**Priority** sets the order Appwrite evaluates rules in. Lower numbers run first. The first rule that matches applies its [action](/docs/products/firewall/actions), and no later rule runs for that request.

Priority ranges from `-100000` to `100000`. The Console's stepper stops at `0`, so type a negative number to go below it.

### How evaluation works

1. Appwrite loads **enabled** rules for the project, ordered by priority ascending (for example `10` before `100`).
2. Appwrite skips rules that do not apply to the request's [resource type](/docs/products/firewall/scopes).
3. For each remaining rule in order, Appwrite checks whether all [conditions](/docs/products/firewall/conditions) match.
4. On the first match, the rule action runs (deny, bypass, challenge, rate limit, or redirect).
5. Appwrite ignores later rules for that request.

If no rule matches, the request continues.

**Stop on match**

**Bypass** stops evaluation, and so does **rate limit** while the request is under the quota. Neither lets a later deny rule run.

One case is different. A rate limit rule with **Limit by** set to `userId` cannot apply to a signed-out visitor, so Appwrite skips it and continues to the rules below.

### Choosing priority values

The Console create wizard defaults priority to `100`, while the API defaults to `0` when you omit the parameter. Common patterns:

| Pattern | Approach |
|---------|----------|
| Allowlist then deny | Bypass at `10`, deny at `100` |
| Tight path before broad rate limit | Path-specific deny at `20`, broad rate limit at `200` |
| Temporary override | Insert a new rule at a lower number than existing policies |
| [Attack mode](/docs/products/firewall/attack-mode) on a site | Catch-all challenge at `0`. Bypass allowlists must use a **negative** priority to run first |

Leave gaps between priorities (for example `10`, `20`, `30`) so you can insert rules later without renumbering everything.

To change a rule's priority, open its actions menu in the rules list and choose **Update**. The change applies to new requests within moments.

[Allowlist trusted IP addresses](/docs/products/firewall/allowlist-ips)

---

## Start with Firewall

URL: https://appwrite.io/docs/products/firewall/quick-start

In this quick start you create a deny rule on the project **API**, send a request that it blocks, and find that request in the traffic overview.

### Open Firewall

1. Sign in to [Appwrite Cloud](https://cloud.appwrite.io).
2. Open a project.
3. Open **Firewall** in the sidebar.

### Create a deny rule

![Create firewall rule wizard with a deny rule](/images/docs/firewall/create-rule.avif)

1. Click **Create rule**.
2. Enter a **Rule name** (for example `Deny staging admin path`).
3. Leave **Resource type** set to **API** so the rule applies to project API traffic (`/v1/...`).
4. Under conditions, set:
   - Attribute: **Path**
   - Operator: **Starts with**
   - Value: a path you want to block (for example `/v1/account`)
5. Under **Then**, choose **Deny**.
6. Keep **Priority** at `100`.
7. Leave **Enabled** on.
8. Review the impact preview in the sidebar, then click **Create rule**.

### Confirm the rule

![Firewall rules list with the new deny rule](/images/docs/firewall/rules-list.avif)

1. On the Firewall page, make sure the resource selector above the rules list shows **API**.
2. Find your rule in the list. It should show **Deny** and its status toggle should be on.

To change the rule later, open its actions menu and choose **Update**.

### Test the rule

Firewall never blocks the Console, so send the request with a client SDK or an API key instead.

```sh
curl -i https://cloud.appwrite.io/v1/account \
  -H "X-Appwrite-Project: <PROJECT_ID>"
```

The response is `403`, and its headers name the rule that stopped it:

```
HTTP/2 403
x-appwrite-waf-rule: <RULE_ID>
x-appwrite-waf-action: deny
```

### See it in traffic overview

Open **Firewall** in your project. The chart above the rules list shows requests that reached your project (**Passed**) alongside each Firewall outcome. The **Denied** series rises as matching requests arrive.

See [Traffic overview](/docs/products/firewall/monitor) for metrics and date ranges, and [Actions](/docs/products/firewall/actions) for what each outcome means.

### Next steps

The [guides](/docs/products/firewall#guides) cover country blocks, IP allowlists, rate limits, challenges, and redirects.

---

## Rate limit authentication traffic

URL: https://appwrite.io/docs/products/firewall/rate-limit-auth

A rate limit rule sets a request quota for matching traffic. Use it on authentication paths to slow brute-force attacks without blocking normal users.

This guide limits session creation. Each client IP address can send `10` matching requests per `60` seconds. Requests over the quota receive a `429` response with a `Retry-After` header.

You cannot change a rule's strategy after you create it. This guide uses **Fixed window**. Read [Rate limit strategies](/docs/products/firewall/actions#rate-limit-strategies) first if you want another.

### Create the rate limit rule

![Create firewall rule wizard with rate limit settings](/images/docs/firewall/rate-limit-auth.avif)

1. Open **Firewall** in your project.
2. Click **Create rule**.
3. Enter a **Rule name** (for example `Rate limit sign-in attempts`).
4. Keep **Resource type** set to **API**.
5. Under conditions, set **Path** **Starts with** `/v1/account/sessions`.
6. Add a second condition: **Method** **Equals** `POST`.
7. Under **Then**, choose **Rate limit**.
8. Set **Request limit** to `10`.
9. Keep **Interval (seconds)** at `60`.
10. Keep **Strategy** at **Fixed window**.
11. Keep **Limit by** set to **IP address**.
12. Review the impact preview, then click **Create rule**.

The path prefix on its own also covers listing and deleting sessions, so the **Method** condition keeps the quota on session creation. Without it, signing out counts against the same quota.

### Choose the quota

Size the quota against the traffic you see in the impact preview. A quota below normal client behavior creates false positives. A quota far above attack speed gives no protection.

**Fixed window** resets the count at each interval boundary, so `Retry-After` tells a throttled client exactly how long to wait.

Set **Limit by** to **User ID** to count per signed-in user instead of per IP address. Signed-out requests then skip the rule.

### Verify the rule

1. Send more than `10` matching requests in one minute with a client SDK or an API key.
2. Confirm the requests over the quota receive a `429` response with a `Retry-After` header.
3. Open **Firewall** and confirm the **Rate limited** series rises in the traffic overview.

[Actions](/docs/products/firewall/actions)

---

## Rules

URL: https://appwrite.io/docs/products/firewall/rules

A **Firewall rule** is a named policy that matches inbound requests and applies an [action](/docs/products/firewall/actions). Rules belong to a **project**. You manage them under **Firewall** in your project.

### What a rule contains

| Field | Purpose |
|-------|---------|
| Name | Label shown in the rules list |
| Description | Optional notes for your team |
| Resource type | [API, Functions, or Sites](/docs/products/firewall/scopes) |
| Resource ID | Required for Functions and Sites scopes |
| Conditions | [Request filters](/docs/products/firewall/conditions). A rule with none matches every request in its scope |
| Action | Deny, bypass, challenge, rate limit, or redirect (set at create time) |
| Priority | Evaluation order ([lower first](/docs/products/firewall/priority)), `-100000` to `100000` |
| Enabled | Whether the rule is evaluated. Disabled rules are kept and count toward plan limits |
| Rule ID | Set once, when the rule is created. The Console generates one for you |

Each action has its own settings, covered in [Actions](/docs/products/firewall/actions).

The name holds up to 128 characters, and the description holds up to 1024.

### Console layout

![Firewall rules list in the Console](/images/docs/firewall/rules-list.avif)

Use the resource selector above the list to filter by API, function, or site. See [Resource scopes](/docs/products/firewall/scopes).

Each row shows the rule's status toggle, name, action, priority, conditions, and last update. Use the status toggle to enable or disable a rule directly from the list, or open the row's actions menu to update or delete it.

When a **site** is selected, the rules toolbar includes **Attack mode**. Turning it on creates a [challenge](/docs/products/firewall/actions#challenge) rule named **Attack mode** that matches every request, or re-enables the one that already exists. See [Attack mode](/docs/products/firewall/attack-mode).

### API endpoints

Each action has its own endpoint: `/v1/waf/rules/deny`, `/bypass`, `/challenge`, `/rate-limit`, and `/redirect`.

```sh
curl -X POST https://cloud.appwrite.io/v1/waf/rules/deny \
  -H "Content-Type: application/json" \
  -H "X-Appwrite-Project: <PROJECT_ID>" \
  -H "X-Appwrite-Key: <API_KEY>" \
  -d '{
    "ruleId": "unique()",
    "name": "Block a country",
    "resourceType": "api",
    "priority": 100,
    "enabled": true,
    "conditions": [
      { "method": "equal", "attribute": "country", "values": ["RU"] }
    ]
  }'
```

Each condition takes a `method`, an `attribute`, and a list of `values`. Operators that take no value, such as `isNull`, still need `values` as an empty list. [Conditions](/docs/products/firewall/conditions#api-attribute-names) lists the attribute names.

A request without `conditions` creates a rule that matches every request in its scope. An empty `conditions` array returns a `400`.

### Plan limits

Firewall rule limits are **per project** and depend on your organization plan:

| Plan | Rules per project |
|------|-------------------|
| Free (Starter) | 2 |
| Pro | 50 |
| Scale | 50 |

When you reach the limit, you cannot create a rule until you delete one or [upgrade your plan](/docs/advanced/billing). Delete a rule from its actions menu in the rules list. Disabled rules count toward the same limit.

The Console shows current usage next to the Firewall title when a limit applies.

### Who can manage rules

Firewall API scopes are `wafRules.read` and `wafRules.write`. On Appwrite Cloud with [organization roles](/docs/advanced/security/roles) enabled, the owner and developer roles hold both scopes and can create, update, and delete rules. The analyst and editor roles hold only `wafRules.read`, so they can view rules without changing them. The billing role holds neither.

When organization roles are disabled, all organization members can manage rules.

[Quick start](/docs/products/firewall/quick-start)

---

## Resource scopes

URL: https://appwrite.io/docs/products/firewall/scopes

Every Firewall rule has a **resource type** that limits which traffic the rule can match. Scopes keep API-wide policies separate from policies for a single function or site.

### Resource types

| Resource type | Applies to | Resource ID |
|---------------|------------|-------------|
| **API** | Project Appwrite API traffic (`/v1/...`) | Not required (stored empty) |
| **Functions** | Public traffic to one function, enforced at the edge | Function ID required |
| **Sites** | Public traffic to one site, enforced at the edge | Site ID required |

### Where rules are enforced

- **API** rules are enforced on requests to your project's Appwrite API. This is the scope to use for Auth, Databases, Storage, and other `/v1` services.
- **Functions** and **Sites** rules are stored on the project and applied to that function's or site's **public traffic** on the Appwrite Network edge.
- Function and site rules do not block the Console or the management API calls that create, update, or deploy those resources. A deny rule on a function does not stop `GET /v1/functions/{id}` from the Console or a valid API key.

### When to use API

Use **API** for project-wide API controls:

- Block abusive client IPs across Auth, Databases, Storage, and other API services
- Rate limit a shared path prefix such as `/v1`
- Deny traffic from specific countries for the whole project API

### When to use Functions

Use **Functions** to protect or exempt the public traffic of one function:

- Rate limit a public webhook function
- Deny non-`POST` methods on an HTTP-triggered function endpoint
- Bypass Firewall checks for a trusted caller IP on that function only

### When to use Sites

Use **Sites** for the public traffic of one deployed web app:

- Redirect a deprecated path on one site
- Deny admin paths on a marketing site
- Rate limit form submission paths for one site

[Attack mode](/docs/products/firewall/attack-mode) is available only in this scope. Select a site on the Firewall page to challenge every visitor with one click during an incident.

### Resource selector in the Console

![Firewall resource selector with API and site scopes](/images/docs/firewall/resource-selector.avif)

The Firewall page filters the rules list with a resource selector. The **Project** group holds the **API** scope, and each function and site appears under its own group. If you create a rule while a function or site is selected, the wizard preselects that resource. You can change the resource type in the create wizard, or later from the rule's **Update** action.

[Redirect a site for maintenance](/docs/products/firewall/site-maintenance)

---

## Redirect a site for maintenance

URL: https://appwrite.io/docs/products/firewall/site-maintenance

A redirect rule sends matching visitors to another location. Use it to put a deployed site into maintenance without a new deployment.

This guide uses two rules on one site. A **bypass** rule keeps the maintenance page itself reachable. A **redirect** rule sends every other path to the maintenance page. Without the bypass rule, requests to the maintenance page would match the redirect rule and loop.

**Keep the maintenance page self-contained**

The redirect rule also catches the CSS, JavaScript, fonts, and images your maintenance page loads, so the page renders unstyled.

Serve a maintenance page with inline styles and no external assets, or add a bypass rule for each asset path at a lower priority number than the redirect.

### Create the bypass rule

1. Open **Firewall** in your project.
2. Click **Create rule**.
3. Enter a **Rule name** (for example `Allow maintenance page`).
4. Set **Resource type** to **Sites** and select your site.
5. Under conditions, set **Path** **Equals** `/maintenance`.
6. Under **Then**, choose **Bypass**.
7. Set **Priority** to `10`.
8. Click **Create rule**.

### Create the redirect rule

![Create firewall rule wizard with redirect settings](/images/docs/firewall/site-maintenance.avif)

1. Click **Create rule** again.
2. Enter a **Rule name** (for example `Redirect to maintenance page`).
3. Set **Resource type** to **Sites** and select the same site.
4. Under conditions, set **Path** **Starts with** `/`.
5. Under **Then**, choose **Redirect**.
6. Set **Redirect location** to `/maintenance`.
7. Keep **Status code** at `302`.
8. Set **Priority** to `100`.
9. Click **Create rule**.

Use `302` so browsers and search engines do not cache the redirect.

### Verify the rules

1. Open any path of the site in a browser. Confirm the browser lands on `/maintenance`.
2. Open `/maintenance` directly. Confirm that the page loads without a redirect and renders with its styles.
3. Open **Firewall** and confirm the **Redirected** series rises in the traffic overview.

### End the maintenance

1. Open **Firewall** and select the site in the resource selector.
2. Turn off the status toggle of the redirect rule.

The site serves normal traffic again. Keep the disabled rule for the next maintenance window, or delete it.

[Resource scopes](/docs/products/firewall/scopes)

---

## Functions

URL: 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

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.

![Create project screen](/images/docs/functions/template.avif)

[Quick start](/docs/products/functions/quick-start)

---

## Deploy from Git

URL: 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

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**.

![Git repository](/images/docs/functions/git-repo.avif)

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

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

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**.

#### Build triggers

Build triggers control which Git changes create automatic deployments. You can configure branch filters and path filters with glob patterns.

**Branch filters** match branch names. Add patterns to limit automatic deployments to specific branches.

```txt
main
staging
preview/**
```

**Path filters** match files changed in a commit or pull request. Leave this field empty to create deployments for all file changes, or add patterns to deploy only when specific files change.

```txt
functions/api/**
packages/shared/**
!docs/**
```

Use these formats to write glob patterns.

| Pattern | What it does | Example | Matches |
| ------- | ----------------------------------------- | ---------------- | ----------------------------------- |
| `main` | Matches exact text | `main` | `main` |
| `*` | Matches within one path segment | `release/*` | `release/1.0` |
| `**` | Matches across nested path segments | `preview/**` | `preview/test`, `preview/team/test` |
| `?` | Matches one character in one path segment | `v?.?` | `v1.0`, `v2.5` |
| `[abc]` | Matches one character from a set | `[Mm]ain` | `main`, `Main` |
| `[a-z]` | Matches one character in a range | `release/[0-9]*` | `release/1.0` |
| `\` | Escapes a special character | `file\?.txt` | `file?.txt` |
| `!` | Excludes matching branches or paths | `!docs/**` | Excludes changes under `docs/` |

#### 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

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

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
- 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

URL: 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

You can create functions using the CLI without needing to access the Console.

To deploy your function with the Appwrite CLI, use the `appwrite init functions` command to create a starter function and paste your code into the generated file and folder.

```sh
appwrite init functions
```

To deploy the generated code, add any dependencies and push the function using the following command:

```sh
appwrite push functions
```

[Learn more about the CLI functions commands](/docs/tooling/command-line/functions#commands)

#### 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.

[Learn more about appwrite.config.json](/docs/tooling/command-line/functions#appwritejson)

For larger projects, you can split function definitions into a separate JSON file with [multi-file CLI configuration](/docs/tooling/command-line/installation#multi-file-configuration).

### Manual Deployment

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:

  ```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 .
  ```

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
- 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

URL: 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
Throughout the life cycle of a deployment, it could have the following status.

| 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. |

### 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
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

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.

**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
- Longer [build timeouts](/docs/advanced/billing/compute#build-timeouts) (45 minutes vs 15 minutes on Free; Enterprise is custom)
- Customizable [build and runtime specifications](/docs/advanced/billing/compute) for CPU and memory on each function

### Deployment retention
Deployment retention controls how long Appwrite keeps non-active function deployments. The active deployment is always kept. When a non-active deployment is older than the configured retention period, Appwrite automatically deletes it during maintenance. Set the value to `0` to keep non-active deployments forever.

To configure deployment retention from the Appwrite Console:

1. Navigate to **Functions**.
2. Open the function you want to configure.
3. Go to **Settings** > **Deployment retention**.
4. Turn on **Keep deployments forever**, or turn it off and choose how long to keep non-active deployments.
5. Click **Update**.

![Function deployment retention settings](/images/docs/functions/deployment-retention.avif)

The Console provides common presets from `1 Week` to `10 Years`. When using the API or a Server SDK, set `deploymentRetention` to the number of days to keep non-active deployments. The value must be between `0` and `36500`, where `0` means unlimited retention.

When updating a function with a Server SDK, pass the existing settings you do not intend to change and update only `deploymentRetention`.

```server-nodejs
const func = await functions.get({
    functionId: '<FUNCTION_ID>'
});

await functions.update({
    functionId: func.$id,
    name: func.name,
    runtime: func.runtime,
    execute: func.execute ?? undefined,
    events: func.events ?? undefined,
    schedule: func.schedule ?? undefined,
    timeout: func.timeout ?? undefined,
    enabled: func.enabled ?? undefined,
    logging: func.logging ?? undefined,
    entrypoint: func.entrypoint ?? undefined,
    commands: func.commands ?? undefined,
    scopes: func.scopes ?? undefined,
    installationId: func.installationId ?? undefined,
    providerRepositoryId: func.providerRepositoryId ?? undefined,
    providerBranch: func.providerBranch ?? undefined,
    providerSilentMode: func.providerSilentMode ?? undefined,
    providerRootDirectory: func.providerRootDirectory ?? undefined,
    buildSpecification: func.buildSpecification ?? undefined,
    runtimeSpecification: func.runtimeSpecification ?? undefined,
    deploymentRetention: 90
});
```

---

## Develop Appwrite Functions

URL: 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
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
You'll find all of these steps in a simple function like this.
Notice the exported entry point that the executor will call.

```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: 'The open-source cloud for agents and developers_',
    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' => 'The open-source cloud for agents and developers_',
        '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": "The open-source cloud for agents and developers_",
        "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": "The open-source cloud for agents and developers_",
    "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: "The open-source cloud for agents and developers_",
    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:       "The open-source cloud for agents and developers_",
			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': 'The open-source cloud for agents and developers_',
    '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 "The open-source cloud for agents and developers_",
            "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", "The open-source cloud for agents and developers_");
        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": "The open-source cloud for agents and developers_",
        "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", "The open-source cloud for agents and developers_" },
            { "learn", "https://appwrite.io/docs" },
            { "connect", "https://appwrite.io/discord" },
            { "getInspired", "https://builtwith.appwrite.io" },
        });
    }
}
```
```rust
use openruntimes::{Context, Response};
use serde_json::json;

// This is your Appwrite function
// It's executed each time we get a request
pub fn main(context: Context) -> Response {
    // Why not try the Appwrite SDK?
    //
    // Set project and set API key
    // let client = appwrite::Client::new()
    //     .set_endpoint(std::env::var("APPWRITE_FUNCTION_API_ENDPOINT").unwrap_or_default())
    //     .set_project(std::env::var("APPWRITE_FUNCTION_PROJECT_ID").unwrap_or_default())
    //     .set_key(context.req.headers.get("x-appwrite-key").cloned().unwrap_or_default());

    // 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!", None, None);
    }

    // `context.res.json()` is a handy helper for sending JSON
    context.res.json(
        json!({
            "motto": "The open-source cloud for agents and developers_",
            "learn": "https://appwrite.io/docs",
            "connect": "https://appwrite.io/discord",
            "getInspired": "https://builtwith.appwrite.io",
        }),
        None,
        None,
    )
}
```

If you prefer to learn through more examples like this, explore the [examples page](/docs/products/functions/examples).

### 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). |

**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.

##### Destructuring assignment
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).

```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!");
}
```

#### 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 | 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. |

```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.");
    }
}
```
```rust
use openruntimes::{Context, Response};
use serde_json::{json, Value};

pub fn main(mut context: Context) -> Response {
    let body_json: Value = context.req.body_json().unwrap_or(Value::Null);

    context.log(context.req.body_text());                       // Raw request body, contains request data
    context.log(body_json.to_string());                         // Object from parsed JSON request body, otherwise string
    context.log(json!(&context.req.headers).to_string());       // String key-value pairs of all request headers, keys are lowercase
    context.log(context.req.scheme.as_str());                   // Value of the x-forwarded-proto header, usually http or https
    context.log(context.req.method.as_str());                   // Request method, such as GET, POST, PUT, DELETE, PATCH, etc.
    context.log(context.req.url.as_str());                      // Full URL, for example: http://awesome.appwrite.io:8000/v1/hooks?limit=12&offset=50
    context.log(context.req.host.as_str());                     // Hostname from the host header, such as awesome.appwrite.io
    context.log(context.req.port as i32);                       // Port from the host header, for example 8000
    context.log(context.req.path.as_str());                     // Path part of URL, for example /v1/hooks
    context.log(context.req.query_string.as_str());             // Raw query params string. For example "limit=12&offset=50"
    context.log(json!(&context.req.query).to_string());         // Parsed query params. For example, req.query.limit

    context.res.text(
        "All the request parameters are logged to the Appwrite Console.",
        None,
        None,
    )
}
```

##### 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
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 | 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. |

```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");
        }
      }
  };
}
```
```rust
use openruntimes::{Context, Response};
use serde_json::json;
use std::collections::HashMap;

pub fn main(context: Context) -> Response {
    match context.req.query.get("type").map(|s| s.as_str()) {
        Some("empty") => context.res.empty(),
        Some("json") => context.res.json(
            json!({ "type": "This is a JSON response" }),
            None,
            None,
        ),
        Some("redirect") => {
            let mut headers = HashMap::new();
            headers.insert("location".to_string(), "https://appwrite.io".to_string());
            context.res.text("", Some(301), Some(headers))
        }
        Some("html") => {
            let mut headers = HashMap::new();
            headers.insert("content-type".to_string(), "text/html".to_string());
            context
                .res
                .text("<h1>This is an HTML response</h1>", Some(200), Some(headers))
        }
        _ => context.res.text("This is a text response", None, None),
    }
}
```

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.fra.appwrite.run/?type=text` |
| `json` | `/?type=json` | `https://64d4d22db370ae41a32e.fra.appwrite.run/?type=json` |
| `redirect` | `/?type=redirect` | `https://64d4d22db370ae41a32e.fra.appwrite.run/?type=redirect` |
| `html` | `/?type=html` | `https://64d4d22db370ae41a32e.fra.appwrite.run/?type=html` |
| `empty` | `/` | `https://64d4d22db370ae41a32e.fra.appwrite.run/` |

#### 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.

```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!");
      }
  };
}
```
```rust
use openruntimes::{Context, Response};

pub fn main(context: Context) -> Response {
    let message = "This is a log, use for logging information to console";
    context.log(format!("Message: {}", message));
    context.log(format!(
        "This function was called with {} method",
        context.req.method
    ));

    let error_message = "This is an error, use for logging errors to console";
    context.error(format!("Error: {}", error_message));

    context
        .res
        .text("Check the Appwrite Console to see logs and errors!", None, None)
}
```

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.

### Environment variables

Pass constants and secrets into your function with environment variables, and read them inside your function using your runtime's standard environment lookup.

[Manage and read environment variables](/docs/products/functions/environment-variables)

### 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.

| &nbsp; | Language | Package Manager | Commands |
| --- | --- | --- | --- |
| ![Node.js logo](/images/platforms/light/node.svg) | Node.js | NPM | `npm install` |
| ![PHP logo](/images/platforms/light/php.svg) | PHP | Composer | `composer install` |
| ![Python logo](/images/platforms/light/python.svg) | Python | pip | `pip install -r requirements.txt` |
| ![Ruby logo](/images/platforms/light/ruby.svg) | Ruby | Bundler | `bundle install` |
| ![Deno logo](/images/platforms/light/deno.svg) | Deno | deno | `deno cache <ENTRYPOINT_FILE>` |
| ![Go logo](/images/platforms/light/go.svg) | Go | Go Modules | N/A |
| ![Dart logo](/images/platforms/light/dart.svg) | Dart | pub | `pub get` |
| ![Swift logo](/images/platforms/light/swift.svg) | Swift | Swift Package Manager | `swift package resolve` |
| ![.NET logo](/images/platforms/light/dotnet.svg) | .NET | NuGet | `dotnet restore` |
| ![Bun logo](/images/platforms/light/bun.svg) | Bun | bun | `bun install` |
| ![Kotlin logo](/images/platforms/light/kotlin.svg) | Kotlin | Gradle | N/A |
| ![Java logo](/images/platforms/light/java.svg) | Java | Gradle | N/A |
| ![C++ logo](/images/platforms/light/c.svg) | C++ | None | N/A |
| ![Rust logo](/images/platforms/light/rust.svg) | Rust | Cargo | `cargo build --release` |

### Using Appwrite in a function
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 keys are the same as [API keys](/docs/partners/project/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.

```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({
            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");
}
```
```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");
    }
}
```
```rust
use appwrite::id::ID;
use appwrite::services::tables_db::TablesDB;
use appwrite::Client;
use openruntimes::{Context, Response};
use serde_json::json;
use std::env;

pub fn main(context: Context) -> Response {
    // Set project and set API key
    let client = Client::new()
        .set_endpoint(env::var("APPWRITE_FUNCTION_API_ENDPOINT").unwrap_or_default())
        .set_project(env::var("APPWRITE_FUNCTION_PROJECT_ID").unwrap_or_default())
        .set_key(
            context
                .req
                .headers
                .get("x-appwrite-key")
                .cloned()
                .unwrap_or_default(),
        );

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

    let runtime = tokio::runtime::Builder::new_current_thread()
        .enable_all()
        .build()
        .unwrap();

    let result = runtime.block_on(async move {
        tables_db
            .create_row(
                "<DATABASE_ID>",
                "<TABLE_ID>",
                ID::unique(),
                json!({}),
                None,
                None,
            )
            .await
    });

    match result {
        Ok(_) => context.res.text("Row created", None, None),
        Err(e) => {
            context.error(format!("Failed to create row: {}", e));
            context.res.text("Failed to create row", None, None)
        }
    }
}
```

#### Using with 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.

```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: '<DATABASE_ID>', table_id: '<TABLE_ID>', row_id: Appwrite::ID.unique(), data: {})
    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({
            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");
}
```
```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");

    }
}
```
```rust
use appwrite::id::ID;
use appwrite::services::tables_db::TablesDB;
use appwrite::Client;
use openruntimes::{Context, Response};
use serde_json::json;
use std::env;

pub fn main(context: Context) -> Response {
    let client = Client::new()
        .set_endpoint(env::var("APPWRITE_FUNCTION_API_ENDPOINT").unwrap_or_default())
        .set_project(env::var("APPWRITE_FUNCTION_PROJECT_ID").unwrap_or_default());

    let client = match context.req.headers.get("x-appwrite-user-jwt") {
        Some(jwt) => client.set_jwt(jwt.clone()),
        None => {
            return context.res.text(
                "Access denied: This function requires authentication. Please sign in to continue.",
                None,
                None,
            );
        }
    };

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

    let runtime = tokio::runtime::Builder::new_current_thread()
        .enable_all()
        .build()
        .unwrap();

    let result = runtime.block_on(async move {
        tables_db
            .create_row(
                "<DATABASE_ID>",
                "<TABLE_ID>",
                ID::unique(),
                json!({}),
                None,
                None,
            )
            .await
    });

    match result {
        Ok(_) => context.res.text("Row created", None, None),
        Err(e) => {
            context.error(format!("Failed to create row: {}", e));
            context.res.text("Failed to create row", None, None)
        }
    }
}
```

### 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.

**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));
}
```

**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));
};
```

**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))
```

**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
```

**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));
}
```

**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)
}
```

**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));
}
```

**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))
}
```

**.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));
        }
    }
}
```

**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))
    }
}
```

**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));
    }
}
```

**Rust**

```rust
// utils/mod.rs
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}
```
```rust
// lib.rs
use openruntimes::{Context, Response};

mod utils;

pub fn main(context: Context) -> Response {
    context
        .res
        .text(utils::add(1, 2).to_string(), None, None)
}
```

---

## Develop locally

URL: 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

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

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.

[Learn more about developing a function](/docs/products/functions/develop)

### 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.

[Learn more about dynamic API keys](/docs/products/functions/develop#dynamic-api-key)

### 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.

| &nbsp; | Language | Dependency File |
| --- | --- | --- |
| ![Node.js logo](/images/platforms/light/node.svg) | Node.js | package.json, package-lock.json |
| ![PHP logo](/images/platforms/light/php.svg) | PHP | composer.json, composer.lock |
| ![Python logo](/images/platforms/light/python.svg) | Python | requirements.txt, requirements.lock |
| ![Ruby logo](/images/platforms/light/ruby.svg) | Ruby | Gemfile, Gemfile.lock |
| ![Deno logo](/images/platforms/light/deno.svg) | Deno | Import URLs |
| ![Go logo](/images/platforms/light/go.svg) | Go | go.mod |
| ![Dart logo](/images/platforms/light/dart.svg) | Dart | Pubspec.yaml |
| ![Swift logo](/images/platforms/light/swift.svg) | Swift | Package.swift (Swift Package Manager Files) |
| ![.NET logo](/images/platforms/light/dotnet.svg) | .NET | .nupkg (NuGet Packages) |
| ![Bun logo](/images/platforms/light/bun.svg) | Bun | package.json, package-lock.json, bun.lockb |
| ![Kotlin logo](/images/platforms/light/kotlin.svg) | Kotlin | JAR Files (Java ARchive) |
| ![Java logo](/images/platforms/light/java.svg) | Java | JAR Files (Java ARchive) |
| ![C++ logo](/images/platforms/light/c.svg) | C++ | .h (Header Files) |
| ![Rust logo](/images/platforms/light/rust.svg) | Rust | Cargo.toml, Cargo.lock |

### 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

Once you've developed your function, push it by running the following CLI command

```sh
appwrite push functions
```

---

## Domains

URL: 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.

[Learn about Function development](/docs/products/functions/develop)

### Generated domains

Each function automatically receives a region-specific domain that's ready to use immediately after deployment.

1. In the Appwrite Console's sidebar, click **Functions**.
1. Under the **Domains** tab, you'll find the generated domain from Appwrite.

The generated domain ends with `.appwrite.run`, which executes your function directly in the region where it is deployed. For example:

```text
https://64d4d22db370ae41a32e.fra.appwrite.run
```

### Edge network domain

You can add an `appwrite.network` domain to your function to take advantage of Appwrite's edge network. The `appwrite.network` domain routes requests to the nearest region based on the user's geographic location, reducing latency for globally distributed users.

To add an edge network domain:

1. Navigate to the Appwrite Console's **Functions** page.
2. Navigate to the **Domains** tab.
3. Click on **Create domain** and add the `appwrite.network` domain to your function.

The edge network domain ends with `.appwrite.network`. For example:

```text
https://64d4d22db370ae41a32e.appwrite.network
```

[Learn more about edge network](/docs/products/network/edges)

### 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.

---

## Environment variables

URL: https://appwrite.io/docs/products/functions/environment-variables

Appwrite Functions can read environment variables at build and runtime. Use them to pass constants and secrets such as API keys, connection strings, and feature flags without hardcoding them in your source.

A function reads from three sources, in this order of precedence:

1. **Project variables** are shared across every function and site in your project. Set them once and every function inherits them automatically. See [project variables](/docs/partners/project/environment-variables) for the full reference.
2. **Function variables** are scoped to a single function. Override a project variable for one function by setting the same key on the function itself.
3. **Appwrite-injected variables** are set by Appwrite at execution time (for example, `APPWRITE_FUNCTION_PROJECT_ID`). These take final precedence and cannot be overridden.

**Redeployment required**

Variable changes only take effect on the next deployment. Redeploy your function after creating, updating, or deleting variables.

### Manage in the Console

1. Navigate to your function in the Appwrite Console.
2. Open the **Settings** tab > **Environment variables** section.
3. Click **Create variable** and enter a key and value.
4. Optionally select the **Secret** checkbox to prevent any team member from reading the value after creation.
5. Click **Create**, then redeploy the function for the change to take effect.

![Function environment variables](/images/docs/functions/env-variables.avif)

You can also configure global variables that apply to all your functions from your project's **Settings** page. See [project variables](/docs/partners/project/environment-variables) for details.

### Manage with a Server SDK

You can also manage function variables programmatically using a [Server SDK](/docs/sdks#server). Each call requires an [API key](/docs/partners/project/api-keys) with the `functions.write` scope to create, update, or delete variables, or the `functions.read` scope to list and read them.

#### Create a variable

```server-nodejs
import { Client, Functions } from '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 API key

const functions = new Functions(client);

const result = await functions.createVariable({
    functionId: '<FUNCTION_ID>',
    key: '<KEY>',
    value: '<VALUE>',
    secret: false // optional
});
```
```server-deno
import { Client, Functions } 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 API key

const functions = new Functions(client);

const result = await functions.createVariable({
    functionId: '<FUNCTION_ID>',
    key: '<KEY>',
    value: '<VALUE>',
    secret: false // optional
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Functions;

$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 API key

$functions = new Functions($client);

$result = $functions->createVariable(
    functionId: '<FUNCTION_ID>',
    key: '<KEY>',
    value: '<VALUE>',
    secret: false // optional
);
```
```server-python
from appwrite.client import Client
from appwrite.services.functions import Functions
from appwrite.models import Variable

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 API key

functions = Functions(client)

result: Variable = functions.create_variable(
    function_id = '<FUNCTION_ID>',
    key = '<KEY>',
    value = '<VALUE>',
    secret = False # optional
)

print(result.model_dump())
```
```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 API key

functions = Functions.new(client)

result = functions.create_variable(
    function_id: '<FUNCTION_ID>',
    key: '<KEY>',
    value: '<VALUE>',
    secret: false # 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 API key

Functions functions = new Functions(client);

Variable result = await functions.CreateVariable(
    functionId: "<FUNCTION_ID>",
    key: "<KEY>",
    value: "<VALUE>",
    secret: false // 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 API key

Functions functions = Functions(client);

Variable result = await functions.createVariable(
    functionId: '<FUNCTION_ID>',
    key: '<KEY>',
    value: '<VALUE>',
    secret: false, // (optional)
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.coroutines.CoroutineCallback
import io.appwrite.services.Functions

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 API key

val functions = Functions(client)

val response = functions.createVariable(
    functionId = "<FUNCTION_ID>",
    key = "<KEY>",
    value = "<VALUE>",
    secret = false // optional
)
```
```server-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Functions;

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 API key

Functions functions = new Functions(client);

functions.createVariable(
    "<FUNCTION_ID>", // functionId
    "<KEY>", // key
    "<VALUE>", // value
    false, // secret (optional)
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        System.out.println(result);
    })
);
```
```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 API key

let functions = Functions(client)

let variable = try await functions.createVariable(
    functionId: "<FUNCTION_ID>",
    key: "<KEY>",
    value: "<VALUE>",
    secret: false // optional
)
```
```server-go
package main

import (
    "fmt"
    "github.com/appwrite/sdk-for-go/appwrite"
)

client := appwrite.NewClient(
    appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"),
    appwrite.WithProject("<YOUR_PROJECT_ID>"),
    appwrite.WithKey("<YOUR_API_KEY>"),
)

functions := appwrite.NewFunctions(client)

response, error := functions.CreateVariable(
    "<FUNCTION_ID>",
    "<KEY>",
    "<VALUE>",
    appwrite.WithCreateVariableSecret(false),
)
```
```server-rust
use appwrite::Client;
use appwrite::services::Functions;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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 API key

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

    let result = functions.create_variable(
        "<FUNCTION_ID>",
        "<KEY>",
        "<VALUE>",
        Some(false) // optional
    ).await?;

    let _ = result;

    Ok(())
}
```

#### List variables

```server-nodejs
import { Client, Functions } from '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 API key

const functions = new Functions(client);

const result = await functions.listVariables({
    functionId: '<FUNCTION_ID>'
});
```
```server-deno
import { Client, Functions } 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 API key

const functions = new Functions(client);

const result = await functions.listVariables({
    functionId: '<FUNCTION_ID>'
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Functions;

$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 API key

$functions = new Functions($client);

$result = $functions->listVariables(
    functionId: '<FUNCTION_ID>'
);
```
```server-python
from appwrite.client import Client
from appwrite.services.functions import Functions
from appwrite.models import VariableList

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 API key

functions = Functions(client)

result: VariableList = functions.list_variables(
    function_id = '<FUNCTION_ID>'
)

print(result.model_dump())
```
```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 API key

functions = Functions.new(client)

result = functions.list_variables(
    function_id: '<FUNCTION_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 API key

Functions functions = new Functions(client);

VariableList result = await functions.ListVariables(
    functionId: "<FUNCTION_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 API key

Functions functions = Functions(client);

VariableList result = await functions.listVariables(
    functionId: '<FUNCTION_ID>',
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.coroutines.CoroutineCallback
import io.appwrite.services.Functions

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 API key

val functions = Functions(client)

val response = functions.listVariables(
    functionId = "<FUNCTION_ID>"
)
```
```server-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Functions;

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 API key

Functions functions = new Functions(client);

functions.listVariables(
    "<FUNCTION_ID>", // functionId
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        System.out.println(result);
    })
);
```
```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 API key

let functions = Functions(client)

let variableList = try await functions.listVariables(
    functionId: "<FUNCTION_ID>"
)
```
```server-go
package main

import (
    "fmt"
    "github.com/appwrite/sdk-for-go/appwrite"
)

client := appwrite.NewClient(
    appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"),
    appwrite.WithProject("<YOUR_PROJECT_ID>"),
    appwrite.WithKey("<YOUR_API_KEY>"),
)

functions := appwrite.NewFunctions(client)

response, error := functions.ListVariables(
    "<FUNCTION_ID>",
)
```
```server-rust
use appwrite::Client;
use appwrite::services::Functions;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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 API key

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

    let result = functions.list_variables(
        "<FUNCTION_ID>"
    ).await?;

    let _ = result;

    Ok(())
}
```

#### Get a variable

```server-nodejs
import { Client, Functions } from '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 API key

const functions = new Functions(client);

const result = await functions.getVariable({
    functionId: '<FUNCTION_ID>',
    variableId: '<VARIABLE_ID>'
});
```
```server-deno
import { Client, Functions } 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 API key

const functions = new Functions(client);

const result = await functions.getVariable({
    functionId: '<FUNCTION_ID>',
    variableId: '<VARIABLE_ID>'
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Functions;

$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 API key

$functions = new Functions($client);

$result = $functions->getVariable(
    functionId: '<FUNCTION_ID>',
    variableId: '<VARIABLE_ID>'
);
```
```server-python
from appwrite.client import Client
from appwrite.services.functions import Functions
from appwrite.models import Variable

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 API key

functions = Functions(client)

result: Variable = functions.get_variable(
    function_id = '<FUNCTION_ID>',
    variable_id = '<VARIABLE_ID>'
)

print(result.model_dump())
```
```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 API key

functions = Functions.new(client)

result = functions.get_variable(
    function_id: '<FUNCTION_ID>',
    variable_id: '<VARIABLE_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 API key

Functions functions = new Functions(client);

Variable result = await functions.GetVariable(
    functionId: "<FUNCTION_ID>",
    variableId: "<VARIABLE_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 API key

Functions functions = Functions(client);

Variable result = await functions.getVariable(
    functionId: '<FUNCTION_ID>',
    variableId: '<VARIABLE_ID>',
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.coroutines.CoroutineCallback
import io.appwrite.services.Functions

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 API key

val functions = Functions(client)

val response = functions.getVariable(
    functionId = "<FUNCTION_ID>",
    variableId = "<VARIABLE_ID>"
)
```
```server-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Functions;

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 API key

Functions functions = new Functions(client);

functions.getVariable(
    "<FUNCTION_ID>", // functionId
    "<VARIABLE_ID>", // variableId
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        System.out.println(result);
    })
);
```
```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 API key

let functions = Functions(client)

let variable = try await functions.getVariable(
    functionId: "<FUNCTION_ID>",
    variableId: "<VARIABLE_ID>"
)
```
```server-go
package main

import (
    "fmt"
    "github.com/appwrite/sdk-for-go/appwrite"
)

client := appwrite.NewClient(
    appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"),
    appwrite.WithProject("<YOUR_PROJECT_ID>"),
    appwrite.WithKey("<YOUR_API_KEY>"),
)

functions := appwrite.NewFunctions(client)

response, error := functions.GetVariable(
    "<FUNCTION_ID>",
    "<VARIABLE_ID>",
)
```
```server-rust
use appwrite::Client;
use appwrite::services::Functions;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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 API key

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

    let result = functions.get_variable(
        "<FUNCTION_ID>",
        "<VARIABLE_ID>"
    ).await?;

    let _ = result;

    Ok(())
}
```

#### Update a variable

You can change a variable's `key`, `value`, or `secret` flag. Marking a variable as secret is one-way. Once set, the value is no longer readable from the Console or API.

```server-nodejs
import { Client, Functions } from '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 API key

const functions = new Functions(client);

const result = await functions.updateVariable({
    functionId: '<FUNCTION_ID>',
    variableId: '<VARIABLE_ID>',
    key: '<KEY>',
    value: '<VALUE>', // optional
    secret: false // optional
});
```
```server-deno
import { Client, Functions } 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 API key

const functions = new Functions(client);

const result = await functions.updateVariable({
    functionId: '<FUNCTION_ID>',
    variableId: '<VARIABLE_ID>',
    key: '<KEY>',
    value: '<VALUE>', // optional
    secret: false // optional
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Functions;

$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 API key

$functions = new Functions($client);

$result = $functions->updateVariable(
    functionId: '<FUNCTION_ID>',
    variableId: '<VARIABLE_ID>',
    key: '<KEY>',
    value: '<VALUE>', // optional
    secret: false // optional
);
```
```server-python
from appwrite.client import Client
from appwrite.services.functions import Functions
from appwrite.models import Variable

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 API key

functions = Functions(client)

result: Variable = functions.update_variable(
    function_id = '<FUNCTION_ID>',
    variable_id = '<VARIABLE_ID>',
    key = '<KEY>',
    value = '<VALUE>', # optional
    secret = False # optional
)

print(result.model_dump())
```
```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 API key

functions = Functions.new(client)

result = functions.update_variable(
    function_id: '<FUNCTION_ID>',
    variable_id: '<VARIABLE_ID>',
    key: '<KEY>',
    value: '<VALUE>', # optional
    secret: false # 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 API key

Functions functions = new Functions(client);

Variable result = await functions.UpdateVariable(
    functionId: "<FUNCTION_ID>",
    variableId: "<VARIABLE_ID>",
    key: "<KEY>",
    value: "<VALUE>", // optional
    secret: false // 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 API key

Functions functions = Functions(client);

Variable result = await functions.updateVariable(
    functionId: '<FUNCTION_ID>',
    variableId: '<VARIABLE_ID>',
    key: '<KEY>',
    value: '<VALUE>', // (optional)
    secret: false, // (optional)
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.coroutines.CoroutineCallback
import io.appwrite.services.Functions

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 API key

val functions = Functions(client)

val response = functions.updateVariable(
    functionId = "<FUNCTION_ID>",
    variableId = "<VARIABLE_ID>",
    key = "<KEY>",
    value = "<VALUE>", // optional
    secret = false // optional
)
```
```server-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Functions;

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 API key

Functions functions = new Functions(client);

functions.updateVariable(
    "<FUNCTION_ID>", // functionId
    "<VARIABLE_ID>", // variableId
    "<KEY>", // key
    "<VALUE>", // value (optional)
    false, // secret (optional)
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        System.out.println(result);
    })
);
```
```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 API key

let functions = Functions(client)

let variable = try await functions.updateVariable(
    functionId: "<FUNCTION_ID>",
    variableId: "<VARIABLE_ID>",
    key: "<KEY>",
    value: "<VALUE>", // optional
    secret: false // optional
)
```
```server-go
package main

import (
    "fmt"
    "github.com/appwrite/sdk-for-go/appwrite"
)

client := appwrite.NewClient(
    appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"),
    appwrite.WithProject("<YOUR_PROJECT_ID>"),
    appwrite.WithKey("<YOUR_API_KEY>"),
)

functions := appwrite.NewFunctions(client)

response, error := functions.UpdateVariable(
    "<FUNCTION_ID>",
    "<VARIABLE_ID>",
    "<KEY>",
    appwrite.WithUpdateVariableValue("<VALUE>"),
    appwrite.WithUpdateVariableSecret(false),
)
```
```server-rust
use appwrite::Client;
use appwrite::services::Functions;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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 API key

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

    let result = functions.update_variable(
        "<FUNCTION_ID>",
        "<VARIABLE_ID>",
        "<KEY>",
        Some("<VALUE>"), // optional
        Some(false) // optional
    ).await?;

    let _ = result;

    Ok(())
}
```

#### Delete a variable

```server-nodejs
import { Client, Functions } from '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 API key

const functions = new Functions(client);

const result = await functions.deleteVariable({
    functionId: '<FUNCTION_ID>',
    variableId: '<VARIABLE_ID>'
});
```
```server-deno
import { Client, Functions } 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 API key

const functions = new Functions(client);

const result = await functions.deleteVariable({
    functionId: '<FUNCTION_ID>',
    variableId: '<VARIABLE_ID>'
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Functions;

$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 API key

$functions = new Functions($client);

$result = $functions->deleteVariable(
    functionId: '<FUNCTION_ID>',
    variableId: '<VARIABLE_ID>'
);
```
```server-python
from appwrite.client import Client
from appwrite.services.functions import Functions

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 API key

functions = Functions(client)

result = functions.delete_variable(
    function_id = '<FUNCTION_ID>',
    variable_id = '<VARIABLE_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 API key

functions = Functions.new(client)

result = functions.delete_variable(
    function_id: '<FUNCTION_ID>',
    variable_id: '<VARIABLE_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 API key

Functions functions = new Functions(client);

await functions.DeleteVariable(
    functionId: "<FUNCTION_ID>",
    variableId: "<VARIABLE_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 API key

Functions functions = Functions(client);

await functions.deleteVariable(
    functionId: '<FUNCTION_ID>',
    variableId: '<VARIABLE_ID>',
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.coroutines.CoroutineCallback
import io.appwrite.services.Functions

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 API key

val functions = Functions(client)

val response = functions.deleteVariable(
    functionId = "<FUNCTION_ID>",
    variableId = "<VARIABLE_ID>"
)
```
```server-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Functions;

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 API key

Functions functions = new Functions(client);

functions.deleteVariable(
    "<FUNCTION_ID>", // functionId
    "<VARIABLE_ID>", // variableId
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        System.out.println(result);
    })
);
```
```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 API key

let functions = Functions(client)

let result = try await functions.deleteVariable(
    functionId: "<FUNCTION_ID>",
    variableId: "<VARIABLE_ID>"
)
```
```server-go
package main

import (
    "fmt"
    "github.com/appwrite/sdk-for-go/appwrite"
)

client := appwrite.NewClient(
    appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"),
    appwrite.WithProject("<YOUR_PROJECT_ID>"),
    appwrite.WithKey("<YOUR_API_KEY>"),
)

functions := appwrite.NewFunctions(client)

response, error := functions.DeleteVariable(
    "<FUNCTION_ID>",
    "<VARIABLE_ID>",
)
```
```server-rust
use appwrite::Client;
use appwrite::services::Functions;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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 API key

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

    functions.delete_variable(
        "<FUNCTION_ID>",
        "<VARIABLE_ID>"
    ).await?;

    Ok(())
}
```

### Read variables in your function

Once a variable is set, you can read it inside your function using your runtime language's standard environment lookup.

```server-nodejs
export default async ({ req, res, log }) => {
    return res.text(process.env.MY_VAR);
}
```
```server-deno
export default async ({ req, res, log }) => {
    return res.text(Deno.env.get('MY_VAR'));
}
```
```server-php
<?php

return function ($context) {
    return $context->res->text(getenv('MY_VAR'));
};
```
```server-python
def main(context):
    return context.res.text(os.environ['MY_VAR'])
```
```server-ruby
def main(context)
    return context.res.text(ENV['MY_VAR'])
end
```
```server-dotnet
namespace DotNetRuntime;

public class Handler {
    public async Task<RuntimeOutput> Main(RuntimeContext Context) {
        var myVar = Environment.GetEnvironmentVariable("MY_VAR");
        return Context.Res.Text(myVar);
    }
}
```
```server-dart
import 'dart:io';
import 'dart:async';

Future<dynamic> main(final context) async {
    return context.res.text(Platform.environment['MY_VAR']);
}
```
```server-kotlin
import io.openruntimes.kotlin.RuntimeContext
import io.openruntimes.kotlin.RuntimeOutput

class Handler {
    suspend fun main(context: RuntimeContext): RuntimeOutput {
        return context.res.text(System.getenv("MY_VAR"))
    }
}
```
```server-java
import io.openruntimes.java.RuntimeContext;
import io.openruntimes.java.RuntimeOutput;

public class Handler {
    public RuntimeOutput main(RuntimeContext context) throws Exception {
        return context.res.text(System.getenv("MY_VAR"));
    }
}
```
```server-swift
import Foundation

func main(context: RuntimeContext) async throws -> RuntimeOutput {
    return context.res.text(ProcessInfo.processInfo.environment["MY_VAR"] ?? "")
}
```
```server-go
package handler

import (
    "os"

    "github.com/open-runtimes/types-for-go/v4/openruntimes"
)

func Main(Context openruntimes.Context) openruntimes.Response {
    return Context.Res.Text(os.Getenv("MY_VAR"))
}
```
```server-rust
use openruntimes::{Context, Response};
use std::env;

pub fn main(context: Context) -> Response {
    context
        .res
        .text(env::var("MY_VAR").unwrap_or_default(), None, None)
}
```

### Appwrite-injected variables

Appwrite passes the following environment variables into every function deployment by default. They take precedence over your own variables, so do not set keys with the `APPWRITE_` prefix.

| 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_DEPLOYMENT_TYPE` | The deployment source type, such as `manual`, `cli`, or `vcs` | Both |
| `APPWRITE_FUNCTION_API_KEY` | The function API key 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 |
| `APPWRITE_FUNCTION_CPUS` | The CPU (runtime) specification of the running function | Both |
| `APPWRITE_FUNCTION_MEMORY` | The memory (runtime) specification of the running function | Both |
| `APPWRITE_VCS_REPOSITORY_ID` | The provider repository ID for VCS deployments | Both |
| `APPWRITE_VCS_REPOSITORY_NAME` | The provider repository name for VCS deployments | Both |
| `APPWRITE_VCS_REPOSITORY_OWNER` | The owner of the provider repository | Both |
| `APPWRITE_VCS_REPOSITORY_URL` | The URL of the provider repository | Both |
| `APPWRITE_VCS_REPOSITORY_BRANCH` | The branch used for the VCS deployment | Both |
| `APPWRITE_VCS_REPOSITORY_BRANCH_URL` | The URL of the branch used for the VCS deployment | Both |
| `APPWRITE_VCS_COMMIT_HASH` | The commit hash used for the VCS deployment | Both |
| `APPWRITE_VCS_COMMIT_MESSAGE` | The commit message used for the VCS deployment | Both |
| `APPWRITE_VCS_COMMIT_URL` | The URL of the commit used for the VCS deployment | Both |
| `APPWRITE_VCS_COMMIT_AUTHOR_NAME` | The name of the VCS commit author | Both |
| `APPWRITE_VCS_COMMIT_AUTHOR_URL` | The URL of the VCS commit author | Both |
| `APPWRITE_VCS_ROOT_DIRECTORY` | The root directory configured for the VCS deployment | Both |

VCS metadata variables are populated for Git deployments. For manual and CLI deployments, VCS fields may be empty.

During the build process, dynamic API keys are automatically provided as the environment variable `APPWRITE_FUNCTION_API_KEY`. This environment variable does not need to be initialized.

### Secret variables

Mark a variable as **Secret** to hide its value from the Console and API after creation. Only the function runtime can read the value at build and runtime. Team members and external integrations cannot retrieve it after creation.

You can mark a variable as secret either when you create it or by updating an existing variable. Marking a variable as secret cannot be reversed. To replace a secret value, delete the variable and create a new one with the same key.

### Limits

| Field | Limit |
|----------------|------------------------------------------------|
| Variable ID | 36 characters, `a-z A-Z 0-9 . - _` |
| Key | 255 characters |
| Value | 8192 characters |

---

## Examples

URL: 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.

#### 1. 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

**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.

**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.

**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.

**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.

**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.

**Rust**

Create a `Cargo.toml` file with the following contents. This file is used to manage your Rust project's dependencies.

```toml
[package]
name = "handler"
version = "0.1.0"
edition = "2021"
rust-version = "1.83"

[lib]
name = "handler"
path = "lib.rs"

[dependencies]
openruntimes = { version = "1.0", package = "openruntimes-types-for-rust" }
serde_json = "1.0"
ureq = { version = "2.10", features = ["json"] }
appwrite = "0.4"
tokio = { version = "1", features = ["rt"] }
urlencoding = "2.1"
```

`ureq` is the blocking HTTP client used by the currency example. `appwrite` and `tokio` are used by the voting and contact form examples that call the Appwrite SDK. `urlencoding` is used by the contact form example to decode form fields. Cargo picks up `Cargo.toml` automatically, so no extra build command is required.

#### Code

```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
```
```rust
use openruntimes::{Context, Response};
use serde_json::Value;

pub fn main(context: Context) -> Response {
    let path = context.req.path.as_str();

    if path == "/eur" || path == "/inr" {
        let base = if path == "/eur" { "EUR" } else { "INR" };
        let amount: f64 = context
            .req
            .query
            .get("amount")
            .and_then(|s| s.parse().ok())
            .unwrap_or(0.0);

        let url = format!(
            "https://api.exchangerate.host/latest?base={}&symbols=USD",
            base
        );

        let data: Value = ureq::get(&url)
            .call()
            .ok()
            .and_then(|r| r.into_json().ok())
            .unwrap_or(Value::Null);

        let rate = data
            .get("rates")
            .and_then(|r| r.get("USD"))
            .and_then(|v| v.as_f64())
            .unwrap_or(0.0);

        return context.res.text((amount * rate).to_string(), None, None);
    }

    context.res.text("Invalid path", None, None)
}
```

#### 2. 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` | Varchar | The name of the topic |
| `description` | Text | Long form description of the topic|

Create a Votes table with the following columns:

| Name | Type | Description |
|---------------|--------|------------------------------------------|
| `userId` | Varchar | The ID of the user who cast the vote |
| `topicId` | Varchar | The ID of the topic that was voted on |
| `vote` | Varchar | The vote cast by the user. Must be either "yes" or "no" |

#### Code

```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.equal('userId', vote.userId),
      Query.equal('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
from appwrite.id import ID
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(database_id='<DATABASE_ID>', table_id='<VOTES_TABLE_ID>', queries=[
        Query.equal('userId', vote['userId']),
        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)

    vote_row = tablesDB.create_row(
        database_id='<DATABASE_ID>',
        table_id='<VOTES_TABLE_ID>',
        row_id=ID.unique(),
        data={
            '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\TablesDB;
use Appwrite\ID;
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 = $tablesDB->listRows(
        databaseId: '<DATABASE_ID>',
        tableId: '<VOTES_TABLE_ID>',
        queries: [
            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);
    }

    $voteRow = $tablesDB->createRow(
        databaseId: '<DATABASE_ID>',
        tableId: '<VOTES_TABLE_ID>',
        rowId: ID::unique(),
        data: [
            'userId' => $vote['userId'],
            'topicId' => $vote['topicId'],
            'vote' => $vote['vote'],
        ]
    );

    return $context->res->json([
      'ok' => true,
      'message' => 'Vote cast.',
      'vote' => $voteRow
    ]);
};
```
```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(
        databaseId: '<DATABASE_ID>',
        tableId: '<VOTES_TABLE_ID>',
        queries: [
            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);
    }

    final voteRow = await tablesDB.createRow(
        databaseId: '<DATABASE_ID>',
        tableId: '<VOTES_TABLE_ID>',
        rowId: ID.unique(),
        data: {
            'userId': vote['userId'],
            'topicId': vote['topicId'],
            'vote': vote['vote'],
        }
    );

    return context.res.json({
      'ok': true,
      'message': 'Vote cast.',
      'vote': voteRow
    });
}
```
```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: '<DATABASE_ID>',
        table_id: '<VOTES_TABLE_ID>',
        queries: [
            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 = tablesDB.create_row(
        database_id: '<DATABASE_ID>',
        table_id: '<VOTES_TABLE_ID>',
        row_id: Appwrite::ID.unique(),
        data: {
            'userId' => vote['userId'],
            'topicId' => vote['topicId'],
            'vote' => vote['vote'],
        }
    )

    return context.res.json({
      'ok': true,
      'message': 'Vote cast.',
      'vote': vote_row
    })
end
```
```rust
use appwrite::error::AppwriteError;
use appwrite::id::ID;
use appwrite::query::Query;
use appwrite::services::tables_db::TablesDB;
use appwrite::Client;
use openruntimes::{Context, Response};
use serde_json::json;
use std::env;

pub fn main(context: Context) -> Response {
    let user_id = context.req.query.get("userId").cloned().unwrap_or_default();
    let topic_id = context.req.query.get("topicId").cloned().unwrap_or_default();
    let vote = context.req.query.get("vote").cloned().unwrap_or_default();

    if vote != "yes" && vote != "no" {
        return context.res.json(
            json!({ "ok": false, "message": "You must vote yes or no." }),
            Some(400),
            None,
        );
    }

    // Set project and set API key
    let client = Client::new()
        .set_endpoint(env::var("APPWRITE_FUNCTION_API_ENDPOINT").unwrap_or_default())
        .set_project(env::var("APPWRITE_FUNCTION_PROJECT_ID").unwrap_or_default())
        .set_key(
            context
                .req
                .headers
                .get("x-appwrite-key")
                .cloned()
                .unwrap_or_default(),
        );

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

    let runtime = tokio::runtime::Builder::new_current_thread()
        .enable_all()
        .build()
        .unwrap();

    let user_id_q = user_id.clone();
    let topic_id_q = topic_id.clone();

    let result: Result<Option<serde_json::Value>, AppwriteError> = runtime.block_on(async move {
        let existing = tables_db
            .list_rows(
                "<DATABASE_ID>",
                "<VOTES_TABLE_ID>",
                Some(vec![
                    Query::equal("userId", json!(user_id_q)).to_string(),
                    Query::equal("topicId", json!(topic_id_q)).to_string(),
                ]),
                None,
                None,
                None,
            )
            .await?;

        if existing.total > 0 {
            return Ok(None);
        }

        let row = tables_db
            .create_row(
                "<DATABASE_ID>",
                "<VOTES_TABLE_ID>",
                ID::unique(),
                json!({
                    "userId": user_id,
                    "topicId": topic_id,
                    "vote": vote,
                }),
                None,
                None,
            )
            .await?;

        Ok(Some(json!(row)))
    });

    match result {
        Ok(Some(row)) => context.res.json(
            json!({ "ok": true, "message": "Vote cast.", "vote": row }),
            None,
            None,
        ),
        Ok(None) => context.res.json(
            json!({ "ok": false, "message": "You have already voted on this topic." }),
            Some(400),
            None,
        ),
        Err(e) => {
            context.error(format!("Vote failed: {}", e));
            context.res.text("Vote failed", Some(500), None)
        }
    }
}
```

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.

#### 3. 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` | Varchar | The name of the message author |
| `email` | Email | The email of the message author |
| `content` | Text | The content of the message |

#### Code

```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</title>
  </head>
  <body>
    <form action="/" method="POST">
      <input type="text" id="name" name="name" placeholder="Name" required>
      <input type="email" id="email" name="email" placeholder="Email" required>
      <textarea id="content" name="content" placeholder="Message" required></textarea>
      <button type="submit">Submit</button>
    </form>
  </body>
</html>`

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: '<DATABASE_ID>',
        tableId: '<MESSAGES_TABLE_ID>',
        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 = '''<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Contact Form</title>
  </head>
  <body>
    <form action="/" method="POST">
      <input type="text" id="name" name="name" placeholder="Name" required>
      <input type="email" id="email" name="email" placeholder="Email" required>
      <textarea id="content" name="content" placeholder="Message" required></textarea>
      <button type="submit">Submit</button>
    </form>
  </body>
</html>
'''

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(database_id='<DATABASE_ID>', table_id='<MESSAGES_TABLE_ID>', row_id=ID.unique(), data=message)

        return context.res.text("Message sent")

    return context.res.text('Not found', 404)
```

```php
<?php

require(__DIR__ . '/../vendor/autoload.php');

use Appwrite\Client;
use Appwrite\Exception;
use Appwrite\Services\TablesDB;
use Appwrite\ID;

$html = '<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Contact Form</title>
  </head>
  <body>
    <form action="/" method="POST">
      <input type="text" id="name" name="name" placeholder="Name" required>
      <input type="email" id="email" name="email" placeholder="Email" required>
      <textarea id="content" name="content" placeholder="Message" required></textarea>
      <button type="submit">Submit</button>
    </form>
  </body>
</html>';

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(
        databaseId: '<DATABASE_ID>',
        tableId: '<MESSAGES_TABLE_ID>',
        rowId: ID::unique(),
        data: $message
    );

    return $context->res->text("Message sent");
  }

  return $context->res->text('Not found', 404);
};
```

```ruby
require "appwrite"

html = '''<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Contact Form</title>
  </head>
  <body>
    <form action="/" method="POST">
      <input type="text" id="name" name="name" placeholder="Name" required>
      <input type="email" id="email" name="email" placeholder="Email" required>
      <textarea id="content" name="content" placeholder="Message" required></textarea>
      <button type="submit">Submit</button>
    </form>
  </body>
</html>
'''

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(database_id: '<DATABASE_ID>', table_id: '<MESSAGES_TABLE_ID>', row_id: Appwrite::ID.unique(), data: 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';
import 'dart:io';

Future main(final context) async {
    final html = '''<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Contact Form</title>
  </head>
  <body>
    <form action="/" method="POST">
      <input type="text" id="name" name="name" placeholder="Name" required>
      <input type="email" id="email" name="email" placeholder="Email" required>
      <textarea id="content" name="content" placeholder="Message" required></textarea>
      <button type="submit">Submit</button>
    </form>
  </body>
</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') {
        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(
            databaseId: '<DATABASE_ID>',
            tableId: '<MESSAGES_TABLE_ID>',
            rowId: ID.unique(),
            data: message
        );

        return context.res.text("Message sent");
    }

    return context.res.text('Not found', 404);
}
```

```rust
use appwrite::id::ID;
use appwrite::services::tables_db::TablesDB;
use appwrite::Client;
use openruntimes::{Context, Response};
use serde_json::json;
use std::collections::HashMap;
use std::env;

const HTML: &str = r#"<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Contact Form</title>
  </head>
  <body>
    <form action="/" method="POST">
      <input type="text" id="name" name="name" placeholder="Name" required>
      <input type="email" id="email" name="email" placeholder="Email" required>
      <textarea id="content" name="content" placeholder="Message" required></textarea>
      <button type="submit">Submit</button>
    </form>
  </body>
</html>"#;

fn parse_form(body: &str) -> HashMap<String, String> {
    body.split('&')
        .filter_map(|pair| {
            let mut split = pair.splitn(2, '=');
            // application/x-www-form-urlencoded encodes spaces as `+`, so
            // replace `+` before percent-decoding any `%xx` sequences.
            let key = split.next()?.replace('+', " ");
            let value = split.next().unwrap_or("").replace('+', " ");
            Some((
                urlencoding::decode(&key).ok()?.into_owned(),
                urlencoding::decode(&value).ok()?.into_owned(),
            ))
        })
        .collect()
}

pub fn main(context: Context) -> Response {
    if context.req.method == "GET" {
        let mut headers = HashMap::new();
        headers.insert("content-type".to_string(), "text/html".to_string());
        return context.res.text(HTML, Some(200), Some(headers));
    }

    let content_type = context
        .req
        .headers
        .get("content-type")
        .map(|s| s.as_str())
        .unwrap_or("");

    if context.req.method == "POST" && content_type == "application/x-www-form-urlencoded" {
        let body = context.req.body_text();
        let form = parse_form(&body);

        let client = Client::new()
            .set_endpoint(env::var("APPWRITE_FUNCTION_API_ENDPOINT").unwrap_or_default())
            .set_project(env::var("APPWRITE_FUNCTION_PROJECT_ID").unwrap_or_default())
            .set_key(
                context
                    .req
                    .headers
                    .get("x-appwrite-key")
                    .cloned()
                    .unwrap_or_default(),
            );

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

        let runtime = tokio::runtime::Builder::new_current_thread()
            .enable_all()
            .build()
            .unwrap();

        let result = runtime.block_on(async move {
            tables_db
                .create_row(
                    "<DATABASE_ID>",
                    "<MESSAGES_TABLE_ID>",
                    ID::unique(),
                    json!({
                        "name": form.get("name").cloned().unwrap_or_default(),
                        "email": form.get("email").cloned().unwrap_or_default(),
                        "content": form.get("content").cloned().unwrap_or_default(),
                    }),
                    None,
                    None,
                )
                .await
        });

        return match result {
            Ok(_) => context.res.text("Message sent", None, None),
            Err(e) => {
                context.error(format!("Failed to save message: {}", e));
                context.res.text("Failed to save message", Some(500), None)
            }
        };
    }

    context.res.text("Not found", Some(404), None)
}
```

Use the function by navigating to the function URL in the browser. Submit the form to store the message in the table.

---

## Execution

URL: 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.

### Execution modes

Appwrite Functions support two execution modes: **synchronous** and **asynchronous**.

#### Synchronous executions

Synchronous executions are those where Appwrite makes the request to the function runtime synchronously and waits for the response. The client making the request will wait until the function completes and receives the response directly.

Synchronous executions are created via:
- The [Create execution](/docs/references/cloud/client-web/functions#createExecution) endpoint where the `async` parameter is `false`
- Requests to custom or auto-generated [domains](/docs/products/functions/execute#domains)

Synchronous executions:
- Return response bodies and headers directly to the client
- Have a **30-second hard timeout limit** to discourage slow API calls that cause poor user experience in apps
- Are ideal for short-running operations where you need response data quickly

#### Asynchronous executions

Asynchronous executions are added to a queue and processed by the function worker as background jobs.

Asynchronous executions are created via:
- The [Create execution](/docs/references/cloud/client-web/functions#createExecution) endpoint where the `async` parameter is `true`
- Event triggers (when functions are triggered by [platform events](/docs/apis/events))
- Scheduled executions (cron jobs or delayed executions)

Asynchronous executions:
- Have no timeout limitations beyond your function's configured timeout
- Are ideal for background processing and event-driven workflows

**Response body storage**

Response bodies and headers are not stored anywhere, so they are only ever returned via synchronous executions.

### 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.fra.appwrite.run
```

When requests are made to this domain, whether through a browser or through an HTTP request,
the request information like request URL, request headers, and request body will be passed to the function.

```bash
curl -X POST https://64d4d22db370ae41a32e.fra.appwrite.run \
-H "X-Custom-Header: 123" \
-H "x-appwrite-user-jwt: <YOUR_JWT_KEY>" \
-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 the 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

You can invoke your Appwrite Functions directly from the [Appwrite SDKs](/docs/sdks).

**Client SDKs**

```client-web
import { Client, Functions } from "appwrite";

const client = new Client();

const functions = new Functions(client);

client
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<PROJECT_ID>') // Your project ID
;

const promise = functions.createExecution({
    functionId: '<FUNCTION_ID>',
    body: '<BODY>',  // optional
    async: false,  // optional
    xpath: '<PATH>',  // 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('<PROJECT_ID>') // Your project ID
  ;
  Future result = functions.createExecution(
    functionId: '<FUNCTION_ID>',
    body: '<BODY>', // optional
    xasync: false, // optional
    path: '<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("<PROJECT_ID>") // Your project ID

let functions = Functions(client)

let execution = try await functions.createExecution(
  functionId: "<FUNCTION_ID>",
  body: "<BODY>", // optional
  async: xfalse, // optional
  path: "<PATH>", // optional
  method: "GET", // optional
  headers: [:] // optional
)
```
```kotlin
import io.appwrite.Client
import io.appwrite.services.Functions

val client = Client(context)
    .setProject("<PROJECT_ID>") // Your project ID

val functions = Functions(client)

val response = functions.createExecution(
    functionId = "<FUNCTION_ID>",
    body = "<BODY>", // optional
    async = false, // optional
    path = "<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("<PROJECT_ID>"); // Your project ID

Functions functions = new Functions(client);

functions.createExecution(
    "<FUNCTION_ID>", // functionId
    "<BODY>", // body (optional)
    false, // async (optional)
    "<PATH>", // 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());
    })
);
```

**Server SDKs**

```server-nodejs
const sdk = require('node-appwrite');

// Init SDK
const client = new sdk.Client();

const functions = new sdk.Functions(client);

client
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<PROJECT_ID>') // Your project ID
;

const promise = functions.createExecution({
    functionId: '<FUNCTION_ID>',
    body: '<BODY>',  // optional
    async: false,  // optional
    xpath: '<PATH>',  // optional
    method: sdk.ExecutionMethod.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
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<PROJECT_ID>') // Your project ID
;

const promise = functions.createExecution({
    functionId: '<FUNCTION_ID>',
    body: '<BODY>',  // optional
    async: false,  // optional
    xpath: '<PATH>',  // optional
    method: sdk.ExecutionMethod.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("<PROJECT_ID>"),
	)

	functions := appwrite.NewFunctions(client)

	execution, err := functions.CreateExecution(
		"<FUNCTION_ID>", // functionId
		functions.WithCreateExecutionBody("<BODY>"), // body (optional)
		functions.WithCreateExecutionAsync(false), // async (optional)
		functions.WithCreateExecutionPath("<PATH>"), // path (optional)
		functions.WithCreateExecutionMethod("GET"), // method (optional)
		functions.WithCreateExecutionHeaders(map[string]interface{}{})) // headers (optional)

	fmt.Println(execution)

	if err != nil {
		fmt.Println(err)
	}
}
```
```php
<?php

use Appwrite\Client;
use Appwrite\Services\Functions;

$client = new Client();

$client
    ->setProject('<PROJECT_ID>') // Your project ID
;

$functions = new Functions($client);

$result = $functions->createExecution(
    functionId: '<FUNCTION_ID>',
    body: '<BODY>',  // optional
    async: false,  // optional
    path: '<PATH>',  // optional
    method: 'GET',  // optional
    headers: [] // optional
);
```
```python
from appwrite.client import Client
from appwrite.services.functions import Functions

client = Client()

(client
  .set_project('<PROJECT_ID>') # Your project ID
)

functions = Functions(client)

result = functions.create_execution(
    function_id = '<FUNCTION_ID>',
    body = '<BODY>',  # optional
    async = False,  # optional
    path = '<PATH>',  # optional
    method = 'GET',  # optional
    headers = {} # optional
)
```
```ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_project('<PROJECT_ID>') # Your project ID

functions = Functions.new(client)

response = functions.create_execution(
    function_id: '<FUNCTION_ID>',
    body: '<BODY>',  # optional
    async: false,  # optional
    path: '<PATH>',  # optional
    method: 'GET',  # optional
    headers: {} # optional
)

puts response.inspect
```
```csharp
using Appwrite;
using Appwrite.Services;
using Appwrite.Models;

var client = new Client()
    .SetProject("<PROJECT_ID>"); // Your project ID

var functions = new Functions(client);

Execution result = await functions.CreateExecution(
    functionId: "<FUNCTION_ID>"
    body: "<BODY>" // optional
    async: false // optional
    path: "<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('<PROJECT_ID>') // Your project ID
  ;

  Future result = functions.createExecution(
    functionId: '<FUNCTION_ID>',
    body: '<BODY>', // optional
    xasync: false, // optional
    path: '<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("<PROJECT_ID>"); // Your project ID

Functions functions = new Functions(client);

functions.createExecution(
    "<FUNCTION_ID>", // functionId
    "<BODY>", // body (optional)
    false, // async (optional)
    "<PATH>", // 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("<PROJECT_ID>"); // Your project ID

Functions functions = new Functions(client);

functions.createExecution(
    "<FUNCTION_ID>", // functionId
    "<BODY>", // body (optional)
    false, // async (optional)
    "<PATH>", // 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("<PROJECT_ID>") // Your project ID

let functions = Functions(client)

let execution = try await functions.createExecution(
  functionId: "<FUNCTION_ID>",
  body: "<BODY>", // optional
  async: xfalse, // optional
  path: "<PATH>", // optional
  method: "GET", // optional
  headers: [:] // optional
)
```
```server-rust
use appwrite::Client;
use appwrite::services::functions::Functions;
use appwrite::enums::execution_method::ExecutionMethod;
use serde_json::json;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new()
        .set_project("<PROJECT_ID>");

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

    let execution = functions.create_execution(
        "<FUNCTION_ID>",                             // functionId
        Some("<BODY>"),                              // body (optional)
        Some(false),                                 // async (optional)
        Some("<PATH>"),                              // path (optional)
        Some(ExecutionMethod::GET),                  // method (optional)
        Some(json!({})),                             // headers (optional)
        None,                                        // scheduledAt (optional)
    ).await?;

    println!("{:?}", execution);
    Ok(())
}
```

### 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 a modal below.

You'll be able to mock executions by configuring the path, method, headers, and body.

![Create project screen](/images/docs/functions/execution/execute-function.avif)

### 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/apis/events).

You can use one of the following events.

**Authentication**

- Name
- Description

---

- `teams.*`
- This event triggers on any teams event.
  Returns [Team Object](/docs/references/cloud/models/team)

---

- `teams.*.create`
- This event triggers when a team is created.
  Returns [Team Object](/docs/references/cloud/models/team)

---

- `teams.*.delete`
- This event triggers when a team is deleted.
  Returns [Team Object](/docs/references/cloud/models/team)

---

- `teams.*.memberships.*`
- This event triggers on any team memberships event.
  Returns [Membership Object](/docs/references/cloud/models/membership)

---

- `teams.*.memberships.*.create`
- This event triggers when a membership is created.
  Returns [Membership Object](/docs/references/cloud/models/membership)

---

- `teams.*.memberships.*.delete`
- This event triggers when a membership is deleted.
  Returns [Membership Object](/docs/references/cloud/models/membership)

---

- `teams.*.memberships.*.update`
- This event triggers when a membership is updated.
  Returns [Membership Object](/docs/references/cloud/models/membership)

---

- `teams.*.memberships.*.update.status`
- This event triggers when a team memberships status is updated.
  Returns [Membership Object](/docs/references/cloud/models/membership)

---

- `teams.*.update`
- This event triggers when a team is updated.
  Returns [Team Object](/docs/references/cloud/models/team)

---

- `teams.*.update.prefs`
- This event triggers when a team's preferences are updated.
  Returns [Team Object](/docs/references/cloud/models/team)

---

- `users.*`
- This event triggers on any user's event.
  Returns [User Object](/docs/references/cloud/models/user)

---

- `users.*.create`
- This event triggers when a user is created.
  Returns [User Object](/docs/references/cloud/models/user)

---

- `users.*.delete`
- This event triggers when a user is deleted.
  Returns [User Object](/docs/references/cloud/models/user)

---

- `users.*.recovery.*`
- This event triggers on any user's recovery token event.
  Returns [Token Object](/docs/references/cloud/models/token)

---

- `users.*.recovery.*.create`
- This event triggers when a recovery token for a user is created.
  Returns [Token Object](/docs/references/cloud/models/token)

---

- `users.*.recovery.*.update`
- This event triggers when a recovery token for a user is validated.
  Returns [Token Object](/docs/references/cloud/models/token)

---

- `users.*.sessions.*`
- This event triggers on any user's sessions event.
  Returns [Session Object](/docs/references/cloud/models/session)

---

- `users.*.sessions.*.create`
- This event triggers when a session for a user is created.
  Returns [Session Object](/docs/references/cloud/models/session)

---

- `users.*.sessions.*.delete`
- This event triggers when a session for a user is deleted.
  Returns [Session Object](/docs/references/cloud/models/session)

---

- `users.*.update`
- This event triggers when a user is updated.
  Returns [User Object](/docs/references/cloud/models/user)

---

- `users.*.update.email`
- This event triggers when a user's email address is updated.
  Returns [User Object](/docs/references/cloud/models/user)

---

- `users.*.update.name`
- This event triggers when a user's name is updated.
  Returns [User Object](/docs/references/cloud/models/user)

---

- `users.*.update.password`
- This event triggers when a user's password is updated.
  Returns [User Object](/docs/references/cloud/models/user)

---

- `users.*.update.prefs`
- This event triggers when a user's preferences is updated.
  Returns [User Object](/docs/references/cloud/models/user)

---

- `users.*.update.status`
- This event triggers when a user's status is updated.
  Returns [User Object](/docs/references/cloud/models/user)

---

- `users.*.verification.*`
- This event triggers on any user's verification token event.
  Returns [Token Object](/docs/references/cloud/models/token)

---

- `users.*.verification.*.create`
- This event triggers when a verification token for a user is created.
  Returns [Token Object](/docs/references/cloud/models/token)

---

- `users.*.verification.*.update`
- This event triggers when a verification token for a user is validated.
  Returns [Token Object](/docs/references/cloud/models/token)

**Databases**

- Name
- Description

---

- `tablesdb.*`
- This event triggers on any database event.
  Returns [Database Object](/docs/references/cloud/models/database)

---

- `tablesdb.*.tables.*`
- This event triggers on any table event.
  Returns [Table Object](/docs/references/cloud/models/table)

---

- `tablesdb.*.tables.*.columns.*`
- This event triggers on any columns event.
  Returns [Column Object](/docs/references/cloud/models/columnList)

---

- `tablesdb.*.tables.*.columns.*.create`
- This event triggers when a column is created.
  Returns [Column Object](/docs/references/cloud/models/columnList)

---

- `tablesdb.*.tables.*.columns.*.update`
- This event triggers when a column is updated.
  Returns [Column Object](/docs/references/cloud/models/columnList)

---

- `tablesdb.*.tables.*.columns.*.delete`
- This event triggers when a column is deleted.
  Returns [Column Object](/docs/references/cloud/models/columnList)

---

- `tablesdb.*.tables.*.create`
- This event triggers when a table is created.
  Returns [Table Object](/docs/references/cloud/models/table)

---

- `tablesdb.*.tables.*.delete`
- This event triggers when a table is deleted.
  Returns [Table Object](/docs/references/cloud/models/table)

---

- `tablesdb.*.tables.*.rows.*`
- This event triggers on any rows event.
  Returns [Row Object](/docs/references/cloud/models/row)

---

- `tablesdb.*.tables.*.rows.*.create`
- This event triggers when a row is created.
  Returns [Row Object](/docs/references/cloud/models/row)

---

- `tablesdb.*.tables.*.rows.*.delete`
- This event triggers when a row is deleted.
  Returns [Row Object](/docs/references/cloud/models/row)

---

- `tablesdb.*.tables.*.rows.*.update`
- This event triggers when a row is updated.
  Returns [Row Object](/docs/references/cloud/models/row)

---

- `tablesdb.*.tables.*.rows.*.upsert`
- This event triggers when a row is upserted.
  Returns [Row Object](/docs/references/cloud/models/row)

---

- `tablesdb.*.tables.*.indexes.*`
- This event triggers on any indexes event.
  Returns [Index Object](/docs/references/cloud/models/columnIndex)

---

- `tablesdb.*.tables.*.indexes.*.create`
- This event triggers when an index is created.
  Returns [Index Object](/docs/references/cloud/models/columnIndex)

---

- `tablesdb.*.tables.*.indexes.*.update`
- This event triggers when an index is updated.
  Returns [Index Object](/docs/references/cloud/models/columnIndex)

---

- `tablesdb.*.tables.*.indexes.*.delete`
- This event triggers when an index is deleted.
  Returns [Index Object](/docs/references/cloud/models/columnIndex)

---

- `tablesdb.*.tables.*.update`
- This event triggers when a table is updated.
  Returns [Table Object](/docs/references/cloud/models/table)

---

- `tablesdb.*.create`
- This event triggers when a database is created.
  Returns [Database Object](/docs/references/cloud/models/database)

---

- `tablesdb.*.delete`
- This event triggers when a database is deleted.
  Returns [Database Object](/docs/references/cloud/models/database)

---

- `tablesdb.*.update`
- This event triggers when a database is updated.
  Returns [Database Object](/docs/references/cloud/models/database)

**Storage**

- Name
- Description

---

- `buckets.*`
- This event triggers on any buckets event.
  Returns [Bucket Object](/docs/references/cloud/models/bucket)

---

- `buckets.*.create`
- This event triggers when a bucket is created.
  Returns [Bucket Object](/docs/references/cloud/models/bucket)

---

- `buckets.*.delete`
- This event triggers when a bucket is deleted.
  Returns [Bucket Object](/docs/references/cloud/models/bucket)

---

- `buckets.*.files.*`
- This event triggers on any files event.
  Returns [File Object](/docs/references/cloud/models/file)

---

- `buckets.*.files.*.create`
- Since the Appwrite SDK chunks files in 5MB increments, this event will trigger for each 5MB chunk. A file is fully uploaded when `chunksTotal` equals `chunksUploaded`.
  Returns [File Object](/docs/references/cloud/models/file)

---

- `buckets.*.files.*.delete`
- This event triggers when a file is deleted.
  Returns [File Object](/docs/references/cloud/models/file)

---

- `buckets.*.files.*.update`
- This event triggers when a file is updated.
  Returns [File Object](/docs/references/cloud/models/file)

---

- `buckets.*.update`
- This event triggers when a bucket is updated.
  Returns [Bucket Object](/docs/references/cloud/models/bucket)

**Functions**

- Name
- Description

---

- `functions.*`
- This event triggers on any functions event.
  Returns [Function Object](/docs/references/cloud/models/function)

---

- `functions.*.create`
- This event triggers when a function is created.
  Returns [Function Object](/docs/references/cloud/models/function)

---

- `functions.*.delete`
- This event triggers when a function is deleted.
  Returns [Function Object](/docs/references/cloud/models/function)

---

- `functions.*.deployments.*`
- This event triggers on any deployments event.
  Returns [Deployment Object](/docs/references/cloud/models/deployment)

---

- `functions.*.deployments.*.create`
- This event triggers when a deployment is created.
  Returns [Deployment Object](/docs/references/cloud/models/deployment)

---

- `functions.*.deployments.*.delete`
- This event triggers when a deployment is deleted.
  Returns [Deployment Object](/docs/references/cloud/models/deployment)

---

- `functions.*.deployments.*.update`
- This event triggers when a deployment is updated.
  Returns [Deployment Object](/docs/references/cloud/models/deployment)

---

- `functions.*.executions.*`
- This event triggers on any executions event.
  Returns [Execution Object](/docs/references/cloud/models/execution)

---

- `functions.*.executions.*.create`
- This event triggers when an execution is created.
  Returns [Execution Object](/docs/references/cloud/models/execution)

---

- `functions.*.executions.*.delete`
- This event triggers when an execution is deleted.
  Returns [Execution Object](/docs/references/cloud/models/execution)

---

- `functions.*.executions.*.update`
- This event triggers when an execution is updated.
  Returns [Execution Object](/docs/references/cloud/models/execution)

---

- `functions.*.update`
- This event triggers when a function is updated.
  Returns [Function Object](/docs/references/cloud/models/function)

**Messaging**

- Name
- Description

---

- `providers.*`
- This event triggers on any providers event.
  Returns [Provider Object](/docs/references/cloud/models/provider)

---

- `providers.*.create`
- This event triggers when a provider is created.
  Returns [Provider Object](/docs/references/cloud/models/provider)

---

- `providers.*.delete`
- This event triggers when a provider is deleted.
  Returns [Provider Object](/docs/references/cloud/models/provider)

---

- `providers.*.update`
- This event triggers when a provider is updated.
  Returns [Provider Object](/docs/references/cloud/models/provider)

---

- `topics.*`
- This event triggers on any topic event.
  Returns [Topic Object](/docs/references/cloud/models/topic)

---

- `topics.*.create`
- This event triggers when a topic is created.
  Returns [Topic Object](/docs/references/cloud/models/topic)

---

- `topics.*.delete`
- This event triggers when a topic is deleted.
  Returns [Topic Object](/docs/references/cloud/models/topic)

---

- `topics.*.update`
- This event triggers when a topic is updated.
  Returns [Topic Object](/docs/references/cloud/models/topic)

---

- `topics.*.subscribers.*.create`
- This event triggers when a subscriber to a topic is created.
  Returns [Topic Object](/docs/references/cloud/models/topic)

---

- `topics.*.subscribers.*.delete`
- This event triggers when a subscriber to a topic is deleted.
  Returns [Topic Object](/docs/references/cloud/models/topic)

---

- `messages.*`
- This event triggers on any message event.
  Returns [Message Object](/docs/references/cloud/models/message)

---

- `messages.*.create`
- This event triggers when a message is created.
  Returns [Message Object](/docs/references/cloud/models/message)

---

- `messages.*.delete`
- This event triggers when a message is deleted.
  Returns [Message Object](/docs/references/cloud/models/message)

---

- `messages.*.update`
- This event triggers when a message is updated.
  Returns [Message Object](/docs/references/cloud/models/message)

### 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

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.

**Console**

To schedule an execution, navigate to **Your function** > **Executions** > **Execute now** > **Schedule** in the Appwrite Console.

![Scheduled execution details screen](/images/docs/functions/execution/scheduled-execution-function.avif)

**Client SDK**

You can also schedule your function executions using a supported [Client SDK](/docs/sdks/#client).

```client-web
import { Client, Functions, ExecutionMethod } from "appwrite";

const client = new Client()
    .setProject('<PROJECT_ID>'); // Your project ID

const functions = new Functions(client);

const result = await functions.createExecution({
    functionId: '<FUNCTION_ID>',
    body: '<BODY>', // optional
    async: true, // Scheduled executions need to be async
    xpath: '<PATH>', // 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('<PROJECT_ID>'); // Your project ID

Functions functions = Functions(client);

Execution result = await functions.createExecution(
    functionId: '<FUNCTION_ID>', // functionId
    body: '<BODY>', // optional
    xasync: true, // Scheduled executions need to be async
    path: '<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('<PROJECT_ID>'); // Your project ID

const functions = new Functions(client);

const result = await functions.createExecution({
    functionId: '<FUNCTION_ID>',
    body: '<BODY>', // optional
    async: true, // Scheduled executions need to be async
    xpath: '<PATH>', // 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("<PROJECT_ID>") // Your project ID

let functions = Functions(client)

let execution = try await functions.createExecution(
    functionId: "<FUNCTION_ID>", // functionId
    body: "<BODY>", // optional
    async: true, // Scheduled executions need to be async
    path: "<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("<PROJECT_ID>") // Your project ID

val functions = Functions(client)

val result = functions.createExecution(
    functionId = "<FUNCTION_ID>", // functionId
    body = "<BODY>", // (optional)
    async = true, // Scheduled executions need to be async
    path = "<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: "<FUNCTION_ID>",
        body: "<BODY>",
        async: true,
        path: "<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://<REGION>.cloud.appwrite.io/v1/functions/<FUNCTION_ID>/executions HTTP/1.1
X-Appwrite-Project: "<PROJECT_ID>"
X-Appwrite-Response-Format: 1.5.0
Content-Type: application/json

{
  "body": "<BODY>",
  "async": true,
  "path": "<PATH>",
  "method": "GET",
  "headers": {},
  "scheduledAt": "2025-10-15T06:38:00.000+00:00"
}
```

**Server SDK**

You can also schedule your function executions using a supported [Server SDK](/docs/sdks/#server).

```server-nodejs
const sdk = require('node-appwrite');

// Init SDK
const client = new sdk.Client();

const functions = new sdk.Functions(client);

client
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<PROJECT_ID>') // Your project ID
;

const promise = functions.createExecution({
    functionId: '<FUNCTION_ID>',
    body: '<BODY>',
    async: true,
    xpath: '<PATH>',
    method: sdk.ExecutionMethod.GET,
    headers: {},
    scheduledAt: '<SCHEDULED_AT>'
});

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
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<PROJECT_ID>') // Your project ID
;

const promise = functions.createExecution({
    functionId: '<FUNCTION_ID>',
    body: '<BODY>',
    async: true,
    xpath: '<PATH>',
    method: sdk.ExecutionMethod.GET,
    headers: {},
    scheduledAt: '<SCHEDULED_AT>'
});

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("<PROJECT_ID>"),
	)

	functions := appwrite.NewFunctions(client)

	execution, err := functions.CreateExecution(
		"<FUNCTION_ID>", // functionId
		functions.WithCreateExecutionBody("<BODY>"), // body (optional)
		functions.WithCreateExecutionAsync(true), // Scheduled executions need to be async
		functions.WithCreateExecutionPath("<PATH>"), // 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
<?php

use Appwrite\Client;
use Appwrite\Services\Functions;

$client = new Client();

$client
    ->setProject('<PROJECT_ID>') // Your project ID
;

$functions = new Functions($client);

$result = $functions->createExecution(
    '<FUNCTION_ID>', // functionId
    '<BODY>', // body (optional)
    true, // Scheduled executions need to be async
    '<PATH>', // 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('<PROJECT_ID>') # Your project ID
)

functions = Functions(client)

result = functions.create_execution(
    function_id = '<FUNCTION_ID>', # functionId
    body = '<BODY>', # body (optional)
    async = True, # Scheduled executions need to be async
    path = '<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('<PROJECT_ID>') # Your project ID

functions = Functions.new(client)

response = functions.create_execution(
    function_id: '<FUNCTION_ID>', # functionId
    body: '<BODY>', # body (optional)
    async: true, # Scheduled executions need to be async
    path: '<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("<PROJECT_ID>"); // Your project ID

var functions = new Functions(client);

Execution result = await functions.CreateExecution(
    functionId: "<FUNCTION_ID>", // functionId
    body: "<BODY>", // body (optional)
    async: true, // Scheduled executions need to be async
    path: "<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('<PROJECT_ID>') // Your project ID
  ;

  Future result = functions.createExecution(
    functionId: '<FUNCTION_ID>', // functionId
    body: '<BODY>', // (optional)
    xasync: true, // Scheduled executions need to be async
    path: '<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("<PROJECT_ID>"); // Your project ID

Functions functions = new Functions(client);

functions.createExecution(
    "<FUNCTION_ID>", // functionId
    "<BODY>", // body (optional)
    true, // Scheduled executions need to be async
    "<PATH>", // 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("<PROJECT_ID>"); // Your project ID

Functions functions = new Functions(client);

functions.createExecution(
    "<FUNCTION_ID>", // functionId
    "<BODY>", // body (optional)
    true, // Scheduled executions need to be async
    "<PATH>", // 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("<PROJECT_ID>") // Your project ID

let functions = Functions(client)

let execution = try await functions.createExecution(
  functionId: "<FUNCTION_ID>",
  body: "<BODY>", // optional
  async: true, // Scheduled executions need to be async
  path: "<PATH>", // optional
  method: .gET, // optional
  headers: [:] // optional
  scheduledAt: "2020-10-15T06:38:00.000+00:00" // Schedule execution (optional)
)
```
```server-rust
use appwrite::Client;
use appwrite::services::functions::Functions;
use appwrite::enums::execution_method::ExecutionMethod;
use serde_json::json;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new()
        .set_project("<PROJECT_ID>");

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

    let execution = functions.create_execution(
        "<FUNCTION_ID>",                             // functionId
        Some("<BODY>"),                              // body (optional)
        Some(true),                                  // async - scheduled executions need to be async
        Some("<PATH>"),                              // path (optional)
        Some(ExecutionMethod::GET),                  // method (optional)
        Some(json!({})),                             // headers (optional)
        Some("2020-10-15T06:38:00.000+00:00"),       // scheduledAt (optional)
    ).await?;

    println!("{:?}", execution);
    Ok(())
}
```

### Permissions

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/security/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

URL: 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

In your function's **Executions** tab, you will see a table of your recent executions.
Here's the information shown on this 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/apis/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

#### Execution status

Each execution can have one of the follow status.

- 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.

### Execution details

When you click on an execution, you will be taken to an execution detail screen.

![Execution details screen](/images/docs/functions/execution/execute-function.avif)

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.

**Access control**

Only the user who created an execution can retrieve it using the [Get execution](/docs/references/cloud/client-web/functions#getExecution) endpoint.

### 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

URL: 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
You can create Appwrite Functions in three different ways.

**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.

![Create project screen](/images/docs/functions/template.avif)

**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`.

  ```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 .
  ```

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.

**CLI**

You can create functions using the CLI without needing to access the Console.

To deploy your function with the Appwrite CLI, use the `appwrite init functions` command to create a starter function and paste your code into the generated file and folder.

```sh
appwrite init functions
```

To deploy the generated code, add any dependencies and push the function using the following command:

```sh
appwrite push functions
```

[Learn more about the CLI functions commands](/docs/tooling/command-line/functions#commands)

### Configuration

#### 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
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**.

**Redeployment required**

This change requires your function to be redeployed to take effect.

#### Build configuration
You can update the entrypoint file and build settings of your function by navigating to your function >
**Settings** > **Configuration**.

**Redeployment required**

This change requires your function to be redeployed to take effect.

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.

#### Resource limits

Under **Settings** - **Resource limits**, you can set **build** and **runtime** specifications independently. The build spec applies while your deployment is being built and packaged; the runtime spec applies to each function execution. Both use the same CPU and memory tiers on Cloud. This lets you align compute with heavy dependency installs or compilation without over-provisioning every invocation.

On Appwrite Cloud, customizing specifications requires the **Pro** plan. See [Compute](/docs/advanced/billing/compute) for tiers, GB-hours, and pricing.

#### Build timeout

On Appwrite Cloud, the **build** phase of each deployment must complete within your plan’s **maximum build duration** (for example, 15 minutes on Free and 45 minutes on Pro and Scale). See [Build timeouts](/docs/advanced/billing/compute#build-timeouts) and the [pricing page](/pricing).

#### Git integration
You can update the entrypoint file and build settings of your function by navigating to your function > **Settings** > **Configuration**.

**Redeployment required**

This change requires your function to be redeployed to take effect.

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
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
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/security/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
Functions can be triggered by [platform events](/docs/apis/events) which reflect changes
that occur in your Appwrite project.
You can configure events triggers by navigating to your function > **Settings** > **Events**.

#### 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 |

#### 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

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.

[Learn more about scopes](/docs/partners/project/api-keys#scopes)

---

## Start with Functions

URL: https://appwrite.io/docs/products/functions/quick-start

You can create and execute your first Appwrite Function in minutes.

### Create function

Before deploying your function with Git, create a new function attached to your Git repository.

![Create function screen](/images/docs/functions/quick-start/create-function.avif)

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
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.

![Execution-screen](/images/docs/functions/quick-start/function-execution.avif)

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
Use this your first function as a springboard to explore the flexible and powerful features of Appwrite Functions.

- [Template](/docs/products/functions/templates): Get a template function up and running with a single click.
- [Develop](/docs/products/functions/develop): Learn about developing your own Appwrite Function.
- [Deploy](/docs/products/functions/deploy-from-git): Learn to deploy Appwrite Functions from Git.
- [Execute](/docs/products/functions/execute): Explore the different ways an Appwrite Function can be executed.
- [Runtimes](/docs/products/functions/runtimes): Write Appwrite Functions in your favorite language.

---

## Runtimes

URL: 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
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.

**All runtimes**

| &nbsp; | Name | Versions | Architectures |
| --- | --- | --- | --- |
| | [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` `node-25` | x86 / arm64 / armv7 / armv8 |
| | [Bun](https://hub.docker.com/r/openruntimes/bun/tags) | `bun-1.0` `bun-1.1` `bun-1.3` | x86 / arm64 |
| | [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` `deno-2.6` | x86 |
| | [Go](https://hub.docker.com/r/openruntimes/go/tags) | `go-1.23` `go-1.26` | x86 / arm64 |
| | [Python](https://hub.docker.com/r/openruntimes/python/tags) | `python-3.8` `python-3.9` `python-3.10` `python-3.11` `python-3.12` `python-3.14` | x86 / arm64 / armv7 / armv8 |
| | [Python ML](https://hub.docker.com/r/openruntimes/python-ml) | `python-ml-3.11` | x86 / arm64 |
| | [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` `dart-3.10` `dart-3.11` | x86 / arm64 / armv7 / armv8 |
| | [PHP](https://hub.docker.com/r/openruntimes/php/tags) | `php-8.0` `php-8.1` `php-8.2` `php-8.3` `php-8.4` | x86 / arm64 / armv7 / armv8 |
| | [Ruby](https://hub.docker.com/r/openruntimes/ruby/tags) | `ruby-3.0` `ruby-3.1` `ruby-3.2` `ruby-3.3` `ruby-4.0` | x86 / arm64 / armv7 / armv8 |
| | [.NET](https://hub.docker.com/r/openruntimes/dotnet/tags) | `dotnet-6.0` `dotnet-7.0` `dotnet-8.0` | x86 / arm64 / armv7 / armv8 |
| | [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 |
| | [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 |
| | [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 |
| | [Flutter](https://hub.docker.com/r/openruntimes/flutter/tags) | `flutter-3.38` `flutter-3.41` | x86 / arm64 |
| | [C++](https://hub.docker.com/r/openruntimes/cpp/tags) | `cpp-17` `cpp-20` | x86 / arm64 / armv7 / armv8 |
| ![Rust logo](/images/platforms/light/rust.svg) | [Rust](https://hub.docker.com/r/openruntimes/rust/tags) | `rust-1.83` | x86 / arm64 |

**Available on Cloud**

| &nbsp; | Name | Versions | Architectures |
| --- | --- | --- | --- |
| | [Node.js](https://hub.docker.com/r/openruntimes/node/tags) | `node-16.0` `node-18.0` `node-22` `node-25` | x86 / arm64 / armv7 / armv8 |
| | [Bun](https://hub.docker.com/r/openruntimes/bun/tags) | `bun-1.0` `bun-1.1` `bun-1.3` | x86 / arm64 |
| | [Deno](https://hub.docker.com/r/openruntimes/deno/tags) | `deno-2.0` `deno-2.6` | x86 |
| | [Go](https://hub.docker.com/r/openruntimes/go/tags) | `go-1.23` `go-1.26` | x86 / arm64 |
| | [Python](https://hub.docker.com/r/openruntimes/python/tags) | `python-3.9` `python-3.12` `python-3.14` | x86 / arm64 / armv7 / armv8 |
| | [Python ML](https://hub.docker.com/r/openruntimes/python-ml) | `python-ml-3.11` | x86 / arm64 |
| | [Dart](https://hub.docker.com/r/openruntimes/dart/tags) | `dart-2.17` `dart-3.1` `dart-3.5` `dart-3.10` `dart-3.11` | x86 / arm64 / armv7 / armv8 |
| | [Flutter](https://hub.docker.com/r/openruntimes/flutter/tags) | `flutter-3.38` `flutter-3.41` | x86 / arm64 |
| | [PHP](https://hub.docker.com/r/openruntimes/php/tags) | `php-8.0` `php-8.3` `php-8.4` | x86 / arm64 / armv7 / armv8 |
| | [Ruby](https://hub.docker.com/r/openruntimes/ruby/tags) | `ruby-3.0` `ruby-3.3` `ruby-4.0` | x86 / arm64 / armv7 / armv8 |
| ![Rust logo](/images/platforms/light/rust.svg) | [Rust](https://hub.docker.com/r/openruntimes/rust/tags) | `rust-1.83` | x86 / arm64 |

---

## Templates

URL: 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
You can find all available templates by navigating to the Appwrite Console, under your project > **Functions** > **Templates**.

![Templates screen](/images/docs/functions/templates/templates.avif)

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

The create function wizard has five steps.

#### 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
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
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
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
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
| Template | Description | Runtimes |
| --- | --- | --- |
| 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, Rust |
| 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 |

---

## Messaging

URL: 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.

![Messaging overview](/images/docs/messaging/message-overview.avif)

Explore what you can build with Appwrite Messaging.

- [Emails](/docs/products/messaging/send-email-messages): Send newsletters, invoices, promotions and other emails.
- [SMS messages](/docs/products/messaging/send-sms-messages): Send SMS messages straight to your user's phone.
- [Push notifications](/docs/products/messaging/send-push-notifications): Send push notifications to your user's devices.

---

## Apple Push Notification service

URL: 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.

#### 1. Add provider

To add APNs as a provider, navigate to **Messaging** > **Providers** > **Create provider** > **Push notification**.

![Add a FCM provider](/images/docs/messaging/providers/apns/provider.avif)

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.

#### 2. 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**.

**Team ID**

Head to **Apple Developer Member Center** > **Membership details** > **Team ID**

 ![Team ID](/images/docs/messaging/providers/apns/team-id.avif)

**Bundle ID**

Head to **Apple Developer Member Center** > **Program resources** > **Certificates, Identifiers & Profiles** > **Identifiers**

![Bundle ID](/images/docs/messaging/providers/apns/bundle-id.avif)

**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.

![Authentication Key ID](/images/docs/messaging/providers/apns/key-id.avif)

**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.

    ![Authentication Key](/images/docs/messaging/providers/apns/authentication-key.avif)

**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.

After adding the following details, click **Save and continue** to enable the provider.

#### 3. 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** > Capabilities > Search for **Push Notifications**.

![Enable PN on Xcode](/images/docs/messaging/providers/apns/xcode-enable-pn.avif)

#### 4. 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.

#### 5. Manage provider

**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.

**Server SDK**

To update or delete providers programmatically, use an [Appwrite Server SDK](/docs/sdks#server).

```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.updateApnsProvider({
    providerId: '<PROVIDER_ID>',
    name: '<NAME>',
    enabled: false,
    authKey: '<AUTH_KEY>',
    authKeyId: '<AUTH_KEY_ID>',
    teamId: '<TEAM_ID>',
    bundleId: '<BUNDLE_ID>'
});
```
```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.updateApnsProvider({
    providerId: '<PROVIDER_ID>',
    name: '<NAME>',
    enabled: false,
    authKey: '<AUTH_KEY>',
    authKeyId: '<AUTH_KEY_ID>',
    teamId: '<TEAM_ID>',
    bundleId: '<BUNDLE_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
    ->setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key
;

$messaging = new Messaging($client);

$result = $messaging->updateApnsProvider(
    providerId: '<PROVIDER_ID>',
    name: '<NAME>',                               // optional
    enabled: false,                               // optional
    authKey: '<AUTH_KEY>',                        // optional
    authKeyId: '<AUTH_KEY_ID>',                   // optional
    teamId: '<TEAM_ID>',                          // optional
    bundleId: '<BUNDLE_ID>'                       // 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_apns_provider(
    provider_id = '<PROVIDER_ID>',
    name = '<NAME>',                            # optional
    enabled = False,                            # optional
    auth_key = '<AUTH_KEY>',                    # optional
    auth_key_id = '<AUTH_KEY_ID>',              # optional
    team_id = '<TEAM_ID>',                      # optional
    bundle_id = '<BUNDLE_ID>'                   # 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_apns_provider(
    provider_id: '<PROVIDER_ID>',
    name: '<NAME>',                               # optional
    enabled: false,                               # optional
    auth_key: '<AUTH_KEY>',                       # optional
    auth_key_id: '<AUTH_KEY_ID>',                 # optional
    team_id: '<TEAM_ID>',                         # optional
    bundle_id: '<BUNDLE_ID>'                      # 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.updateApnsProvider(
    providerId: "<PROVIDER_ID>"
    name: "<NAME>"                                // optional
    enabled: false                                // optional
    authKey: "<AUTH_KEY>"                         // optional
    authKeyId: "<AUTH_KEY_ID>"                    // optional
    teamId: "<TEAM_ID>"                           // optional
    bundleId: "<BUNDLE_ID>");                     // 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.updateApnsProvider(
    providerId: '<PROVIDER_ID>',
    name: '<NAME>',                              // optional
    enabled: false,                              // optional
    authKey: '<AUTH_KEY>',                       // optional
    authKeyId: '<AUTH_KEY_ID>',                  // optional
    teamId: '<TEAM_ID>',                         // optional
    bundleId: '<BUNDLE_ID>',                     // 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.updateApnsProvider(
    "<PROVIDER_ID>",                              // providerId
    "<NAME>",                                     // name (optional)
    false,                                        // enabled (optional)
    "<AUTH_KEY>",                                 // authKey (optional)
    "<AUTH_KEY_ID>",                              // authKeyId (optional)
    "<TEAM_ID>",                                  // teamId (optional)
    "<BUNDLE_ID>"                                 // 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://<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.updateApnsProvider(
    "<PROVIDER_ID>",                              // providerId
    "<NAME>",                                     // name (optional)
    false,                                        // enabled (optional)
    "<AUTH_KEY>",                                 // authKey (optional)
    "<AUTH_KEY_ID>",                              // authKeyId (optional)
    "<TEAM_ID>",                                  // teamId (optional)
    "<BUNDLE_ID>"                                 // bundleId (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.updateApnsProvider(
  providerId: "<PROVIDER_ID>",
  name: "<NAME>",                                // optional
  enabled: xfalse,                               // optional
  authKey: "<AUTH_KEY>",                         // optional
  authKeyId: "<AUTH_KEY_ID>",                    // optional
  teamId: "<TEAM_ID>",                           // optional
  bundleId: "<BUNDLE_ID>"                        // optional
)
```
```server-rust
use appwrite::Client;
use appwrite::services::messaging::Messaging;

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

    let messaging = Messaging::new(&client);

    let provider = messaging.update_apns_provider(
        "<PROVIDER_ID>",                             // providerId
        Some("<NAME>"),                              // name (optional)
        Some(false),                                 // enabled (optional)
        Some("<AUTH_KEY>"),                           // authKey (optional)
        Some("<AUTH_KEY_ID>"),                        // authKeyId (optional)
        Some("<TEAM_ID>"),                           // teamId (optional)
        Some("<BUNDLE_ID>"),                         // bundleId (optional)
        None,                                        // sandbox (optional)
    ).await?;

    println!("{:?}", provider);
    Ok(())
}
```

---

## Firebase Cloud Messaging

URL: 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.

#### 1. Add provider

To add FCM as a provider, navigate to **Messaging** > **Providers** > **Add provider** > **Push notification**.

![Add a FCM provider](/images/docs/messaging/providers/fcm/provider.avif)

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.

#### 2. 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**.

**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).

Head to **Project settings** > **Service accounts** > **Generate new private key**.

![FCM admin key](/images/docs/messaging/providers/fcm/admin-key.avif)

After all the relevant details are provided, you can enable the provider.

#### 3. Configure app

Some additional configuration is required to enable push notifications in your mobile app.

**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) guide.
```xml
<service android:name="<YOUR_NOTIFICATION_HANDLER_SERVICE>" android:exported="false">
    <intent-filter>
        <action android:name="com.google.firebase.MESSAGING_EVENT" />
    </intent-filter>
</service>
```

**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** > Capabilities > Search for **Push Notifications**.
1. If using SwiftUI, disable swizzling by setting `FirebaseAppDelegateProxyEnabled` to `NO` in your `Info.plist`.

**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.

#### 4. 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.

#### 5. Manage provider

**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.

**Server SDK**

To update or delete providers programmatically, use an [Appwrite Server SDK](/docs/sdks#server).

```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.updateFCMProvider({
    providerId: '<PROVIDER_ID>',
    name: '<NAME>',
    enabled: false,
    serviceAccountJSON: {}
});
```
```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.updateFCMProvider({
    providerId: '<PROVIDER_ID>',
    name: '<NAME>',
    enabled: false,
    serviceAccountJSON: {}
});
```
```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->updateFCMProvider(
    providerId: '<PROVIDER_ID>',
    name: '<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://<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_fcm_provider(
    provider_id = '<PROVIDER_ID>',
    name = '<NAME>',                            # optional
    enabled = False,                            # optional
    service_account_json = {}                   # 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_fcm_provider(
    provider_id: '<PROVIDER_ID>',
    name: '<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://<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.UpdateFCMProvider(
    providerId: "<PROVIDER_ID>"
    name: "<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://<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.updateFCMProvider(
    providerId: '<PROVIDER_ID>',
    name: '<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://<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.updateFCMProvider(
    "<PROVIDER_ID>",                              // providerId
    "<NAME>",                                     // 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://<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.updateFCMProvider(
    "<PROVIDER_ID>",                              // providerId
    "<NAME>",                                     // 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://<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.updateFCMProvider(
  providerId: "<PROVIDER_ID>",
  name: "<NAME>",                                // optional
  enabled: xfalse,                               // optional
  serviceAccountJSON: [:]                        // optional
)
```
```server-rust
use appwrite::Client;
use appwrite::services::messaging::Messaging;
use serde_json::json;

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

    let messaging = Messaging::new(&client);

    let provider = messaging.update_fcm_provider(
        "<PROVIDER_ID>",                             // providerId
        Some("<NAME>"),                              // name (optional)
        Some(false),                                 // enabled (optional)
        Some(json!({})),                             // serviceAccountJSON (optional)
    ).await?;

    println!("{:?}", provider);
    Ok(())
}
```

---

## Mailgun

URL: 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.

#### 1. Add provider

To add Mailgun as a provider, navigate to **Messaging** > **Providers** > **Add provider** > **Email**.

![Add a SMTP provider](/images/docs/messaging/providers/mailgun/add-mailgun.avif)

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.

#### 2. Configure provider

In the **Configure** step, you will need to provide details from your Mailgun dashboard to connect your Appwrite project.

![Configure SMTP provider](/images/docs/messaging/providers/mailgun/configure-mailgun.avif)

You will need to provide the following information from your **Mailgun dashboard**.

| 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. |

After adding the following details, click **Save and continue** to enable the provider.

#### 3. 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.

**Console**

To send a test message, navigate to **Messaging** > **Messages** > **Create message** > **Email**.

![Create email message](/images/docs/messaging/messages/create-email-message.avif)

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.

**Server SDK**

To send a message programmatically, use an [Appwrite Server SDK](/docs/sdks#server).

```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='<MESSAGE_ID>',
    subject='<SUBJECT>',
    content='<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>"
)
```
```server-rust
use appwrite::Client;
use appwrite::services::messaging::Messaging;

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

    let messaging = Messaging::new(&client);

    let message = messaging.create_email(
        "<MESSAGE_ID>",                              // messageId
        "<SUBJECT>",                                 // subject
        "<CONTENT>",                                 // content
        None,                                        // topics (optional)
        None,                                        // users (optional)
        None,                                        // targets (optional)
        None,                                        // cc (optional)
        None,                                        // bcc (optional)
        None,                                        // attachments (optional)
        None,                                        // draft (optional)
        None,                                        // html (optional)
        None,                                        // scheduledAt (optional)
    ).await?;

    println!("{:?}", message);
    Ok(())
}
```

You can follow the [Send email messages](/docs/products/messaging/send-push-notifications) guide to send your first push notification and test your provider.

#### 4. Manage provider

**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.

**Server SDK**

To update or delete providers programmatically, use an [Appwrite Server SDK](/docs/sdks#server).

```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.updateMailgunProvider({
    providerId: '<PROVIDER_ID>',
    name: '<PROVIDER_NAME>',
    apiKey: '<API_KEY>',
    domain: '<DOMAIN>',
    isEuRegion: false,
    fromName: '<SENDER_NAME>',
    fromEmail: '<SENDER_EMAIL>',
    replyToName: '<REPLY_TO_NAME>',
    replyToEmail: '<REPLY_TO_EMAIL>',
    enabled: true
}).then(function (response) {
    console.log(response);
}, function (error) {
    console.log(error);
});

// delete provider
messaging.deleteProvider({
    providerId: '<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.updateMailgunProvider({
    providerId: '<PROVIDER_ID>',
    name: '<PROVIDER_NAME>',
    apiKey: '<API_KEY>',
    domain: '<DOMAIN>',
    isEuRegion: false,
    fromName: '<SENDER_NAME>',
    fromEmail: '<SENDER_EMAIL>',
    replyToName: '<REPLY_TO_NAME>',
    replyToEmail: '<REPLY_TO_EMAIL>',
    enabled: true
}).then(function (response) {
    console.log(response);
}, function (error) {
    console.log(error);
});

// delete provider
messaging.deleteProvider({
    providerId: '<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_mailgun_provider(
    provider_id='<PROVIDER_ID>',
    name='<PROVIDER_NAME>',
    api_key='<API_KEY>',
    domain='<DOMAIN>',
    is_eu_region=False,
    from_name='<SENDER_NAME>',
    from_email='<SENDER_EMAIL>',
    reply_to_name='<REPLY_TO_NAME>',
    reply_to_email='<REPLY_TO_EMAIL>',
    enabled=True,
)
```
```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?>",
)
```
```server-rust
use appwrite::Client;
use appwrite::services::messaging::Messaging;

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

    let messaging = Messaging::new(&client);

    let provider = messaging.update_mailgun_provider(
        "<PROVIDER_ID>",                             // providerId
        Some("<PROVIDER_NAME>"),                     // name (optional)
        Some("<API_KEY>"),                           // apiKey (optional)
        Some("<DOMAIN>"),                            // domain (optional)
        Some(false),                                 // isEuRegion (optional)
        Some(true),                                  // enabled (optional)
        Some("<SENDER_NAME>"),                       // fromName (optional)
        Some("<SENDER_EMAIL>"),                      // fromEmail (optional)
        Some("<REPLY_TO_NAME>"),                     // replyToName (optional)
        Some("<REPLY_TO_EMAIL>"),                    // replyToEmail (optional)
    ).await?;

    println!("{:?}", provider);
    Ok(())
}
```

---

## Messages

URL: 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.

![Add a target](/images/docs/messaging/messages/messages-overview.avif)

### Messages

Each message displays with the following information.

| Column | 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. |

### Messages types

There are three types of messages

| Message type | 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. |

### 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.

![Message lifecycle](/images/docs/messaging/message-status.avif)

### 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.

| Message type | 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. |

### Composing messages
Different types of messages have different content and configurable options.
Here are the different components that make up a message.

**Push notifications**

| Parameter | Required | 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. |

**Emails**

| Parameter | Required | 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. |

**SMS**

| Parameter | Required | 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. |

### Sending a message
You can create a message with a Server SDK. You can send a push notification like this.

```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: [],
    users: [],
    targets: [],
    data: {},
    action: '<ACTION>',
    icon: '<ICON>',
    sound: '<SOUND>',
    color: '<COLOR>',
    tag: '<TAG>',
    badge: 1,
    contentAvailable: false,
    critical: false,
    priority: 'normal',
    draft: true,
    scheduledAt: ''
});
```
```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: [],
    users: [],
    targets: [],
    data: {},
    action: '<ACTION>',
    icon: '<ICON>',
    sound: '<SOUND>',
    color: '<COLOR>',
    tag: '<TAG>',
    badge: 1,
    contentAvailable: false,
    critical: false,
    priority: 'normal',
    draft: true,
    scheduledAt: ''
});
```
```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
)
```
```server-rust
use appwrite::Client;
use appwrite::services::messaging::Messaging;
use appwrite::enums::MessagePriority;
use serde_json::json;

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

    let messaging = Messaging::new(&client);

    let message = messaging.create_push(
        "<MESSAGE_ID>",                              // messageId
        Some("<TITLE>"),                             // title (optional)
        Some("<BODY>"),                              // body (optional)
        Some(vec![]),                                // topics (optional)
        Some(vec![]),                                // users (optional)
        Some(vec![]),                                // targets (optional)
        Some(json!({})),                             // data (optional)
        Some("<ACTION>"),                            // action (optional)
        None,                                        // image (optional)
        Some("<ICON>"),                              // icon (optional)
        Some("<SOUND>"),                             // sound (optional)
        Some("<COLOR>"),                             // color (optional)
        Some("<TAG>"),                               // tag (optional)
        Some(1),                                     // badge (optional)
        Some(true),                                  // draft (optional)
        None,                                        // scheduledAt (optional)
        Some(false),                                 // contentAvailable (optional)
        Some(false),                                 // critical (optional)
        Some(MessagePriority::Normal),               // priority (optional)
    ).await?;

    println!("{:?}", message);
    Ok(())
}
```

[Learn more about sending a push notification](/docs/products/messaging/send-push-notifications)

You can send an email like this.

```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({
    messageId: '<MESSAGE_ID>',
    subject: '<SUBJECT>',
    content: '<CONTENT>',
    topics: [],
    users: [],
    targets: [],
    cc: [],
    bcc: [],
    draft: true,
    html: false,
    scheduledAt: ''
});
```
```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({
    messageId: '<MESSAGE_ID>',
    subject: '<SUBJECT>',
    content: '<CONTENT>',
    topics: [],
    users: [],
    targets: [],
    cc: [],
    bcc: [],
    draft: true,
    html: false,
    scheduledAt: ''
});
```
```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
)
```
```server-rust
use appwrite::Client;
use appwrite::services::messaging::Messaging;

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

    let messaging = Messaging::new(&client);

    let message = messaging.create_email(
        "<MESSAGE_ID>",                              // messageId
        "<SUBJECT>",                                 // subject
        "<CONTENT>",                                 // content
        Some(vec![]),                                // topics (optional)
        Some(vec![]),                                // users (optional)
        Some(vec![]),                                // targets (optional)
        Some(vec![]),                                // cc (optional)
        Some(vec![]),                                // bcc (optional)
        None,                                        // attachments (optional)
        Some(true),                                  // draft (optional)
        Some(false),                                 // html (optional)
        None,                                        // scheduledAt (optional)
    ).await?;

    println!("{:?}", message);
    Ok(())
}
```

[Learn more about sending an email](/docs/products/messaging/send-email-messages)

You can send an SMS message like this.

```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({
    messageId: '<MESSAGE_ID>',
    content: '<CONTENT>',
    topics: [],
    users: [],
    targets: [],
    draft: true,
    scheduledAt: ''
});
```
```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({
    messageId: '<MESSAGE_ID>',
    content: '<CONTENT>',
    topics: [],
    users: [],
    targets: [],
    draft: true,
    scheduledAt: ''
});
```
```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
)
```
```server-rust
use appwrite::Client;
use appwrite::services::messaging::Messaging;

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

    let messaging = Messaging::new(&client);

    let message = messaging.create_sms(
        "<MESSAGE_ID>",                              // messageId
        "<CONTENT>",                                 // content
        Some(vec![]),                                // topics (optional)
        Some(vec![]),                                // users (optional)
        Some(vec![]),                                // targets (optional)
        Some(true),                                  // draft (optional)
        None,                                        // scheduledAt (optional)
    ).await?;

    println!("{:?}", message);
    Ok(())
}
```

[Learn more about sending a SMS message](/docs/products/messaging/send-sms-messages)

---

## MSG91

URL: 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.

#### 1. Add provider

To add MSG91 as a provider, navigate to **Messaging** > **Providers** > **Add provider** > **SMS**.

![Add a MSG91 provider](/images/docs/messaging/providers/msg91/provider.avif)

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.

#### 2. 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**.

| 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 | |

After adding the following details, click **Save and continue** to enable the provider.

#### 3. 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.

**Console**

To send a test message, navigate to **Messaging** > **Messages** > **Create message** > **SMS**.

![Create an SMS message](/images/docs/messaging/messages/create-sms-message.avif)

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.

**Server SDK**

To send a message programmatically, use an [Appwrite Server SDK](/docs/sdks#server).

```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({
    messageId: '<MESSAGE_ID>',
    content: '<CONTENT>',
    topics: [],
    users: [],
    targets: [],
    draft: true,
    scheduledAt: ''
});
```
```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({
    messageId: '<MESSAGE_ID>',
    content: '<CONTENT>',
    topics: [],
    users: [],
    targets: [],
    draft: true,
    scheduledAt: ''
});
```
```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
)
```
```server-rust
use appwrite::Client;
use appwrite::services::messaging::Messaging;

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

    let messaging = Messaging::new(&client);

    let message = messaging.create_sms(
        "<MESSAGE_ID>",                              // messageId
        "<CONTENT>",                                 // content
        None,                                        // topics (optional)
        None,                                        // users (optional)
        None,                                        // targets (optional)
        Some(true),                                  // draft (optional)
        None,                                        // scheduledAt (optional)
    ).await?;

    println!("{:?}", message);
    Ok(())
}
```

You can follow the [Send email messages](/docs/products/messaging/send-sms-messages) guide to send your first push notification and test your provider.

#### 4. Manage provider

**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.

**Server SDK**

To update or delete providers programmatically, use an [Appwrite Server SDK](/docs/sdks#server).

```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({
    providerId: '<PROVIDER_ID>',
    name: '<NAME>',
    enabled: false,
    templateId: '<TEMPLATE_ID>',
    senderId: '<SENDER_ID>',
    authKey: '<AUTH_KEY>'
});
```
```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({
    providerId: '<PROVIDER_ID>',
    name: '<NAME>',
    enabled: false,
    templateId: '<TEMPLATE_ID>',
    senderId: '<SENDER_ID>',
    authKey: '<AUTH_KEY>'
});
```
```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
)
```
```server-rust
use appwrite::Client;
use appwrite::services::messaging::Messaging;

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

    let messaging = Messaging::new(&client);

    let provider = messaging.update_msg91_provider(
        "<PROVIDER_ID>",                             // providerId
        Some("<NAME>"),                              // name (optional)
        Some(false),                                 // enabled (optional)
        Some("<TEMPLATE_ID>"),                       // templateId (optional)
        Some("<SENDER_ID>"),                         // senderId (optional)
        Some("<AUTH_KEY>"),                           // authKey (optional)
    ).await?;

    println!("{:?}", provider);
    Ok(())
}
```

---

## Providers

URL: 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
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.

- [APNS](/docs/products/messaging/apns): Send push notifications to apps on Apple devices through Apple Push Notification service (APNs).
- [FCM](/docs/products/messaging/fcm): Send push notifications to Android, Apple, or Web app with Firebase Cloud Messaging (FCM).

### Email
Deliver customized emails to users to send reminders, updates, promotions, and custom authentication logic.

- [Mailgun](/docs/products/messaging/mailgun): Deliver custom email messages to users using Mailgun.
- [Resend](/docs/products/messaging/resend): Deliver custom email messages to users using Resend.
- [SendGrid](/docs/products/messaging/sendgrid): Deliver custom email messages to users using SendGrid.
- [Amazon SES](/docs/products/messaging/ses): Deliver custom email messages to users using Amazon SES.
- [SMTP](/docs/products/messaging/smtp): Deliver custom email messages to users using standard SMTP settings.

### SMS
Send customized SMS messages to users by phone to send reminders, updates, promotions, and one-time passwords.

- [Twilio](/docs/products/messaging/twilio): Deliver custom SMS messages to users using Twilio.
- [MSG91](/docs/products/messaging/msg91): Deliver custom SMS messages to users using MSG91.
- [Telesign](/docs/products/messaging/telesign): Deliver custom SMS messages to users using Telesign.
- [Textmagic](/docs/products/messaging/textmagic): Deliver custom SMS messages to users using Textmagic.
- [Vonage](/docs/products/messaging/vonage): Deliver custom SMS messages to users using Vonage.

---

## Resend

URL: https://appwrite.io/docs/products/messaging/resend

Resend 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.

#### 1. Add provider

To add Resend as a provider, navigate to **Messaging** > **Providers** > **Add provider** > **Email**.

![Add a Resend provider](/images/docs/messaging/providers/resend/add-resend.avif)

Give your provider a name > choose **Resend** > click **Save and continue**.
The provider will be saved to your project, but not enabled until you complete its configuration.

#### 2. Configure provider

In the **Configure** step, you will need to provide details from your Resend dashboard to connect your Appwrite project.

![Configure Resend provider](/images/docs/messaging/providers/resend/configure-resend.avif)
You will need to provide the following information from your **Resend dashboard**.

| Field name | |
| --- | --- |
| API key | Head to API Keys -> Create API Key. You can also follow [Resend's instructions](https://resend.com/docs/dashboard/api-keys/introduction) to create an API key. |
| Sender email | The provider sends emails from this sender email. The sender email needs to be an email under a [verified domain](https://resend.com/docs/dashboard/domains/introduction) in Resend. |
| 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. |
| Reply-to name | The reply-to name that appears in the emails sent from this provider. |

After adding the following details, click **Save and continue** to enable the provider.

#### 3. 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.

**Console**

To send a test message, navigate to **Messaging** > **Messages** > **Create message** > **Email**.

![Create email message](/images/docs/messaging/messages/create-email-message.avif)

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.

**Server SDK**

To send a message programmatically, use an [Appwrite Server SDK](/docs/sdks#server).

```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='<MESSAGE_ID>',
    subject='<SUBJECT>',
    content='<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;

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>"
)
```
```server-rust
use appwrite::Client;
use appwrite::services::messaging::Messaging;

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

    let messaging = Messaging::new(&client);

    let message = messaging.create_email(
        "<MESSAGE_ID>",                              // messageId
        "<SUBJECT>",                                 // subject
        "<CONTENT>",                                 // content
        None,                                        // topics (optional)
        None,                                        // users (optional)
        None,                                        // targets (optional)
        None,                                        // cc (optional)
        None,                                        // bcc (optional)
        None,                                        // attachments (optional)
        None,                                        // draft (optional)
        None,                                        // html (optional)
        None,                                        // scheduledAt (optional)
    ).await?;

    println!("{:?}", message);
    Ok(())
}
```

You can follow the [Send email messages](/docs/products/messaging/send-email-messages) guide to send your first email message and test your provider.

#### 4. Manage provider

**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.

**Server SDK**

To update or delete providers programmatically, use an [Appwrite Server SDK](/docs/sdks#server).

```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.updateResendProvider({
    providerId: '<PROVIDER_ID>',
    name: '<NAME>',
    enabled: false,
    apiKey: '<API_KEY>',
    fromName: '<FROM_NAME>',
    fromEmail: 'email@example.com',
    replyToName: '<REPLY_TO_NAME>',
    replyToEmail: '<REPLY_TO_EMAIL>'
});
```
```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.updateResendProvider({
    providerId: '<PROVIDER_ID>',
    name: '<NAME>',
    enabled: false,
    apiKey: '<API_KEY>',
    fromName: '<FROM_NAME>',
    fromEmail: 'email@example.com',
    replyToName: '<REPLY_TO_NAME>',
    replyToEmail: '<REPLY_TO_EMAIL>'
});
```
```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->updateResendProvider(
    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_resend_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_resend_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.UpdateResendProvider(
    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.updateResendProvider(
    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.services.Messaging

val 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

val messaging = Messaging(client)

val provider = messaging.updateResendProvider(
    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
)
```
```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.updateResendProvider(
    "<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.updateResendProvider(
  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
)
```
```server-rust
use appwrite::Client;
use appwrite::services::messaging::Messaging;

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

    let messaging = Messaging::new(&client);

    let provider = messaging.update_resend_provider(
        "<PROVIDER_ID>",                             // providerId
        Some("<NAME>"),                              // name (optional)
        Some(false),                                 // enabled (optional)
        Some("<API_KEY>"),                           // apiKey (optional)
        Some("<FROM_NAME>"),                         // fromName (optional)
        Some("email@example.com"),                   // fromEmail (optional)
        Some("<REPLY_TO_NAME>"),                     // replyToName (optional)
        Some("<REPLY_TO_EMAIL>"),                    // replyToEmail (optional)
    ).await?;

    println!("{:?}", provider);
    Ok(())
}
```

---

## Send email messages

URL: 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
Appwrite supports [Mailgun](/docs/products/messaging/mailgun/), [Resend](/docs/products/messaging/resend/), [Sendgrid](/docs/products/messaging/sendgrid/), [Amazon SES](/docs/products/messaging/ses/), and [SMTP](/docs/products/messaging/smtp/) as
email providers. You must configure one of them as a provider.

![Add a SMTP provider](/images/docs/messaging/providers/mailgun/add-mailgun.avif)
To add a new provider navigate to **Messaging** > **Providers** > **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), [Resend](/docs/products/messaging/resend#configure-provider), [Sendgrid](/docs/products/messaging/sendgrid#configure-provider), and [Amazon SES](/docs/products/messaging/ses#configure-provider).

### 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**.

![Target overview](/images/docs/messaging/targets/target-overview.avif)

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**.

![Add a target](/images/docs/messaging/targets/add-targets.avif)

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.

```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({
    userId: '<USER_ID>',
    targetId: '<TARGET_ID>',
    providerType: sdk.MessagingProviderType.Email,
    identifier: '<IDENTIFIER>',
    providerId: '<PROVIDER_ID>',
    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
    .setJWT('eyJhbVCJ9.eyJ...');                    // Your secret JSON Web Token

const users = new sdk.Users(client);

const target = await users.createTarget({
    userId: '<USER_ID>',
    targetId: '<TARGET_ID>',
    providerType: sdk.MessagingProviderType.Email,
    identifier: '<IDENTIFIER>',
    providerId: '<PROVIDER_ID>',
    name: '<NAME>'
});
```
```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
)
```
```server-rust
use appwrite::Client;
use appwrite::services::users::Users;
use appwrite::enums::messaging_provider_type::MessagingProviderType;

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

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

    let target = users.create_target(
        "<USER_ID>",
        "<TARGET_ID>",
        MessagingProviderType::Email,
        "<IDENTIFIER>",
        Some("<PROVIDER_ID>"),  // optional
        Some("<NAME>"),         // optional
    ).await?;

    println!("{:?}", target);
    Ok(())
}
```

### Create topics (optional)
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** > **Create topic**.

![Add a target](/images/docs/messaging/topics/create-topics.avif)

You can also create topics programmatically using an [Appwrite Server SDK](/docs/references/cloud/server-nodejs/messaging#createTopic).

```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({
    topicId: '<TOPIC_ID>',
    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({
    topicId: '<TOPIC_ID>',
    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>"
)
```
```server-rust
use appwrite::Client;
use appwrite::services::messaging::Messaging;

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

    let messaging = Messaging::new(&client);

    let topic = messaging.create_topic(
        "<TOPIC_ID>",
        "<NAME>",
        None, // subscribe (optional)
    ).await?;

    println!("{:?}", topic);
    Ok(())
}
```

### 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.

```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({
    messageId: '<MESSAGE_ID>',
    subject: '<SUBJECT>',
    content: '<CONTENT>',
    topics: [],
    users: [],
    targets: [],
    cc: [],
    bcc: [],
    attachments: [],
    draft: false,
    html: false,
    scheduledAt: ''
});
```
```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({
    messageId: '<MESSAGE_ID>',
    subject: '<SUBJECT>',
    content: '<CONTENT>',
    topics: [],
    users: [],
    targets: [],
    cc: [],
    bcc: [],
    attachments: [],
    draft: false,
    html: false,
    scheduledAt: ''
});
```
```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
)
```
```server-rust
use appwrite::Client;
use appwrite::services::messaging::Messaging;

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

    let messaging = Messaging::new(&client);

    let message = messaging.create_email(
        "<MESSAGE_ID>",
        "<SUBJECT>",
        "<CONTENT>",
        Some(vec![]),       // topics (optional)
        Some(vec![]),       // users (optional)
        Some(vec![]),       // targets (optional)
        Some(vec![]),       // cc (optional)
        Some(vec![]),       // bcc (optional)
        None,               // attachments (optional)
        Some(false),        // draft (optional)
        Some(false),        // html (optional)
        None,               // scheduled_at (optional)
    ).await?;

    println!("{:?}", message);
    Ok(())
}
```

### 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.

```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({
    messageId: '<MESSAGE_ID>',
    subject: '<SUBJECT>',
    content: '<CONTENT>',
    topics: [],
    users: [],
    targets: [],
    cc: [],
    bcc: [],
    draft: false,
    html: false,
    scheduledAt: '2025-02-13T22:01:00+0000'
});
```
```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({
    messageId: '<MESSAGE_ID>',
    subject: '<SUBJECT>',
    content: '<CONTENT>',
    topics: [],
    users: [],
    targets: [],
    cc: [],
    bcc: [],
    draft: false,
    html: false,
    scheduledAt: '2025-02-13T22:01:00+0000'
});
```
```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"
)
```
```server-rust
use appwrite::Client;
use appwrite::services::messaging::Messaging;

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

    let messaging = Messaging::new(&client);

    let message = messaging.create_email(
        "<MESSAGE_ID>",
        "<SUBJECT>",
        "<CONTENT>",
        Some(vec![]),                          // topics (optional)
        Some(vec![]),                          // users (optional)
        Some(vec![]),                          // targets (optional)
        Some(vec![]),                          // cc (optional)
        Some(vec![]),                          // bcc (optional)
        None,                                  // attachments (optional)
        Some(false),                           // draft (optional)
        Some(false),                           // html (optional)
        Some("2025-02-13T22:01:00+0000"),      // scheduled_at (optional)
    ).await?;

    println!("{:?}", message);
    Ok(())
}
```

---

## Send push notification

URL: 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.

#### 1. 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.

- [APNS](/docs/products/messaging/apns): Configure APNs for push notification to Apple devices.
- [FCM](/docs/products/messaging/fcm): Configure FCM for push notification to Android and Apple devices.

#### 2. Add targets

Before sending your first push notification, your application must register itself for push notification,
then provide the device token to Appwrite.

**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** > Capabilities > Search for **Push Notifications**.

 ![Authentication Key](/images/docs/messaging/providers/apns/xcode-enable-pn.avif)

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)")
    }
}
```

**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>
```

**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** > 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
}
```

#### 3. Request permissions

Your app must ask for permission to receive push notification from the user.

**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).

**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.

**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
```

#### 4. Send message

You can send messages in both the Appwrite Console and programmatically using the Appwrite Server SDK.

**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** > Capabilities > uncheck **Automatically manage signing**.
Then manually select a **Provisioning profile** that is a **Distribution profile**.

**Console**

To send a test message, navigate to **Messaging** > **Messages** > **Create message** > **Push notification**.

![Create email message](/images/docs/messaging/messages/create-push-notification.avif)

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.

**Server SDK**

To send a message programmatically, use an [Appwrite Server SDK](/docs/sdks#server).

```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
)
```
```server-rust
use appwrite::Client;
use appwrite::services::messaging::Messaging;
use serde_json::json;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let 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

    let messaging = Messaging::new(&client);

    let message = messaging.create_push(
        "<MESSAGE_ID>",
        Some("<TITLE>"),                    // title (optional)
        Some("<BODY>"),                     // body (optional)
        Some(vec![]),                       // topics (optional)
        Some(vec![]),                       // users (optional)
        Some(vec![]),                       // targets (optional)
        Some(json!({})),                    // data (optional)
        Some("<ACTION>"),                   // action (optional)
        Some("<IMAGE>"),                    // image (optional)
        Some("<ICON>"),                     // icon (optional)
        Some("<SOUND>"),                    // sound (optional)
        Some("<COLOR>"),                    // color (optional)
        Some("<TAG>"),                      // tag (optional)
        Some(1),                            // badge (optional)
        Some(false),                        // draft (optional)
        None,                               // scheduled_at (optional)
        None,                               // content_available (optional)
        None,                               // critical (optional)
        None,                               // priority (optional)
    ).await?;

    println!("{:?}", message);
    Ok(())
}
```

---

## Send SMS messages

URL: 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
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.

![Add a SMTP provider](/images/docs/messaging/providers/twilio/provider.avif)
To add a new provider navigate to **Messaging** > **Providers** > **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
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**.

![Target overview](/images/docs/messaging/targets/target-overview.avif)

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**.

![Add a target](/images/docs/messaging/targets/add-targets.avif)

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.

```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({
    userId: '<USER_ID>',
    targetId: '<TARGET_ID>',
    providerType: sdk.MessagingProviderType.Phone,
    identifier: '<IDENTIFIER>',
    providerId: '<PROVIDER_ID>',
    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
    .setJWT('eyJhbVCJ9.eyJ...');                    // Your secret JSON Web Token

const users = new sdk.Users(client);

const target = await users.createTarget({
    userId: '<USER_ID>',
    targetId: '<TARGET_ID>',
    providerType: sdk.MessagingProviderType.Phone,
    identifier: '<IDENTIFIER>',
    providerId: '<PROVIDER_ID>',
    name: '<NAME>'
});
```
```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
)
```
```server-rust
use appwrite::Client;
use appwrite::services::users::Users;
use appwrite::enums::messaging_provider_type::MessagingProviderType;

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

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

    let target = users.create_target(
        "<USER_ID>",
        "<TARGET_ID>",
        MessagingProviderType::Sms,
        "<IDENTIFIER>",
        Some("<PROVIDER_ID>"),  // optional
        Some("<NAME>"),         // optional
    ).await?;

    println!("{:?}", target);
    Ok(())
}
```

### Create topics (optional)
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** > **Create topic**.

![Add a target](/images/docs/messaging/topics/create-topics.avif)

You can also create topics programmatically using an [Appwrite Server SDK](/docs/references/cloud/server-nodejs/messaging#createTopic).

```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({
    topicId: '<TOPIC_ID>',
    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({
    topicId: '<TOPIC_ID>',
    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>"
)
```
```server-rust
use appwrite::Client;
use appwrite::services::messaging::Messaging;

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

    let messaging = Messaging::new(&client);

    let topic = messaging.create_topic(
        "<TOPIC_ID>",
        "<NAME>",
        None, // subscribe (optional)
    ).await?;

    println!("{:?}", topic);
    Ok(())
}
```

### Send SMS messages
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.

```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({
    messageId: '<MESSAGE_ID>',
    content: '<CONTENT>',
    topics: [],
    users: [],
    targets: [],
    draft: false,
    scheduledAt: ''
});
```
```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({
    messageId: '<MESSAGE_ID>',
    content: '<CONTENT>',
    topics: [],
    users: [],
    targets: [],
    draft: false,
    scheduledAt: ''
});
```
```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
)
```
```server-rust
use appwrite::Client;
use appwrite::services::messaging::Messaging;

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

    let messaging = Messaging::new(&client);

    let message = messaging.create_sms(
        "<MESSAGE_ID>",
        "<CONTENT>",
        Some(vec![]),       // topics (optional)
        Some(vec![]),       // users (optional)
        Some(vec![]),       // targets (optional)
        Some(false),        // draft (optional)
        None,               // scheduled_at (optional)
    ).await?;

    println!("{:?}", message);
    Ok(())
}
```

### Schedule SMS message
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.

```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({
    messageId: '<MESSAGE_ID>',
    content: '<CONTENT>',
    topics: [],
    users: [],
    targets: [],
    draft: false,
    scheduledAt: '2025-02-13T22:01:00+0000'
});
```
```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({
    messageId: '<MESSAGE_ID>',
    content: '<CONTENT>',
    topics: [],
    users: [],
    targets: [],
    draft: false,
    scheduledAt: '2025-02-13T22:01:00+0000'
});
```
```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
)
```
```server-rust
use appwrite::Client;
use appwrite::services::messaging::Messaging;

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

    let messaging = Messaging::new(&client);

    let message = messaging.create_sms(
        "<MESSAGE_ID>",
        "<CONTENT>",
        Some(vec![]),                          // topics (optional)
        Some(vec![]),                          // users (optional)
        Some(vec![]),                          // targets (optional)
        Some(false),                           // draft (optional)
        Some("2025-02-13T22:01:00+0000"),      // scheduled_at (optional)
    ).await?;

    println!("{:?}", message);
    Ok(())
}
```

---

## SendGrid

URL: 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.

#### 1. Add provider

To add SendGrid as a provider, navigate to **Messaging** > **Providers** > **Add provider** > **Email**.

![Add a SMTP provider](/images/docs/messaging/providers/sendgrid/add-sendgrid.avif)

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.

#### 2. Configure provider

In the **Configure** step, you will need to provide details from your SendGrid dashboard to connect your Appwrite project.

![Configure SMTP provider](/images/docs/messaging/providers/sendgrid/configure-sendgrid.avif)
You will need to provide the following information from your **SendGrid dashboard**.

| 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. |

After adding the following details, click **Save and continue** to enable the provider.

#### 3. 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.

**Console**

To send a test message, navigate to **Messaging** > **Messages** > **Create message** > **Email**.

![Create email message](/images/docs/messaging/messages/create-email-message.avif)

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.

**Server SDK**

To send a message programmatically, use an [Appwrite Server SDK](/docs/sdks#server).

```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='<MESSAGE_ID>',
    subject='<SUBJECT>',
    content='<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>"
)
```
```server-rust
use appwrite::Client;
use appwrite::services::messaging::Messaging;

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

    let messaging = Messaging::new(&client);

    let message = messaging.create_email(
        "<MESSAGE_ID>",                              // messageId
        "<SUBJECT>",                                 // subject
        "<CONTENT>",                                 // content
        None,                                        // topics (optional)
        None,                                        // users (optional)
        None,                                        // targets (optional)
        None,                                        // cc (optional)
        None,                                        // bcc (optional)
        None,                                        // attachments (optional)
        None,                                        // draft (optional)
        None,                                        // html (optional)
        None,                                        // scheduledAt (optional)
    ).await?;

    println!("{:?}", message);
    Ok(())
}
```

You can follow the [Send email messages](/docs/products/messaging/send-email-messages) guide to send your first push notification and test your provider.

#### 4. Manage provider

**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.

**Server SDK**

To update or delete providers programmatically, use an [Appwrite Server SDK](/docs/sdks#server).

```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({
    providerId: '<PROVIDER_ID>',
    name: '<NAME>',
    enabled: false,
    apiKey: '<API_KEY>',
    fromName: '<FROM_NAME>',
    fromEmail: 'email@example.com',
    replyToName: '<REPLY_TO_NAME>',
    replyToEmail: '<REPLY_TO_EMAIL>'
});
```
```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({
    providerId: '<PROVIDER_ID>',
    name: '<NAME>',
    enabled: false,
    apiKey: '<API_KEY>',
    fromName: '<FROM_NAME>',
    fromEmail: 'email@example.com',
    replyToName: '<REPLY_TO_NAME>',
    replyToEmail: '<REPLY_TO_EMAIL>'
});
```
```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
)
```
```server-rust
use appwrite::Client;
use appwrite::services::messaging::Messaging;

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

    let messaging = Messaging::new(&client);

    let provider = messaging.update_sendgrid_provider(
        "<PROVIDER_ID>",                             // providerId
        Some("<NAME>"),                              // name (optional)
        Some(false),                                 // enabled (optional)
        Some("<API_KEY>"),                           // apiKey (optional)
        Some("<FROM_NAME>"),                         // fromName (optional)
        Some("email@example.com"),                   // fromEmail (optional)
        Some("<REPLY_TO_NAME>"),                     // replyToName (optional)
        Some("<REPLY_TO_EMAIL>"),                    // replyToEmail (optional)
    ).await?;

    println!("{:?}", provider);
    Ok(())
}
```

---

## Amazon SES

URL: https://appwrite.io/docs/products/messaging/ses

Amazon SES 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.

#### 1. Add provider

To add Amazon SES as a provider, navigate to **Messaging** > **Providers** > **Add provider**, then choose **Amazon SES** under Email.

Give your provider a name and complete the configuration, then click **Create provider**.

#### 2. Configure provider

In the configuration step, you will need credentials from the AWS console to connect your Appwrite project.

You will need to provide the following information from your **AWS account**.

| Field name | |
| --- | --- |
| Access key | An IAM access key ID with permission to send email through Amazon SES. Create access keys in the [IAM console](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html). |
| Secret key | The secret access key that belongs to the access key ID. |
| Region | The AWS region of your [verified SES identity](https://docs.aws.amazon.com/ses/latest/dg/verify-addresses-and-domains.html). Select the same region you use in the Amazon SES console. |
| Sender email | The provider sends emails from this sender email. The address must be a verified email or belong to a verified domain in Amazon SES. |
| 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. |
| Reply-to name | The reply-to name that appears in the emails sent from this provider. |

After adding the following details, click **Create provider** to save and enable the provider.

#### 3. 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.

To send a test message, navigate to **Messaging** > **Messages** > **Create message** > **Email**.

![Create email message](/images/docs/messaging/messages/create-email-message.avif)

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.

You can also send email programmatically with an [Appwrite Server SDK](/docs/sdks#server). Follow the [Send email messages](/docs/products/messaging/send-email-messages) guide to send your first email message and test your provider.

#### 4. Manage provider

**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.

**Server SDK**

To update or delete providers programmatically, use an [Appwrite Server SDK](/docs/sdks#server).

```server-nodejs
const sdk = require('node-appwrite');

const client = new sdk.Client();
const messaging = new sdk.Messaging(client);

client
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>')
;

const provider = await messaging.updateSesProvider({
    providerId: '<PROVIDER_ID>',
    name: '<NAME>',
    enabled: false,
    accessKey: '<ACCESS_KEY>',
    secretKey: '<SECRET_KEY>',
    region: 'us-east-1',
    fromName: '<FROM_NAME>',
    fromEmail: 'email@example.com',
    replyToName: '<REPLY_TO_NAME>',
    replyToEmail: '<REPLY_TO_EMAIL>'
});
```
```python
from appwrite.client import Client
from appwrite.services.messaging import Messaging

client = Client()

(client
  .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
  .set_project('<PROJECT_ID>')
  .set_key('<YOUR_API_KEY>')
)

messaging = Messaging(client)

result = messaging.update_ses_provider(
    provider_id = '<PROVIDER_ID>',
    name = '<NAME>',
    enabled = False,
    access_key = '<ACCESS_KEY>',
    secret_key = '<SECRET_KEY>',
    region = 'us-east-1',
    from_name = '<FROM_NAME>',
    from_email = 'email@example.com',
    reply_to_name = '<REPLY_TO_NAME>',
    reply_to_email = '<REPLY_TO_EMAIL>'
)
```
```php
<?php

use Appwrite\Client;
use Appwrite\Services\Messaging;

$client = new Client();

$client
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>')
;

$messaging = new Messaging($client);

$result = $messaging->updateSesProvider(
    providerId: '<PROVIDER_ID>',
    name: '<NAME>',
    enabled: false,
    accessKey: '<ACCESS_KEY>',
    secretKey: '<SECRET_KEY>',
    region: 'us-east-1',
    fromName: '<FROM_NAME>',
    fromEmail: 'email@example.com',
    replyToName: '<REPLY_TO_NAME>',
    replyToEmail: '<REPLY_TO_EMAIL>'
);
```

---

## SMTP

URL: 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.

#### 1. Add provider

To add a custom SMTP server as a provider, navigate to **Messaging** > **Providers** > **Add provider** > **Email**.

![Add a SMTP provider](/images/docs/messaging/providers/smtp/add-smtp.avif)

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.

#### 2. 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**.

| 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. |

After adding the following details, click **Save and continue** to enable the provider.

#### 3. 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.

**Console**

To send a test message, navigate to **Messaging** > **Messages** > **Create message** > **Email**.

![Create email message](/images/docs/messaging/messages/create-email-message.avif)

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.

**Server SDK**

To send a message programmatically, use an [Appwrite Server SDK](/docs/sdks#server).

```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='<MESSAGE_ID>',
    subject='<SUBJECT>',
    content='<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>"
)
```
```server-rust
use appwrite::Client;
use appwrite::services::messaging::Messaging;

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

    let messaging = Messaging::new(&client);

    let message = messaging.create_email(
        "<MESSAGE_ID>",                              // messageId
        "<SUBJECT>",                                 // subject
        "<CONTENT>",                                 // content
        None,                                        // topics (optional)
        None,                                        // users (optional)
        None,                                        // targets (optional)
        None,                                        // cc (optional)
        None,                                        // bcc (optional)
        None,                                        // attachments (optional)
        None,                                        // draft (optional)
        None,                                        // html (optional)
        None,                                        // scheduledAt (optional)
    ).await?;

    println!("{:?}", message);
    Ok(())
}
```

You can follow the [Send email messages](/docs/products/messaging/send-push-notifications) guide to send your first push notification and test your provider.

#### 4. Manage provider

**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.

**Server SDK**

To update or delete providers programmatically, use an [Appwrite Server SDK](/docs/sdks#server).

```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.updateSmtpProvider({
    providerId: '<PROVIDER_ID>',
    name: '<PROVIDER_NAME>',
    host: '<HOST>',
    port: 587,
    username: '<USERNAME>',
    password: '<PASSWORD>',
    encryption: sdk.SmtpEncryption.Tls,
    autoTLS: true,
    mailer: '<MAILER>',
    fromName: '<SENDER_NAME>',
    fromEmail: '<SENDER_EMAIL>',
    replyToName: '<REPLY_TO_NAME>',
    replyToEmail: '<REPLY_TO_EMAIL>',
    enabled: true
}).then(function (response) {
    console.log(response);
}, function (error) {
    console.log(error);
});

// delete provider
messaging.deleteProvider({
    providerId: '<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.updateSmtpProvider({
    providerId: '<PROVIDER_ID>',
    name: '<PROVIDER_NAME>',
    host: '<HOST>',
    port: 587,
    username: '<USERNAME>',
    password: '<PASSWORD>',
    encryption: sdk.SmtpEncryption.Tls,
    autoTLS: true,
    mailer: '<MAILER>',
    fromName: '<SENDER_NAME>',
    fromEmail: '<SENDER_EMAIL>',
    replyToName: '<REPLY_TO_NAME>',
    replyToEmail: '<REPLY_TO_EMAIL>',
    enabled: true
}).then(function (response) {
    console.log(response);
}, function (error) {
    console.log(error);
});

// delete provider
messaging.deleteProvider({
    providerId: '<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_smtp_provider(
    provider_id='<PROVIDER_ID>',
    name='<PROVIDER_NAME>',
    host='<HOST>',
    port=587,
    username='<USERNAME>',
    password='<PASSWORD>',
    encryption='tls',
    auto_tls=True,
    mailer='<MAILER>',
    from_name='<SENDER_NAME>',
    from_email='<SENDER_EMAIL>',
    reply_to_name='<REPLY_TO_NAME>',
    reply_to_email='<REPLY_TO_EMAIL>',
    enabled=True,
)
```
```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?>",
)
```
```server-rust
use appwrite::Client;
use appwrite::services::messaging::Messaging;

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

    let messaging = Messaging::new(&client);

    // update provider
    let provider = messaging.update_smtp_provider(
        "<PROVIDER_ID>",                             // providerId
        Some("<PROVIDER_NAME>"),                     // name (optional)
        Some("<HOST>"),                              // host (optional)
        Some(587),                                   // port (optional)
        Some("<USERNAME>"),                          // username (optional)
        Some("<PASSWORD>"),                          // password (optional)
        None,                                        // encryption (optional)
        Some(true),                                  // autoTLS (optional)
        Some("<MAILER>"),                            // mailer (optional)
        Some("<SENDER_NAME>"),                       // fromName (optional)
        Some("<SENDER_EMAIL>"),                      // fromEmail (optional)
        Some("<REPLY_TO_NAME>"),                     // replyToName (optional)
        Some("<REPLY_TO_EMAIL>"),                    // replyToEmail (optional)
        Some(true),                                  // enabled (optional)
    ).await?;

    // delete provider
    messaging.delete_provider("<PROVIDER_ID>").await?;

    println!("{:?}", provider);
    Ok(())
}
```

---

## Targets

URL: 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.

![Target overview](/images/docs/messaging/targets/target-overview.avif)

### 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.

[Learn more about topics](/docs/products/messaging/topics)

### Types of targets
There are three types of targets you can use to reach your targets.
| 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. |

### Add a target
Before you can send messages, make sure you have the appropriate targets added for your user.
#### 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), [email OTP](/docs/products/auth/email-otp), and [OAuth2](https://appwrite.io/docs/products/auth/oauth2) login will already have an email 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
Push notifications require configuration on both the Appwrite platform and your client app's code.

**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** > Capabilities > Search for **Push Notifications**.
1. If using SwiftUI, disable swizzling by setting `FirebaseAppDelegateProxyEnabled` to `NO` in your `Info.plist`.

![Enable Push Notification in XCode](/images/docs/messaging/targets/xcode-enable-pn.avif)

**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) guide.
```xml
<service android:name="<YOUR_NOTIFICATION_HANDLER_SERVICE>" android:exported="false">
    <intent-filter>
        <action android:name="com.google.firebase.MESSAGING_EVENT" />
    </intent-filter>
</service>
```

**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** > Capabilities > Search for **Push Notifications**.

![Enable Push Notification in XCode](/images/docs/messaging/targets/xcode-enable-pn.avif)

---

## Telesign

URL: 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.

#### 1. Add provider

To add Telesign as a provider, navigate to **Messaging** > **Providers** > **Add provider** > **SMS**.

![Add a Telesign provider](/images/docs/messaging/providers/telesign/provider.avif)

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.

#### 2. 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**.

| 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. |

After adding the following details, click **Save and continue** to enable the provider.

#### 3. 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.

**Console**

To send a test message, navigate to **Messaging** > **Messages** > **Create message** > **SMS**.

![Create SMS message](/images/docs/messaging/messages/create-sms-message.avif)

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.

**Server SDK**

To send a message programmatically, use an [Appwrite Server SDK](/docs/sdks#server).

```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({
    messageId: '<MESSAGE_ID>',
    content: '<CONTENT>',
    topics: [],
    users: [],
    targets: [],
    draft: true,
    scheduledAt: ''
});
```
```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({
    messageId: '<MESSAGE_ID>',
    content: '<CONTENT>',
    topics: [],
    users: [],
    targets: [],
    draft: true,
    scheduledAt: ''
});
```
```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
)
```
```server-rust
use appwrite::Client;
use appwrite::services::messaging::Messaging;

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

    let messaging = Messaging::new(&client);

    let message = messaging.create_sms(
        "<MESSAGE_ID>",                              // messageId
        "<CONTENT>",                                 // content
        Some(vec![]),                                // topics (optional)
        Some(vec![]),                                // users (optional)
        Some(vec![]),                                // targets (optional)
        Some(true),                                  // draft (optional)
        None,                                        // scheduledAt (optional)
    ).await?;

    println!("{:?}", message);
    Ok(())
}
```

You can follow the [Send SMS messages](/docs/products/messaging/send-sms-messages) guide to send your first push notification and test your provider.

#### 4. Manage provider

**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.

**Server SDK**

To update or delete providers programmatically, use an [Appwrite Server SDK](/docs/sdks#server).

```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({
    providerId: '<PROVIDER_ID>',
    name: '<NAME>',
    enabled: false,
    customerId: '<CUSTOMER_ID>',
    apiKey: '<API_KEY>',
    from: '<FROM>'
});
```
```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({
    providerId: '<PROVIDER_ID>',
    name: '<NAME>',
    enabled: false,
    customerId: '<CUSTOMER_ID>',
    apiKey: '<API_KEY>',
    from: '<FROM>'
});
```
```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
)
```
```server-rust
use appwrite::Client;
use appwrite::services::messaging::Messaging;

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

    let messaging = Messaging::new(&client);

    let provider = messaging.update_telesign_provider(
        "<PROVIDER_ID>",                             // providerId
        Some("<NAME>"),                              // name (optional)
        Some(false),                                 // enabled (optional)
        Some("<CUSTOMER_ID>"),                       // customerId (optional)
        Some("<API_KEY>"),                           // apiKey (optional)
        Some("<FROM>"),                              // from (optional)
    ).await?;

    println!("{:?}", provider);
    Ok(())
}
```

---

## Textmagic

URL: 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.

#### 1. Add provider

To add Textmagic as a provider, navigate to **Messaging** > **Providers** > **Add provider** > **SMS**.

![Add a Textmagic provider](/images/docs/messaging/providers/textmagic/provider.avif)

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.

#### 2. 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**.

| 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**. |

After adding the following details, click **Save and continue** to enable the provider.

#### 3. 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.

**Console**

To send a test message, navigate to **Messaging** > **Messages** > **Create message** > **SMS**.

![Create an SMS message](/images/docs/messaging/messages/create-sms-message.avif)

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.

**Server SDK**

To send a message programmatically, use an [Appwrite Server SDK](/docs/sdks#server).

```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({
    messageId: '<MESSAGE_ID>',
    content: '<CONTENT>',
    topics: [],
    users: [],
    targets: [],
    draft: true,
    scheduledAt: ''
});
```
```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({
    messageId: '<MESSAGE_ID>',
    content: '<CONTENT>',
    topics: [],
    users: [],
    targets: [],
    draft: true,
    scheduledAt: ''
});
```
```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
)
```
```server-rust
use appwrite::Client;
use appwrite::services::messaging::Messaging;

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

    let messaging = Messaging::new(&client);

    let message = messaging.create_sms(
        "<MESSAGE_ID>",                              // messageId
        "<CONTENT>",                                 // content
        Some(vec![]),                                // topics (optional)
        Some(vec![]),                                // users (optional)
        Some(vec![]),                                // targets (optional)
        Some(true),                                  // draft (optional)
        None,                                        // scheduledAt (optional)
    ).await?;

    println!("{:?}", message);
    Ok(())
}
```

You can follow the [Send SMS messages](/docs/products/messaging/send-sms-messages) guide to send your first push notification and test your provider.

#### 4. Manage provider

**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.

**Server SDK**

To update or delete providers programmatically, use an [Appwrite Server SDK](/docs/sdks#server).

```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({
    providerId: '<PROVIDER_ID>',
    name: '<NAME>',
    enabled: false,
    username: '<USERNAME>',
    apiKey: '<API_KEY>',
    from: '<FROM>'
});
```
```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({
    providerId: '<PROVIDER_ID>',
    name: '<NAME>',
    enabled: false,
    username: '<USERNAME>',
    apiKey: '<API_KEY>',
    from: '<FROM>'
});
```
```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
)
```
```server-rust
use appwrite::Client;
use appwrite::services::messaging::Messaging;

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

    let messaging = Messaging::new(&client);

    let provider = messaging.update_textmagic_provider(
        "<PROVIDER_ID>",                             // providerId
        Some("<NAME>"),                              // name (optional)
        Some(false),                                 // enabled (optional)
        Some("<USERNAME>"),                          // username (optional)
        Some("<API_KEY>"),                           // apiKey (optional)
        Some("<FROM>"),                              // from (optional)
    ).await?;

    println!("{:?}", provider);
    Ok(())
}
```

---

## Topics

URL: https://appwrite.io/docs/products/messaging/topics

In Appwrite Messaging, you can use topics to deliver messages to groups of users at once.

![Add a target](/images/docs/messaging/topics/topics.avif)

### 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.

[Learn more about targets](/docs/products/messaging/targets)

### 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
You can create topics

**Console**

![Add a topic](/images/docs/messaging/topics/create-topics.avif)
Navigate to your Appwrite Console > **Messaging** > **Topics** > **Create topic**.

**Server SDK**

```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({
    topicId: '<TOPIC_ID>',
    name: '<NAME>',
    subscribe: ['<ROLE>']
});

```
```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: ['<ROLE>']
});
```
```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
)
```
```server-rust
use appwrite::Client;
use appwrite::services::messaging::Messaging;

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

    let messaging = Messaging::new(&client);

    let topic = messaging.create_topic(
        "<TOPIC_ID>",                                // topicId
        "<NAME>",                                    // name
        Some(vec!["<ROLES>".to_string()]),            // subscribe (optional)
    ).await?;

    println!("{:?}", topic);
    Ok(())
}
```

**CLI**

**Before proceeding**

Ensure you [**install**](/docs/tooling/command-line/installation#getting-started) the CLI, [**log in**](/docs/tooling/command-line/installation#login) to your Appwrite account, and [**initialize**](/docs/tooling/command-line/installation#initialization) your Appwrite project.

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.

[Learn more about the CLI topics commands](/docs/tooling/command-line/topics#commands)

### 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**.

[Learn more about permission roles](/docs/advanced/security/permissions#permission-roles)

### Subscribe targets to a topic

**Console**

During development, you can subscribe targets to a topic for testing right in the Appwrite console.

![Add a topic](/images/docs/messaging/topics/add-subscriber.avif)
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).

**Server SDK**

```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({
    topicId: '<TOPIC_ID>',
    subscriberId: '<SUBSCRIBER_ID>',
    targetId: '<TARGET_ID>'
});
```
```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>"
)
```
```server-rust
use appwrite::Client;
use appwrite::services::messaging::Messaging;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new()
        .set_endpoint("https://<REGION>.cloud.appwrite.io/v1")
        .set_project("<PROJECT_ID>")
        .set_jwt("eyJhbVCJ9.eyJ...");

    let messaging = Messaging::new(&client);

    let subscriber = messaging.create_subscriber(
        "<TOPIC_ID>",                                // topicId
        "<SUBSCRIBER_ID>",                           // subscriberId
        "<TARGET_ID>",                               // targetId
    ).await?;

    println!("{:?}", subscriber);
    Ok(())
}
```

---

## Twilio

URL: 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.

#### 1. Add provider

To add Twilio as a provider, navigate to **Messaging** > **Providers** > **Add provider** > **SMS**.

![Add a Twilio provider](/images/docs/messaging/providers/twilio/provider.avif)

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.

#### 2. 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**.

| 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**. |

After adding the following details, click **Save and continue** to enable the provider.

#### 3. 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.

**Console**

To send a test message, navigate to **Messaging** > **Messages** > **Create message** > **SMS**.

![Create an SMS message](/images/docs/messaging/messages/create-sms-message.avif)

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.

**Server SDK**

To send a message programmatically, use an [Appwrite Server SDK](/docs/sdks#server).

```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({
    messageId: '<MESSAGE_ID>',
    content: '<CONTENT>',
    topics: [],
    users: [],
    targets: [],
    draft: true,
    scheduledAt: ''
});
```
```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({
    messageId: '<MESSAGE_ID>',
    content: '<CONTENT>',
    topics: [],
    users: [],
    targets: [],
    draft: true,
    scheduledAt: ''
});
```
```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
)
```
```server-rust
use appwrite::Client;
use appwrite::services::messaging::Messaging;

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

    let messaging = Messaging::new(&client);

    let message = messaging.create_sms(
        "<MESSAGE_ID>",                              // messageId
        "<CONTENT>",                                 // content
        Some(vec![]),                                // topics (optional)
        Some(vec![]),                                // users (optional)
        Some(vec![]),                                // targets (optional)
        Some(true),                                  // draft (optional)
        None,                                        // scheduledAt (optional)
    ).await?;

    println!("{:?}", message);
    Ok(())
}
```

#### 4. Manage provider

**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.

**Server SDK**

To update or delete providers programmatically, use an [Appwrite Server SDK](/docs/sdks#server).

```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({
    providerId: '<PROVIDER_ID>',
    name: '<NAME>',
    enabled: false,
    accountSid: '<ACCOUNT_SID>',
    authToken: '<AUTH_TOKEN>',
    from: '<FROM>'
});
```
```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({
    providerId: '<PROVIDER_ID>',
    name: '<NAME>',
    enabled: false,
    accountSid: '<ACCOUNT_SID>',
    authToken: '<AUTH_TOKEN>',
    from: '<FROM>'
});
```
```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
)
```
```server-rust
use appwrite::Client;
use appwrite::services::messaging::Messaging;

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

    let messaging = Messaging::new(&client);

    let provider = messaging.update_twilio_provider(
        "<PROVIDER_ID>",                             // providerId
        Some("<NAME>"),                              // name (optional)
        Some(false),                                 // enabled (optional)
        Some("<ACCOUNT_SID>"),                       // accountSid (optional)
        Some("<AUTH_TOKEN>"),                         // authToken (optional)
        Some("<FROM>"),                              // from (optional)
    ).await?;

    println!("{:?}", provider);
    Ok(())
}
```

---

## Vonage

URL: 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.

#### 1. Add provider

To add Vonage as a provider, navigate to **Messaging** > **Providers** > **Add provider** > **SMS**.

![Add a Vonage provider](/images/docs/messaging/providers/vonage/provider.avif)

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.

#### 2. 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**.

| 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**. |

After adding the following details, click **Save and continue** to enable the provider.

#### 3. 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.

**Console**

To send a test message, navigate to **Messaging** > **Messages** > **Create message** > **SMS**.

![Create an SMS message](/images/docs/messaging/messages/create-sms-message.avif)

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.

**Server SDK**

To send a message programmatically, use an [Appwrite Server SDK](/docs/sdks#server).

```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({
    messageId: '<MESSAGE_ID>',
    content: '<CONTENT>',
    topics: [],
    users: [],
    targets: [],
    draft: true,
    scheduledAt: ''
});
```
```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({
    messageId: '<MESSAGE_ID>',
    content: '<CONTENT>',
    topics: [],
    users: [],
    targets: [],
    draft: true,
    scheduledAt: ''
});
```
```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
)
```
```server-rust
use appwrite::Client;
use appwrite::services::messaging::Messaging;

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

    let messaging = Messaging::new(&client);

    let message = messaging.create_sms(
        "<MESSAGE_ID>",                              // messageId
        "<CONTENT>",                                 // content
        Some(vec![]),                                // topics (optional)
        Some(vec![]),                                // users (optional)
        Some(vec![]),                                // targets (optional)
        Some(true),                                  // draft (optional)
        None,                                        // scheduledAt (optional)
    ).await?;

    println!("{:?}", message);
    Ok(())
}
```

You can follow the [Send SMS messages](/docs/products/messaging/send-sms-messages) guide to send your first push notification and test your provider.

#### 4. Manage provider

**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.

**Server SDK**

To update or delete providers programmatically, use an [Appwrite Server SDK](/docs/sdks#server).

```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({
    providerId: '<PROVIDER_ID>',
    name: '<NAME>',
    enabled: false,
    apiKey: '<API_KEY>',
    apiSecret: '<API_SECRET>',
    from: '<FROM>'
});
```
```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({
    providerId: '<PROVIDER_ID>',
    name: '<NAME>',
    enabled: false,
    apiKey: '<API_KEY>',
    apiSecret: '<API_SECRET>',
    from: '<FROM>'
});
```
```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
)
```
```server-rust
use appwrite::Client;
use appwrite::services::messaging::Messaging;

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

    let messaging = Messaging::new(&client);

    let provider = messaging.update_vonage_provider(
        "<PROVIDER_ID>",                             // providerId
        Some("<NAME>"),                              // name (optional)
        Some(false),                                 // enabled (optional)
        Some("<API_KEY>"),                           // apiKey (optional)
        Some("<API_SECRET>"),                        // apiSecret (optional)
        Some("<FROM>"),                              // from (optional)
    ).await?;

    println!("{:?}", provider);
    Ok(())
}
```

---

## Network

URL: 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.

![Network map](/images/docs/network/all-maps.avif)

### 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:

- [Regions](/docs/products/network/regions): Multi-region deployments for geo-redundancy, compliance and low-latency.
- [Edges](/docs/products/network/edges): Edge nodes for fast request processing and reduced round-trip times.
- [CDN](/docs/products/network/cdn): Global infrastructure for optimized routing, enabling faster and more consistent data delivery.
- [Endpoints](/docs/products/network/endpoints): Dedicated endpoints for region-specific, edge, and compute workloads.

#### 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.

**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.

### 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.

**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).

### Features

- [Domain Name System](/docs/products/network/dns): Provides dedicated nameservers and DNS management for apex domains with SSL certification.
- [DDoS mitigation](/docs/products/network/ddos): Protects against distributed denial-of-service attacks, ensuring uninterrupted access.
- [Transport Layer Security (TLS)](/docs/products/network/tls): Encrypts data in transit for secure and private communication.
- [Firewall](/docs/products/firewall): Project rules to deny, rate limit, redirect, or bypass matching API, Functions, and Sites traffic.
- [Compression](/docs/products/network/compression): Reduces data size in transit to improve transfer speed and efficiency.
- [Caching](/docs/products/network/caching): Stores frequently accessed data for faster retrieval and lower latency.

---

## Certification Authority Authorization (CAA) records

URL: https://appwrite.io/docs/products/network/caa-records

A Certification Authority Authorization (CAA) record is a DNS record that specifies which certificate authorities (CAs) are allowed to issue TLS certificates for your domain. CAA records help prevent unauthorized certificate issuance and are defined in [RFC 8659](https://datatracker.ietf.org/doc/html/rfc8659).

When Appwrite issues a TLS certificate for a [custom domain](/docs/products/network/custom-domains), an [Appwrite Sites domain](/docs/products/sites/domains), or a [Function domain](/docs/products/functions/domains), the certificate authority used by Appwrite checks your domain's CAA records before issuing. If your domain has no CAA records at all, any CA, including Appwrite's, is allowed to issue and no action is needed from you. If your domain already has CAA records and none of them authorize the CA that Appwrite uses, issuance fails and your domain stays unverified until you add the required record.

**CAA records are additive, not exclusive**

Adding a CAA record for Appwrite does **not** replace your existing CAA records, override certificates issued by other CAs, or invalidate certificates already in use elsewhere. CAA only controls **future** certificate issuance. You can safely keep every CAA record you already have and add Appwrite's alongside them. See [Setting multiple CAA records](#multiple).

### Certificate authority used by Appwrite

Appwrite Cloud uses [Certainly](https://docs.fastly.com/products/certainly), Fastly's certificate authority, to issue TLS certificates for Sites, Functions, and custom API domains. If you need to authorize Appwrite's CA in a CAA record, use the value `certainly.com`.

For more on how Appwrite manages certificates, see the [TLS documentation](/docs/advanced/security/tls).

### Do you need a CAA record?

By default, no. CAA records are not mandatory in DNS, and if your domain has no CAA records at all, any publicly trusted CA, including the one Appwrite uses, is permitted to issue a certificate for it. This is the common case for most domains, and no action is needed from you.

You **do** need to add a CAA record that authorizes Appwrite's CA in either of these situations.

- Your domain already has one or more CAA records that do not include `certainly.com`. Existing CAA records form an allow-list, and any CA not on it is blocked.
- Your DNS provider, registrar, or organization adds CAA records automatically for new domains.

If you are unsure, inspect your existing CAA records with a tool like [DNS Checker](https://dnschecker.org/) or by running `dig CAA example.com` from a terminal.

**What the Appwrite Console shows**

When you add a custom domain, the Appwrite Console may surface a CAA value alongside the CNAME or NS record as part of the standard setup. Adding it is only strictly necessary when one of the conditions above applies. If neither applies, you can safely skip the CAA step.

#### Apex domains and subdomains

CAA records are scoped to where they sit in DNS, so adding one for Appwrite does not have to touch your main domain.

- A CAA record at an apex like `example.com` applies to the apex and is inherited by every subdomain that does **not** have its own CAA records.
- A CAA record at a subdomain like `app.example.com` applies only to that subdomain. As soon as a subdomain has any CAA record of its own, the inherited apex records are ignored for that subdomain.

If you are adding an Appwrite Site or custom domain on a subdomain and you already have a CAA policy at the apex for another CA, you have two safe choices.

1. Add Appwrite's CAA record at the subdomain only. This leaves your apex policy untouched and lets the apex CA continue issuing certificates for the apex domain.
2. Add Appwrite's CAA record at the apex alongside your existing CAA records. Both CAs remain authorized everywhere. See [Setting multiple CAA records](#multiple).

### How to add a CAA record

The exact UI differs between DNS providers, but the values are the same.

1. Open your DNS provider's DNS management dashboard.
2. Create a new record and select **CAA** as the record type.
3. Set the **name** (or **host**) to the domain you are configuring, for example `example.com` for an apex domain or `app.example.com` for a subdomain.
4. Set the **flags** to `0`.
5. Set the **tag** to `issue`.
6. Set the **value** to the CA shown in the Appwrite Console (`certainly.com` for Appwrite Cloud).
7. Save the record and wait for DNS propagation. This can take up to 48 hours.

A complete CAA record in zone file format looks like this.

```text
example.com. IN CAA 0 issue "certainly.com"
```

### Setting multiple CAA records

CAA records are additive. Each record authorizes one CA, and a CA is allowed to issue a certificate if any record at the domain matches it. To allow more than one CA, create one CAA record per CA at the same domain name.

For example, to allow both Appwrite Cloud's CA and Let's Encrypt on the same domain, add two records.

```text
example.com. IN CAA 0 issue "certainly.com"
example.com. IN CAA 0 issue "letsencrypt.org"
```

Most DNS dashboards model this as two separate CAA entries on the same host. Do not replace existing CAA records when adding the one Appwrite needs. Add the Appwrite record next to them so both your existing CAs and Appwrite's CA stay authorized.

**Using Appwrite DNS as your nameserver?**

If you have delegated your domain to [Appwrite DNS](/docs/products/network/dns) by pointing your nameservers to `ns1.appwrite.zone` and `ns2.appwrite.zone`, Appwrite automatically applies the CAA record needed for its certificate authority. You do not need to add a CAA record manually in this case. You can still add additional CAA records for other CAs from the **Domains** tab in your organization if you want to authorize them alongside Appwrite's CA.

#### Wildcard certificates

The `issue` tag controls non-wildcard certificate issuance. If a CA needs to issue a wildcard certificate (for example, `*.example.com`), it checks the `issuewild` tag instead. If no `issuewild` record exists, the CA falls back to the `issue` records.

If you have set restrictive `issuewild` records for other CAs, add an `issuewild` record for Appwrite's CA as well.

```text
example.com. IN CAA 0 issuewild "certainly.com"
```

#### Reporting violations

The `iodef` tag is optional and tells CAs where to report attempts at unauthorized issuance. It is independent of `issue` and `issuewild` records and does not need to change to use Appwrite.

```text
example.com. IN CAA 0 iodef "mailto:security@example.com"
```

### Troubleshooting

If a custom domain stays unverified or its certificate fails to issue, check the following.

- Confirm the CAA record uses the exact value shown in the Appwrite Console, with no trailing spaces, quotes, or `https://` prefix.
- Confirm the CAA record sits at the right level. For an apex domain, it belongs at the apex. For a subdomain, CAA records at the apex are inherited unless the subdomain itself has CAA records, in which case only the subdomain's records apply.
- Wait for DNS propagation. CAA changes can take up to 48 hours to be visible to the CA.
- Check the current state of your CAA records with [DNS Checker](https://dnschecker.org/) or `dig CAA example.com`.

If issues persist, [contact us](/contact-us) and we will help debug your DNS setup.

---

## Caching

URL: 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

At the region level, Appwrite provides smart in-memory caching for various resources:

- **Rows**: 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

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
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

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)

URL: 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.

![PoPs map](/images/docs/network/pops-map.avif)

### 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.

**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.

### 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

URL: 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

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
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
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 | Ratio | 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
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
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

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

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({
    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: 'webp'
});

console.log(result.href);
```

| Algorithm | Formats | Ratio | 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
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.

---

## Custom domains

URL: https://appwrite.io/docs/products/network/custom-domains

Appwrite custom domains allows you to use your own domain as your Appwrite API endpoint.

### 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
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

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. If the Console also shows a [CAA record](/docs/products/network/caa-records), add it to your DNS provider so Appwrite's certificate authority is authorized to issue a certificate for your domain. Existing CAA records should be kept in place. See [Setting multiple CAA records](/docs/products/network/caa-records#multiple).
6. 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.
7. 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

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.

---

## DDoS mitigation

URL: 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

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

- **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

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

URL: 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

- **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

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

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](/docs/products/network/caa-records) | 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

Apex domains (also known as root domains) are domains without a subdomain prefix, like `example.com` instead of `www.example.com`. Appwrite offers two methods for setting up apex domains:

#### Using NS records

This method delegates DNS management to Appwrite by changing your domain's nameservers:

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

**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.

#### Using CNAME flattening

Some DNS providers support CNAME-like behavior at the apex level through a feature called **CNAME flattening**. This allows you to use your apex domain without delegating nameservers to Appwrite.

Depending on your DNS provider, this feature may be provided through **CNAME records**, **ALIAS records**, or **ANAME records**. Check your provider's documentation for the specific DNS record they support.

To use this method:

1. In the Appwrite Console, add your apex domain and copy the provided CNAME and [CAA](/docs/products/network/caa-records) records
2. In your DNS provider's settings, create an ALIAS, ANAME, or CNAME record at the apex (depending on what your provider supports)
3. Point the record to the Appwrite hostname provided in the console
4. Add the [CAA record](/docs/products/network/caa-records) at the apex level in your DNS provider's settings, alongside any CAA records you already have
5. Wait for DNS propagation (may take up to 48 hours)
6. Return to Appwrite Console to verify the domain

This method allows you to maintain control over your remaining DNS configuration while still using your apex domain with Appwrite.

**Existing CAA records?**

If your apex already has CAA records for other certificate authorities, do not replace them. CAA records are additive. Add Appwrite's record next to your existing ones to keep every authorized CA. See [Setting multiple CAA records](/docs/products/network/caa-records#multiple).

### Adding records in Appwrite

If you delegated your domain to Appwrite's DNS servers using NS records, 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

**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.

---

## Edges

URL: 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.

![Edges map](/images/docs/network/edges-map.avif)

**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.

### 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 | Status |
|---------------------|--------|-----------------|
| 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

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.

**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.

### 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.

**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.

---

## Endpoints

URL: 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

The **`appwrite.network`** domain provides geo-balanced endpoints that route traffic to the nearest edge node based on the user's geographic location.

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

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

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

| 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

URL: 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.

![Regions map](/images/docs/network/regions-map.avif)

### List

Appwrite is currently available in the following list of regions:

| Region | Code | Endpoint | Status |
|---------------------|--------|-----------------------------------------|-----------------|
| 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

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.

**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.

### 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)

URL: 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

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

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.

---

## Sites

URL: 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, [Firewall](/docs/products/firewall), 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.

![Create first site](/images/docs/sites/create-first-site.avif)

Try out one of our most popular framework quick-starts:

- [TanStack Start](/docs/products/sites/quick-start/tanstack-start)

- [Next.js](/docs/products/sites/quick-start/nextjs)

- [Nuxt](/docs/products/sites/quick-start/nuxt)

- [SvelteKit](/docs/products/sites/quick-start/sveltekit)

- [Vue](/docs/products/sites/quick-start/vue)

- [Astro](/docs/products/sites/quick-start/astro)

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:

- [Migrating from Vercel](/docs/products/sites/migrations/vercel)

---

## Deploy from CLI

URL: 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

You can create sites using the CLI without needing to access the Console.

To deploy your site with the Appwrite CLI, use the `appwrite init sites` command to create a starter site and paste your code into the generated file and folder.

```sh
appwrite init sites
```

To deploy the generated code, add any dependencies and push the site using the following command:

```sh
appwrite push sites
```

[Learn more about the CLI sites commands](/docs/tooling/command-line/sites#commands)

#### 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.

[Learn more about appwrite.config.json](/docs/tooling/command-line/sites#appwritejson)

For larger projects, you can split site definitions into a separate JSON file with [multi-file CLI configuration](/docs/tooling/command-line/installation#multi-file-configuration).

### 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

URL: 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**.

![Git repository](/images/docs/sites/git-repo.avif)

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**.

#### Build triggers

Build triggers control which Git changes create automatic deployments. You can configure branch filters and path filters with glob patterns.

**Branch filters** match branch names. Add patterns to limit automatic deployments to specific branches.

```txt
main
staging
preview/**
```

**Path filters** match files changed in a commit or pull request. Leave this field empty to create deployments for all file changes, or add patterns to deploy only when specific files change.

```txt
apps/web/**
packages/ui/**
!docs/**
```

Use these formats to write glob patterns.

| Pattern | What it does | Example | Matches |
| ------- | ----------------------------------------- | ---------------- | ----------------------------------- |
| `main` | Matches exact text | `main` | `main` |
| `*` | Matches within one path segment | `release/*` | `release/1.0` |
| `**` | Matches across nested path segments | `preview/**` | `preview/test`, `preview/team/test` |
| `?` | Matches one character in one path segment | `v?.?` | `v1.0`, `v2.5` |
| `[abc]` | Matches one character from a set | `[Mm]ain` | `main`, `Main` |
| `[a-z]` | Matches one character in a range | `release/[0-9]*` | `release/1.0` |
| `\` | Escapes a special character | `file\?.txt` | `file?.txt` |
| `!` | Excludes matching branches or paths | `!docs/**` | Excludes changes under `docs/` |

#### 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

URL: 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.

    ![Manual deployment](/images/docs/sites/manual-deployment.avif)
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

URL: 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**.

![Deployment logs](/images/docs/sites/deployment-logs.avif)

### 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

![Create deployment](/images/docs/sites/create-deployment.avif)

### 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.

![Cancel deployment](/images/docs/sites/cancel-deployment.avif)

### 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.

![Redeploy](/images/docs/sites/redeploy.avif)

Redeployment behavior varies depending on how the initial deployment was created.

**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
- Longer [build timeouts](/docs/advanced/billing/compute#build-timeouts) (45 minutes vs 15 minutes on Free; Enterprise is custom)
- Customizable [build and runtime specifications](/docs/advanced/billing/compute) for CPU and memory on each site

### Deployment retention

Deployment retention controls how long Appwrite keeps non-active site deployments. The active deployment is always kept. When a non-active deployment is older than the configured retention period, Appwrite automatically deletes it during maintenance. Set the value to `0` to keep non-active deployments forever.

To configure deployment retention from the Appwrite Console:

1. Navigate to **Sites**.
2. Open the site you want to configure.
3. Go to **Settings** > **Deployment retention**.
4. Turn on **Keep deployments forever**, or turn it off and choose how long to keep non-active deployments.
5. Click **Update**.

![Site deployment retention settings](/images/docs/sites/deployment-retention.avif)

The Console provides common presets from `1 Week` to `10 Years`. When using the API or a Server SDK, set `deploymentRetention` to the number of days to keep non-active deployments. The value must be between `0` and `36500`, where `0` means unlimited retention.

When updating a site with a Server SDK, pass the existing settings you do not intend to change and update only `deploymentRetention`.

```server-nodejs
const site = await sites.get({
    siteId: '<SITE_ID>'
});

await sites.update({
    siteId: site.$id,
    name: site.name,
    framework: site.framework,
    enabled: site.enabled ?? undefined,
    logging: site.logging ?? undefined,
    timeout: site.timeout ?? undefined,
    installCommand: site.installCommand ?? undefined,
    buildCommand: site.buildCommand ?? undefined,
    startCommand: site.startCommand ?? undefined,
    outputDirectory: site.outputDirectory ?? undefined,
    buildRuntime: site.buildRuntime ?? undefined,
    adapter: site.adapter ?? undefined,
    fallbackFile: site.fallbackFile ?? undefined,
    installationId: site.installationId ?? undefined,
    providerRepositoryId: site.providerRepositoryId ?? undefined,
    providerBranch: site.providerBranch ?? undefined,
    providerSilentMode: site.providerSilentMode ?? undefined,
    providerRootDirectory: site.providerRootDirectory ?? undefined,
    buildSpecification: site.buildSpecification ?? undefined,
    runtimeSpecification: site.runtimeSpecification ?? undefined,
    deploymentRetention: 90
});
```

---

## Develop Appwrite Sites

URL: 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.

![Rendering strategy](/images/docs/sites/build-settings-rendering-ssr.avif)

### 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).

![Timeout](/images/docs/sites/timeout.avif)

The default timeout is set at `15 seconds` and the maximum value possible is `30 seconds`.

### Resource limits

Under **Settings** - **Resource limits**, you can set **build** and **runtime** specifications independently. The build spec applies while dependencies are installed and your site is built for deployment; the runtime spec applies when your site serves traffic, including server-side rendering (SSR). Both use the same CPU and memory tiers on Cloud.

On Appwrite Cloud, customizing specifications requires the **Pro** plan. See [Compute](/docs/advanced/billing/compute) for tiers, GB-hours, and pricing.

#### Build timeouts

On Appwrite Cloud, the **build** phase of each deployment must complete within your plan’s **maximum build duration** (for example, 15 minutes on Free and 45 minutes on Pro and Scale). See [Build timeouts](/docs/advanced/billing/compute#build-timeouts) and the [pricing page](/pricing).

### 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.

![Install command](/images/docs/sites/build-settings-install-command.avif)

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.

- &nbsp;
- Framework
- Default package manager
- Install command
- Build command
- Output directory

---

-
![TanStack Start logo](/images/platforms/light/tanstack.svg)
- **TanStack Start**
- `npm`
- `npm install`
- `npm run build`
- `./dist`

---

-
- **Next.js**
- `npm`
- `npm install`
- `npm run build`
- `./.next`

---

-
- **Nuxt**
- `npm`
- `npm install` or `yarn install`
- `npm run build` or `yarn build`
- `./.output`

---

-
- **SvelteKit**
- `npm`
- `npm install`
- `npm run build`
- `./build`

---

-
- **Angular**
- `npm`
- `npm install`
- `npm run build`
- `./dist/angular/browser`

---

-
![Remix logo](/images/platforms/light/remix.svg)
- **Remix**
- `npm`
- `npm install`
- `npm run build`
- `./build`

---

-
- **Astro**
- `npm`
- `npm install`
- `npm run build`
- `./dist`

---
-
- **Flutter**
- `pub`
- `flutter pub get`
- `flutter build web --release -t lib/main.dart`
- `./build/web`

---

-
- **React Native**
- `npm`
- `npm install`
- `npm run build`
- `./dist`

---

-
- **Other JavaScript**
- `npm`
- (Empty)
- (Empty)
- `./`

---

## Domains

URL: 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).

![Branch and commit URLs](/images/docs/sites/branch-commit-urls.avif)

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 via NS records:

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.

![Add domain](/images/docs/sites/add-domain-ns.avif)

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.

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.

[Learn more about Appwrite DNS server](/docs/products/network/dns)

#### Add an apex domain without changing nameservers

To add an apex domain without changing your NS records via [CNAME flattening](/docs/products/network/dns#using-cname-flattening):

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. Copy the **CNAME** record provided by Appwrite.
6. Copy the **[CAA](/docs/products/network/caa-records)** record provided by Appwrite.
7. In your DNS provider's settings, create an ALIAS, ANAME, or CNAME record at the apex (depending on what your provider supports).
8. Point the record to the Appwrite hostname provided in the console.
9. Add the [CAA record](/docs/products/network/caa-records) to your DNS provider's settings, also pointed at the apex. If you already have CAA records on your domain, keep them and add Appwrite's alongside them. See [Setting multiple CAA records](/docs/products/network/caa-records#multiple).
10. Return to the Site settings and wait for verification status.

![Add domain](/images/docs/sites/add-domain-cname.avif)

DNS changes can take up to 48 hours to propagate. Once verified, your apex domain will be ready to use with full control over your remaining DNS configuration.

#### 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.

![Add domain](/images/docs/sites/add-domain.avif)

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

**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`.

---

## Environment variables

URL: https://appwrite.io/docs/products/sites/environment-variables

Appwrite Sites can read environment variables at build and runtime. Use them to pass constants and secrets such as API keys, connection strings, and feature flags without hardcoding them in your source.

A site reads from three sources, in this order of precedence:

1. **Project variables** are shared across every function and site in your project. Set them once and every site inherits them automatically. See [project variables](/docs/partners/project/environment-variables) for the full reference.
2. **Site variables** are scoped to a single site. Override a project variable for one site by setting the same key on the site itself.
3. **Appwrite-injected variables** are set by Appwrite at deployment time (for example, `APPWRITE_SITE_PROJECT_ID`). These take final precedence and cannot be overridden.

**Redeployment required**

Variable changes only take effect on the next deployment. Redeploy your site after creating, updating, or deleting variables.

### Manage in the Console

1. Navigate to your site in the Appwrite Console.
2. Open the **Settings** tab > **Environment variables** section.
3. Click **Create variable** and enter a key and value.
4. Optionally select the **Secret** checkbox to prevent any team member from reading the value after creation.
5. Click **Create**, then redeploy the site for the change to take effect.

![Site environment variables](/images/docs/sites/env-variables.avif)

You can also configure global variables that apply to all your sites from your project's **Settings** page. See [project variables](/docs/partners/project/environment-variables) for details.

### Manage with a Server SDK

You can also manage site variables programmatically using a [Server SDK](/docs/sdks#server). Each call requires an [API key](/docs/partners/project/api-keys) with the `sites.write` scope to create, update, or delete variables, or the `sites.read` scope to list and read them.

#### Create a variable

```server-nodejs
import { Client, Sites } from '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 API key

const sites = new Sites(client);

const result = await sites.createVariable({
    siteId: '<SITE_ID>',
    key: '<KEY>',
    value: '<VALUE>',
    secret: false // optional
});
```
```server-deno
import { Client, Sites } 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 API key

const sites = new Sites(client);

const result = await sites.createVariable({
    siteId: '<SITE_ID>',
    key: '<KEY>',
    value: '<VALUE>',
    secret: false // optional
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Sites;

$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 API key

$sites = new Sites($client);

$result = $sites->createVariable(
    siteId: '<SITE_ID>',
    key: '<KEY>',
    value: '<VALUE>',
    secret: false // optional
);
```
```server-python
from appwrite.client import Client
from appwrite.services.sites import Sites
from appwrite.models import Variable

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 API key

sites = Sites(client)

result: Variable = sites.create_variable(
    site_id = '<SITE_ID>',
    key = '<KEY>',
    value = '<VALUE>',
    secret = False # optional
)

print(result.model_dump())
```
```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 API key

sites = Sites.new(client)

result = sites.create_variable(
    site_id: '<SITE_ID>',
    key: '<KEY>',
    value: '<VALUE>',
    secret: false # 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 API key

Sites sites = new Sites(client);

Variable result = await sites.CreateVariable(
    siteId: "<SITE_ID>",
    key: "<KEY>",
    value: "<VALUE>",
    secret: false // 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 API key

Sites sites = Sites(client);

Variable result = await sites.createVariable(
    siteId: '<SITE_ID>',
    key: '<KEY>',
    value: '<VALUE>',
    secret: false, // (optional)
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.coroutines.CoroutineCallback
import io.appwrite.services.Sites

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 API key

val sites = Sites(client)

val response = sites.createVariable(
    siteId = "<SITE_ID>",
    key = "<KEY>",
    value = "<VALUE>",
    secret = false // optional
)
```
```server-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Sites;

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 API key

Sites sites = new Sites(client);

sites.createVariable(
    "<SITE_ID>", // siteId
    "<KEY>", // key
    "<VALUE>", // value
    false, // secret (optional)
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        System.out.println(result);
    })
);
```
```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 API key

let sites = Sites(client)

let variable = try await sites.createVariable(
    siteId: "<SITE_ID>",
    key: "<KEY>",
    value: "<VALUE>",
    secret: false // optional
)
```
```server-go
package main

import (
    "fmt"
    "github.com/appwrite/sdk-for-go/appwrite"
)

client := appwrite.NewClient(
    appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"),
    appwrite.WithProject("<YOUR_PROJECT_ID>"),
    appwrite.WithKey("<YOUR_API_KEY>"),
)

sites := appwrite.NewSites(client)

response, error := sites.CreateVariable(
    "<SITE_ID>",
    "<KEY>",
    "<VALUE>",
    appwrite.WithCreateVariableSecret(false),
)
```
```server-rust
use appwrite::Client;
use appwrite::services::Sites;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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 API key

    let sites = Sites::new(&client);

    let result = sites.create_variable(
        "<SITE_ID>",
        "<KEY>",
        "<VALUE>",
        Some(false) // optional
    ).await?;

    let _ = result;

    Ok(())
}
```

#### List variables

```server-nodejs
import { Client, Sites } from '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 API key

const sites = new Sites(client);

const result = await sites.listVariables({
    siteId: '<SITE_ID>'
});
```
```server-deno
import { Client, Sites } 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 API key

const sites = new Sites(client);

const result = await sites.listVariables({
    siteId: '<SITE_ID>'
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Sites;

$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 API key

$sites = new Sites($client);

$result = $sites->listVariables(
    siteId: '<SITE_ID>'
);
```
```server-python
from appwrite.client import Client
from appwrite.services.sites import Sites
from appwrite.models import VariableList

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 API key

sites = Sites(client)

result: VariableList = sites.list_variables(
    site_id = '<SITE_ID>'
)

print(result.model_dump())
```
```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 API key

sites = Sites.new(client)

result = sites.list_variables(
    site_id: '<SITE_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 API key

Sites sites = new Sites(client);

VariableList result = await sites.ListVariables(
    siteId: "<SITE_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 API key

Sites sites = Sites(client);

VariableList result = await sites.listVariables(
    siteId: '<SITE_ID>',
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.coroutines.CoroutineCallback
import io.appwrite.services.Sites

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 API key

val sites = Sites(client)

val response = sites.listVariables(
    siteId = "<SITE_ID>"
)
```
```server-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Sites;

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 API key

Sites sites = new Sites(client);

sites.listVariables(
    "<SITE_ID>", // siteId
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        System.out.println(result);
    })
);
```
```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 API key

let sites = Sites(client)

let variableList = try await sites.listVariables(
    siteId: "<SITE_ID>"
)
```
```server-go
package main

import (
    "fmt"
    "github.com/appwrite/sdk-for-go/appwrite"
)

client := appwrite.NewClient(
    appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"),
    appwrite.WithProject("<YOUR_PROJECT_ID>"),
    appwrite.WithKey("<YOUR_API_KEY>"),
)

sites := appwrite.NewSites(client)

response, error := sites.ListVariables(
    "<SITE_ID>",
)
```
```server-rust
use appwrite::Client;
use appwrite::services::Sites;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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 API key

    let sites = Sites::new(&client);

    let result = sites.list_variables(
        "<SITE_ID>"
    ).await?;

    let _ = result;

    Ok(())
}
```

#### Get a variable

```server-nodejs
import { Client, Sites } from '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 API key

const sites = new Sites(client);

const result = await sites.getVariable({
    siteId: '<SITE_ID>',
    variableId: '<VARIABLE_ID>'
});
```
```server-deno
import { Client, Sites } 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 API key

const sites = new Sites(client);

const result = await sites.getVariable({
    siteId: '<SITE_ID>',
    variableId: '<VARIABLE_ID>'
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Sites;

$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 API key

$sites = new Sites($client);

$result = $sites->getVariable(
    siteId: '<SITE_ID>',
    variableId: '<VARIABLE_ID>'
);
```
```server-python
from appwrite.client import Client
from appwrite.services.sites import Sites
from appwrite.models import Variable

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 API key

sites = Sites(client)

result: Variable = sites.get_variable(
    site_id = '<SITE_ID>',
    variable_id = '<VARIABLE_ID>'
)

print(result.model_dump())
```
```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 API key

sites = Sites.new(client)

result = sites.get_variable(
    site_id: '<SITE_ID>',
    variable_id: '<VARIABLE_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 API key

Sites sites = new Sites(client);

Variable result = await sites.GetVariable(
    siteId: "<SITE_ID>",
    variableId: "<VARIABLE_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 API key

Sites sites = Sites(client);

Variable result = await sites.getVariable(
    siteId: '<SITE_ID>',
    variableId: '<VARIABLE_ID>',
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.coroutines.CoroutineCallback
import io.appwrite.services.Sites

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 API key

val sites = Sites(client)

val response = sites.getVariable(
    siteId = "<SITE_ID>",
    variableId = "<VARIABLE_ID>"
)
```
```server-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Sites;

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 API key

Sites sites = new Sites(client);

sites.getVariable(
    "<SITE_ID>", // siteId
    "<VARIABLE_ID>", // variableId
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        System.out.println(result);
    })
);
```
```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 API key

let sites = Sites(client)

let variable = try await sites.getVariable(
    siteId: "<SITE_ID>",
    variableId: "<VARIABLE_ID>"
)
```
```server-go
package main

import (
    "fmt"
    "github.com/appwrite/sdk-for-go/appwrite"
)

client := appwrite.NewClient(
    appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"),
    appwrite.WithProject("<YOUR_PROJECT_ID>"),
    appwrite.WithKey("<YOUR_API_KEY>"),
)

sites := appwrite.NewSites(client)

response, error := sites.GetVariable(
    "<SITE_ID>",
    "<VARIABLE_ID>",
)
```
```server-rust
use appwrite::Client;
use appwrite::services::Sites;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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 API key

    let sites = Sites::new(&client);

    let result = sites.get_variable(
        "<SITE_ID>",
        "<VARIABLE_ID>"
    ).await?;

    let _ = result;

    Ok(())
}
```

#### Update a variable

You can change a variable's `key`, `value`, or `secret` flag. Marking a variable as secret is one-way. Once set, the value is no longer readable from the Console or API.

```server-nodejs
import { Client, Sites } from '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 API key

const sites = new Sites(client);

const result = await sites.updateVariable({
    siteId: '<SITE_ID>',
    variableId: '<VARIABLE_ID>',
    key: '<KEY>',
    value: '<VALUE>', // optional
    secret: false // optional
});
```
```server-deno
import { Client, Sites } 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 API key

const sites = new Sites(client);

const result = await sites.updateVariable({
    siteId: '<SITE_ID>',
    variableId: '<VARIABLE_ID>',
    key: '<KEY>',
    value: '<VALUE>', // optional
    secret: false // optional
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Sites;

$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 API key

$sites = new Sites($client);

$result = $sites->updateVariable(
    siteId: '<SITE_ID>',
    variableId: '<VARIABLE_ID>',
    key: '<KEY>',
    value: '<VALUE>', // optional
    secret: false // optional
);
```
```server-python
from appwrite.client import Client
from appwrite.services.sites import Sites
from appwrite.models import Variable

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 API key

sites = Sites(client)

result: Variable = sites.update_variable(
    site_id = '<SITE_ID>',
    variable_id = '<VARIABLE_ID>',
    key = '<KEY>',
    value = '<VALUE>', # optional
    secret = False # optional
)

print(result.model_dump())
```
```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 API key

sites = Sites.new(client)

result = sites.update_variable(
    site_id: '<SITE_ID>',
    variable_id: '<VARIABLE_ID>',
    key: '<KEY>',
    value: '<VALUE>', # optional
    secret: false # 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 API key

Sites sites = new Sites(client);

Variable result = await sites.UpdateVariable(
    siteId: "<SITE_ID>",
    variableId: "<VARIABLE_ID>",
    key: "<KEY>",
    value: "<VALUE>", // optional
    secret: false // 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 API key

Sites sites = Sites(client);

Variable result = await sites.updateVariable(
    siteId: '<SITE_ID>',
    variableId: '<VARIABLE_ID>',
    key: '<KEY>',
    value: '<VALUE>', // (optional)
    secret: false, // (optional)
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.coroutines.CoroutineCallback
import io.appwrite.services.Sites

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 API key

val sites = Sites(client)

val response = sites.updateVariable(
    siteId = "<SITE_ID>",
    variableId = "<VARIABLE_ID>",
    key = "<KEY>",
    value = "<VALUE>", // optional
    secret = false // optional
)
```
```server-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Sites;

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 API key

Sites sites = new Sites(client);

sites.updateVariable(
    "<SITE_ID>", // siteId
    "<VARIABLE_ID>", // variableId
    "<KEY>", // key
    "<VALUE>", // value (optional)
    false, // secret (optional)
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        System.out.println(result);
    })
);
```
```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 API key

let sites = Sites(client)

let variable = try await sites.updateVariable(
    siteId: "<SITE_ID>",
    variableId: "<VARIABLE_ID>",
    key: "<KEY>",
    value: "<VALUE>", // optional
    secret: false // optional
)
```
```server-go
package main

import (
    "fmt"
    "github.com/appwrite/sdk-for-go/appwrite"
)

client := appwrite.NewClient(
    appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"),
    appwrite.WithProject("<YOUR_PROJECT_ID>"),
    appwrite.WithKey("<YOUR_API_KEY>"),
)

sites := appwrite.NewSites(client)

response, error := sites.UpdateVariable(
    "<SITE_ID>",
    "<VARIABLE_ID>",
    "<KEY>",
    appwrite.WithUpdateVariableValue("<VALUE>"),
    appwrite.WithUpdateVariableSecret(false),
)
```
```server-rust
use appwrite::Client;
use appwrite::services::Sites;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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 API key

    let sites = Sites::new(&client);

    let result = sites.update_variable(
        "<SITE_ID>",
        "<VARIABLE_ID>",
        "<KEY>",
        Some("<VALUE>"), // optional
        Some(false) // optional
    ).await?;

    let _ = result;

    Ok(())
}
```

#### Delete a variable

```server-nodejs
import { Client, Sites } from '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 API key

const sites = new Sites(client);

const result = await sites.deleteVariable({
    siteId: '<SITE_ID>',
    variableId: '<VARIABLE_ID>'
});
```
```server-deno
import { Client, Sites } 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 API key

const sites = new Sites(client);

const result = await sites.deleteVariable({
    siteId: '<SITE_ID>',
    variableId: '<VARIABLE_ID>'
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Sites;

$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 API key

$sites = new Sites($client);

$result = $sites->deleteVariable(
    siteId: '<SITE_ID>',
    variableId: '<VARIABLE_ID>'
);
```
```server-python
from appwrite.client import Client
from appwrite.services.sites import Sites

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 API key

sites = Sites(client)

result = sites.delete_variable(
    site_id = '<SITE_ID>',
    variable_id = '<VARIABLE_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 API key

sites = Sites.new(client)

result = sites.delete_variable(
    site_id: '<SITE_ID>',
    variable_id: '<VARIABLE_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 API key

Sites sites = new Sites(client);

await sites.DeleteVariable(
    siteId: "<SITE_ID>",
    variableId: "<VARIABLE_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 API key

Sites sites = Sites(client);

await sites.deleteVariable(
    siteId: '<SITE_ID>',
    variableId: '<VARIABLE_ID>',
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.coroutines.CoroutineCallback
import io.appwrite.services.Sites

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 API key

val sites = Sites(client)

val response = sites.deleteVariable(
    siteId = "<SITE_ID>",
    variableId = "<VARIABLE_ID>"
)
```
```server-java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Sites;

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 API key

Sites sites = new Sites(client);

sites.deleteVariable(
    "<SITE_ID>", // siteId
    "<VARIABLE_ID>", // variableId
    new CoroutineCallback<>((result, error) -> {
        if (error != null) {
            error.printStackTrace();
            return;
        }

        System.out.println(result);
    })
);
```
```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 API key

let sites = Sites(client)

let result = try await sites.deleteVariable(
    siteId: "<SITE_ID>",
    variableId: "<VARIABLE_ID>"
)
```
```server-go
package main

import (
    "fmt"
    "github.com/appwrite/sdk-for-go/appwrite"
)

client := appwrite.NewClient(
    appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"),
    appwrite.WithProject("<YOUR_PROJECT_ID>"),
    appwrite.WithKey("<YOUR_API_KEY>"),
)

sites := appwrite.NewSites(client)

response, error := sites.DeleteVariable(
    "<SITE_ID>",
    "<VARIABLE_ID>",
)
```
```server-rust
use appwrite::Client;
use appwrite::services::Sites;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    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 API key

    let sites = Sites::new(&client);

    sites.delete_variable(
        "<SITE_ID>",
        "<VARIABLE_ID>"
    ).await?;

    Ok(())
}
```

### Read variables in your site

Read variables inside your site using your framework's standard environment lookup. For SSR sites, variables are read at runtime; for static sites, variables marked for build are inlined at build time.

For framework-specific guidance such as Next.js, SvelteKit, or Astro, see the [framework adapters](/docs/products/sites/frameworks).

### Appwrite-injected variables

Appwrite passes the following environment variables into every site deployment by default. They take precedence over your own variables, so do not set keys with the `APPWRITE_` prefix.

| Variable | Description | Available at Build and/or Run Time |
| ------------------------------------ | ------------------------------------------------------------- | ---------------------------------- |
| `APPWRITE_SITE_API_ENDPOINT` | The API endpoint of the running site | Both |
| `APPWRITE_VERSION` | The Appwrite version used to run the site | Both |
| `APPWRITE_REGION` | The region where the site will run from | Both |
| `APPWRITE_DEPLOYMENT_TYPE` | The deployment source type, such as `manual`, `cli`, or `vcs` | Both |
| `APPWRITE_SITE_API_KEY` | The site API key used for server authentication | Build time |
| `APPWRITE_SITE_ID` | The ID of the running site | Both |
| `APPWRITE_SITE_NAME` | The name of the running site | Both |
| `APPWRITE_SITE_DEPLOYMENT` | The deployment ID of the running site | Both |
| `APPWRITE_SITE_PROJECT_ID` | The project ID of the running site | Both |
| `APPWRITE_SITE_RUNTIME_NAME` | The runtime name of the running site | Both |
| `APPWRITE_SITE_RUNTIME_VERSION` | The runtime version of the running site | Both |
| `APPWRITE_SITE_CPUS` | The CPU (runtime) specification of the running site | Both |
| `APPWRITE_SITE_MEMORY` | The memory (runtime) specification of the running site | Both |
| `APPWRITE_VCS_REPOSITORY_ID` | The provider repository ID for VCS deployments | Both |
| `APPWRITE_VCS_REPOSITORY_NAME` | The provider repository name for VCS deployments | Both |
| `APPWRITE_VCS_REPOSITORY_OWNER` | The owner of the provider repository | Both |
| `APPWRITE_VCS_REPOSITORY_URL` | The URL of the provider repository | Both |
| `APPWRITE_VCS_REPOSITORY_BRANCH` | The branch used for the VCS deployment | Both |
| `APPWRITE_VCS_REPOSITORY_BRANCH_URL` | The URL of the branch used for the VCS deployment | Both |
| `APPWRITE_VCS_COMMIT_HASH` | The commit hash used for the VCS deployment | Both |
| `APPWRITE_VCS_COMMIT_MESSAGE` | The commit message used for the VCS deployment | Both |
| `APPWRITE_VCS_COMMIT_URL` | The URL of the commit used for the VCS deployment | Both |
| `APPWRITE_VCS_COMMIT_AUTHOR_NAME` | The name of the VCS commit author | Both |
| `APPWRITE_VCS_COMMIT_AUTHOR_URL` | The URL of the VCS commit author | Both |
| `APPWRITE_VCS_ROOT_DIRECTORY` | The root directory configured for the VCS deployment | Both |

VCS metadata variables are populated for Git deployments. For manual and CLI deployments, VCS fields may be empty.

### Secret variables

Mark a variable as **Secret** to hide its value from the Console and API after creation. Only the site runtime can read the value at build and runtime. Team members and external integrations cannot retrieve it after creation.

You can mark a variable as secret either when you create it or by updating an existing variable. Marking a variable as secret cannot be reversed. To replace a secret value, delete the variable and create a new one with the same key.

### Limits

| Field | Limit |
|----------------|------------------------------------------------|
| Variable ID | 36 characters, `a-z A-Z 0-9 . - _` |
| Key | 255 characters |
| Value | 8192 characters |

---

## Frameworks

URL: 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

- &nbsp;
- Framework
- Rendering strategy

---

-
![TanStack Start logo](/images/platforms/light/tanstack.svg)
- [**TanStack Start**](/docs/products/sites/quick-start/tanstack-start)
- `SSR` `Static`

---

-
- [**Next.js**](/docs/products/sites/quick-start/nextjs)
- `SSR` `Static`

---

-
- [**Nuxt**](/docs/products/sites/quick-start/nuxt)
- `SSR` `Static`

---

-
- [**SvelteKit**](/docs/products/sites/quick-start/sveltekit)
- `SSR` `Static`

---

-
- [**Angular**](/docs/products/sites/quick-start/angular)
- `SSR` `Static`

---

-
![Remix logo](/images/platforms/light/remix.svg)
- [**Remix**](/docs/products/sites/quick-start/remix)
- `SSR` `Static`

---

-
- [**Astro**](/docs/products/sites/quick-start/astro)
- `SSR` `Static`

---
-
- [**Flutter Web**](/docs/products/sites/quick-start/flutter)
- `Static`

---

-
- [**React Native**](/docs/products/sites/quick-start/react-native)
- `Static`

---

-
- [**Other JavaScript**](/docs/products/sites/quick-start/vanilla)
- `Static`

---

#### 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

URL: 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.

![Instant rollback](/images/docs/sites/instant-rollback.avif)

This will allow you to select a previously active, ready deployment to promote to currently active state.

---

## Logs

URL: 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.

![Log details](/images/docs/sites/log-details.avif)

You can find both request and response information, including parameters and headers.

**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.

### 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.

![Logging settings](/images/docs/sites/logging-settings.avif)

### 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

URL: 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.

- 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

### Migration process

Follow these steps to move your application from Vercel to Appwrite Sites.

#### 1. 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.

#### 2. 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.

### 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

#### 1. 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

#### 2. 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`

#### 3. 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

**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.

### Migrating a subdomain

For subdomains, both Vercel and Appwrite use CNAME records, making the migration process simpler.

#### 1. 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

#### 2. 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

### Domain rule types

When adding a domain in Appwrite, you can choose from three rule types: Active deployment, Git branch, or Redirect.

[Learn more about domain rule types](/docs/products/sites/domains#domain-rule-types)

### Configure build settings

After setting up your project and domain, you'll need to configure your build settings to match your Vercel configuration.

#### 1. 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)

![Build settings](/images/docs/sites/build-settings-install-command.avif)

### Framework defaults

Appwrite automatically detects and applies default settings for popular frameworks like Next.js, Nuxt, SvelteKit, Angular, and Astro.

[Learn more about framework defaults and project dependencies](/docs/products/sites/develop#project-dependencies)

### Manage environment variables

Environment variables require special attention during migration, as they control how your application behaves in different environments.

#### 1. 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

**System variables**

Vercel automatically provides system variables like `VERCEL_URL`. You'll need to adapt your code to use Appwrite's equivalent system variables.

#### 2. 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

![Environment variables](/images/docs/sites/env-variables.avif)

### Appwrite system variables
Appwrite automatically injects these variables into your site:

| Variable | Description | Available at Build and/or Run Time |
| ------------------------------------ | ------------------------------------------------------------- | ---------------------------------- |
| `APPWRITE_SITE_API_ENDPOINT` | The API endpoint of the running site | Both |
| `APPWRITE_VERSION` | The Appwrite version used to run the site | Both |
| `APPWRITE_REGION` | The region where the site will run from | Both |
| `APPWRITE_DEPLOYMENT_TYPE` | The deployment source type, such as `manual`, `cli`, or `vcs` | Both |
| `APPWRITE_SITE_API_KEY` | The site API key used for server authentication | Build time |
| `APPWRITE_SITE_ID` | The ID of the running site | Both |
| `APPWRITE_SITE_NAME` | The name of the running site | Both |
| `APPWRITE_SITE_DEPLOYMENT` | The deployment ID of the running site | Both |
| `APPWRITE_SITE_PROJECT_ID` | The project ID of the running site | Both |
| `APPWRITE_SITE_RUNTIME_NAME` | The runtime name of the running site | Both |
| `APPWRITE_SITE_RUNTIME_VERSION` | The runtime version of the running site | Both |
| `APPWRITE_SITE_CPUS` | The CPU (runtime) specification of the running site | Both |
| `APPWRITE_SITE_MEMORY` | The memory (runtime) specification of the running site | Both |
| `APPWRITE_VCS_REPOSITORY_ID` | The provider repository ID for VCS deployments | Both |
| `APPWRITE_VCS_REPOSITORY_NAME` | The provider repository name for VCS deployments | Both |
| `APPWRITE_VCS_REPOSITORY_OWNER` | The owner of the provider repository | Both |
| `APPWRITE_VCS_REPOSITORY_URL` | The URL of the provider repository | Both |
| `APPWRITE_VCS_REPOSITORY_BRANCH` | The branch used for the VCS deployment | Both |
| `APPWRITE_VCS_REPOSITORY_BRANCH_URL` | The URL of the branch used for the VCS deployment | Both |
| `APPWRITE_VCS_COMMIT_HASH` | The commit hash used for the VCS deployment | Both |
| `APPWRITE_VCS_COMMIT_MESSAGE` | The commit message used for the VCS deployment | Both |
| `APPWRITE_VCS_COMMIT_URL` | The URL of the commit used for the VCS deployment | Both |
| `APPWRITE_VCS_COMMIT_AUTHOR_NAME` | The name of the VCS commit author | Both |
| `APPWRITE_VCS_COMMIT_AUTHOR_URL` | The URL of the VCS commit author | Both |
| `APPWRITE_VCS_ROOT_DIRECTORY` | The root directory configured for the VCS deployment | Both |

VCS metadata variables are populated for Git deployments. For manual and CLI deployments, VCS fields may be empty.

### Handle redirects and rewrites

One key difference between Vercel and Appwrite is how they handle redirects and rewrites.

**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.

#### 1. 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`).

#### 2. 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
        }
      });
    }
  }
}
```

### Migrate serverless functions

There are two approaches to serverless functions when migrating from Vercel to Appwrite:

**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).

#### 1. 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.

#### 2. 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

[Learn how to create and deploy functions](/docs/products/functions/quick-start)

#### 3. 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'
});
```

### 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.

#### 1. 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
});
```

### 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

URL: 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.

![Preview deployment](/images/docs/sites/preview-deployment.avif)

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.

![GitHub as source](/images/docs/sites/github-source.avif)

---

## Start with Sites

URL: 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.

![Create site wizard](/images/docs/sites/create-site-wizard.avif)

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.

- [TanStack Start](/docs/products/sites/quick-start/tanstack-start)

- [Next.js](/docs/products/sites/quick-start/nextjs)

- [Nuxt](/docs/products/sites/quick-start/nuxt)

- [SvelteKit](/docs/products/sites/quick-start/sveltekit)

- [Angular](/docs/products/sites/quick-start/angular)

- [Remix](/docs/products/sites/quick-start/remix)

- [React](/docs/products/sites/quick-start/react)

- [Vue.js](/docs/products/sites/quick-start/vue)

- [Astro](/docs/products/sites/quick-start/astro)

- [Flutter](/docs/products/sites/quick-start/flutter)

- [React Native](/docs/products/sites/quick-start/react-native)

- [Vanilla JS](/docs/products/sites/quick-start/vanilla)

### Explore

Use your first site as a springboard to explore the flexible and powerful features of Appwrite Sites.

- [Templates](/docs/products/sites/templates): Get a template site up and running with a single click.

- [Develop](/docs/products/sites/develop): Learn about developing your own Appwrite Site.

- [Deploy](/docs/products/sites/deploy-from-git): Configure and deploy your Appwrite Site from Git.

- [Frameworks](/docs/products/sites/frameworks): Learn which frameworks are supported by Appwrite Sites.

---

## Deploy an Angular app to Appwrite Sites

URL: https://appwrite.io/docs/products/sites/quick-start/angular

#### 1. 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).

#### 2. Create Appwrite project

Head to the [Appwrite Console](https://cloud.appwrite.io).

![Create Appwrite project](/images/docs/quick-starts/create-project.avif)

If this is your first time using Appwrite, create an account and create your first project.

#### 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 **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.

#### 4. Visit site

After successful deployment, click on the **Visit site** button.

---

## Deploy a Astro app to Appwrite Sites

URL: https://appwrite.io/docs/products/sites/quick-start/astro

#### 1. 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).

#### 2. Create Appwrite project

Head to the [Appwrite Console](https://cloud.appwrite.io).

![Create Appwrite project](/images/docs/quick-starts/create-project.avif)

If this is your first time using Appwrite, create an account and create your first project.

#### 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 **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.

#### 4. Visit site

After successful deployment, click on the **Visit site** button.

---

## Deploy a Flutter Web app to Appwrite Sites

URL: https://appwrite.io/docs/products/sites/quick-start/flutter

#### 1. 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).

#### 2. Create Appwrite project

Head to the [Appwrite Console](https://cloud.appwrite.io).

![Create Appwrite project](/images/docs/quick-starts/create-project.avif)

If this is your first time using Appwrite, create an account and create your first project.

#### 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:** `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.

#### 4. Visit site

After successful deployment, click on the **Visit site** button.

---

## Deploy a Next.js app to Appwrite Sites

URL: https://appwrite.io/docs/products/sites/quick-start/nextjs

#### 1. Create Next.js app

**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.

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).

#### 2. Create Appwrite project

Head to the [Appwrite Console](https://cloud.appwrite.io).

![Create Appwrite project](/images/docs/quick-starts/create-project.avif)

If this is your first time using Appwrite, create an account and create your first project.

#### 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 **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.

#### 4. Visit site

After successful deployment, click on the **Visit site** button.

---

## Deploy a Nuxt app to Appwrite Sites

URL: https://appwrite.io/docs/products/sites/quick-start/nuxt

#### 1. 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).

#### 2. Create Appwrite project

Head to the [Appwrite Console](https://cloud.appwrite.io).

![Create Appwrite project](/images/docs/quick-starts/create-project.avif)

If this is your first time using Appwrite, create an account and create your first project.

#### 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 **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.

#### 4. Visit site

After successful deployment, click on the **Visit site** button.

---

## Deploy a React app to Appwrite Sites

URL: https://appwrite.io/docs/products/sites/quick-start/react

#### 1. 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).

#### 2. Create Appwrite project

Head to the [Appwrite Console](https://cloud.appwrite.io).

![Create Appwrite project](/images/docs/quick-starts/create-project.avif)

If this is your first time using Appwrite, create an account and create your first project.

#### 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 **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.

#### 4. Visit site

After successful deployment, click on the **Visit site** button.

---

## Deploy a React Native app to Appwrite Sites

URL: https://appwrite.io/docs/products/sites/quick-start/react-native

#### 1. 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).

#### 2. Create Appwrite project

Head to the [Appwrite Console](https://cloud.appwrite.io).

![Create Appwrite project](/images/docs/quick-starts/create-project.avif)

If this is your first time using Appwrite, create an account and create your first project.

#### 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 **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.

#### 4. Visit site

After successful deployment, click on the **Visit site** button.

---

## Deploy a Remix app to Appwrite Sites

URL: https://appwrite.io/docs/products/sites/quick-start/remix

#### 1. 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).

#### 2. Create Appwrite project

Head to the [Appwrite Console](https://cloud.appwrite.io).

![Create Appwrite project](/images/docs/quick-starts/create-project.avif)

If this is your first time using Appwrite, create an account and create your first project.

#### 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 **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.

#### 4. Visit site

After successful deployment, click on the **Visit site** button.

---

## Deploy a SvelteKit app to Appwrite Sites

URL: https://appwrite.io/docs/products/sites/quick-start/sveltekit

#### 1. 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).

#### 2. Create Appwrite project

Head to the [Appwrite Console](https://cloud.appwrite.io).

![Create Appwrite project](/images/docs/quick-starts/create-project.avif)

If this is your first time using Appwrite, create an account and create your first project.

#### 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 **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.

#### 4. Visit site

After successful deployment, click on the **Visit site** button.

---

## Deploy a TanStack Start app to Appwrite Sites

URL: https://appwrite.io/docs/products/sites/quick-start/tanstack-start

#### 1. 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).

#### 2. Create Appwrite project

Head to the [Appwrite Console](https://cloud.appwrite.io).

![Create Appwrite project](/images/docs/quick-starts/create-project.avif)

If this is your first time using Appwrite, create an account and create your first project.

#### 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 **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.

#### 4. Visit site

After successful deployment, click on the **Visit site** button.

---

## Deploy a Vanilla JS app to Appwrite Sites

URL: https://appwrite.io/docs/products/sites/quick-start/vanilla

#### 1. 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).

#### 2. Create Appwrite project

Head to the [Appwrite Console](https://cloud.appwrite.io).

![Create Appwrite project](/images/docs/quick-starts/create-project.avif)

If this is your first time using Appwrite, create an account and create your first project.

#### 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 **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.

#### 4. Visit site

After successful deployment, click on the **Visit site** button.

---

## Deploy a Vue.js app to Appwrite Sites

URL: https://appwrite.io/docs/products/sites/quick-start/vue

#### 1. 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).

#### 2. Create Appwrite project

Head to the [Appwrite Console](https://cloud.appwrite.io).

![Create Appwrite project](/images/docs/quick-starts/create-project.avif)

If this is your first time using Appwrite, create an account and create your first project.

#### 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 **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.

#### 4. Visit site

After successful deployment, click on the **Visit site** button.

---

## Rendering

URL: 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.

- [Static](/docs/products/sites/rendering/static): Host a static site or SPA

- [Server Side Rendering](/docs/products/sites/rendering/ssr): Host an SSR site

### 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

URL: 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

![Rendering strategy](/images/docs/sites/build-settings-rendering-ssr.avif)

#### 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:

**Analog**

Set `ssr: true` in `analog` plugin in the `vite.config.ts` file.

**Angular**

Ensure the `src/server.ts` file uses `@angular/ssr/node` package.

**TanStack Start**

No additional configuration is needed as SSR is enabled by default.

**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.

**Nuxt**

Set build command to `npm run build` in site settings.

**SvelteKit**

Use `@sveltejs/adapter-node` adapter in the `svelte.config.js` file.

**Remix**

Ensure the `entry.server.tsx` file uses `@remix-run/node` package.

**Astro**

Use `@astrojs/node` adapter in the `astro.config.mjs` file.

### 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.

| Variable | Description | Available at Build and/or Run Time |
| ------------------------------------ | ------------------------------------------------------------- | ---------------------------------- |
| `APPWRITE_SITE_API_ENDPOINT` | The API endpoint of the running site | Both |
| `APPWRITE_VERSION` | The Appwrite version used to run the site | Both |
| `APPWRITE_REGION` | The region where the site will run from | Both |
| `APPWRITE_DEPLOYMENT_TYPE` | The deployment source type, such as `manual`, `cli`, or `vcs` | Both |
| `APPWRITE_SITE_API_KEY` | The site API key used for server authentication | Build time |
| `APPWRITE_SITE_ID` | The ID of the running site | Both |
| `APPWRITE_SITE_NAME` | The name of the running site | Both |
| `APPWRITE_SITE_DEPLOYMENT` | The deployment ID of the running site | Both |
| `APPWRITE_SITE_PROJECT_ID` | The project ID of the running site | Both |
| `APPWRITE_SITE_RUNTIME_NAME` | The runtime name of the running site | Both |
| `APPWRITE_SITE_RUNTIME_VERSION` | The runtime version of the running site | Both |
| `APPWRITE_SITE_CPUS` | The CPU (runtime) specification of the running site | Both |
| `APPWRITE_SITE_MEMORY` | The memory (runtime) specification of the running site | Both |
| `APPWRITE_VCS_REPOSITORY_ID` | The provider repository ID for VCS deployments | Both |
| `APPWRITE_VCS_REPOSITORY_NAME` | The provider repository name for VCS deployments | Both |
| `APPWRITE_VCS_REPOSITORY_OWNER` | The owner of the provider repository | Both |
| `APPWRITE_VCS_REPOSITORY_URL` | The URL of the provider repository | Both |
| `APPWRITE_VCS_REPOSITORY_BRANCH` | The branch used for the VCS deployment | Both |
| `APPWRITE_VCS_REPOSITORY_BRANCH_URL` | The URL of the branch used for the VCS deployment | Both |
| `APPWRITE_VCS_COMMIT_HASH` | The commit hash used for the VCS deployment | Both |
| `APPWRITE_VCS_COMMIT_MESSAGE` | The commit message used for the VCS deployment | Both |
| `APPWRITE_VCS_COMMIT_URL` | The URL of the commit used for the VCS deployment | Both |
| `APPWRITE_VCS_COMMIT_AUTHOR_NAME` | The name of the VCS commit author | Both |
| `APPWRITE_VCS_COMMIT_AUTHOR_URL` | The URL of the VCS commit author | Both |
| `APPWRITE_VCS_ROOT_DIRECTORY` | The root directory configured for the VCS deployment | Both |

VCS metadata variables are populated for Git deployments. For manual and CLI deployments, VCS fields may be empty.

---

## Static

URL: 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

![Rendering strategy](/images/docs/sites/build-settings-rendering-static.avif)

#### Enabling static builds on your web app

To enable static builds for your web app, you may need to make some additional updates in case of the following frameworks:

**Analog**

Set `static: true` in `analog` plugin in the `vite.config.ts` file.

**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.

**Next.js**

Set `output: export` in the `next.config.js`.

**Nuxt**

Set build command to `npm run generate` in site settings.

**SvelteKit**

Use `@sveltejs/adapter-static` adapter in the `svelte.config.js` file.

**Remix**

Set `ssr: false` in `remix` plugin in the `vite.config.ts` file.

**Astro**

Ensure you don't set `adapter` in the `astro.config.mjs` file.

### 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

URL: 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**.

![Site templates](/images/docs/sites/site-templates.avif)

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

URL: 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.

**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).

### Get started
Get started with Appwrite Storage. Learn to setup up a bucket, upload, and download your first file.

[Quick start](/docs/products/storage/quick-start)

---

## Buckets

URL: 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
You can create your bucket from the Appwrite Console, a [Server SDK](/docs/sdks#server), or the [CLI](/docs/tooling/command-line/buckets).

**Console**

You can create a bucket by heading to the **Storage** page and clicking **Create bucket**.

![Create bucket on console](/images/docs/storage/create-bucket.avif)

**Server SDK**

You can also create buckets programmatically using a [Server SDK](/docs/sdks#server). Appwrite [Server SDKs](/docs/sdks#server) require an [API key](/docs/advanced/security/api-keys).

```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='<BUCKET_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

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>"
)

```
```server-rust
use appwrite::Client;
use appwrite::services::storage::Storage;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let 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

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

    let bucket = storage.create_bucket(
        "<BUCKET_ID>",
        "<NAME>",
        None, // permissions (optional)
        None, // file_security (optional)
        None, // enabled (optional)
        None, // maximum_file_size (optional)
        None, // allowed_file_extensions (optional)
        None, // compression (optional)
        None, // encryption (optional)
        None, // antivirus (optional)
        None, // transformations (optional)
    ).await?;

    println!("{:?}", bucket);
    Ok(())
}
```

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).

**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.

[Learn more about the CLI buckets commands](/docs/tooling/command-line/buckets#commands)

### 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).

### Folders
Files in a bucket can be organized into virtual folders.
Each file carries a folder path, like `photos/2026`, that you set when uploading it.
Folders are derived from these paths, so there is nothing to create or manage separately: a folder appears when the first file is uploaded into it and disappears when the last file inside it is deleted.

[Learn about working with folders](/docs/products/storage/folders).

### 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
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
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
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

URL: 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).

**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.

![Create file token](/images/docs/storage/create-file-token.avif)

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.

![Copy file token-based URL](/images/docs/storage/copy-file-token-url.avif)

**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/partners/project/api-keys) with the `tokens.write` scope enabled.

```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-rust
use appwrite::Client;
use appwrite::services::tokens::Tokens;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let 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

    let tokens = Tokens::new(&client);

    let result = tokens.create_file_token(
        "<BUCKET_ID>",
        "<FILE_ID>",
        Some(""), // expire (optional)
    ).await?;

    println!("{:?}", result);
    Ok(())
}
```

```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": 
}
```

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.

### 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.

**Console**

Head to the **Storage** page, open a file inside a bucket, and scroll down to the **File tokens** section.

![List of file tokens](/images/docs/storage/list-file-tokens.avif)

**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/partners/project/api-keys) with the `tokens.read` scope enabled.

```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-rust
use appwrite::Client;
use appwrite::services::tokens::Tokens;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let 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

    let tokens = Tokens::new(&client);

    let result = tokens.list(
        "<BUCKET_ID>",
        "<FILE_ID>",
        None, // queries (optional)
        None, // total (optional)
    ).await?;

    println!("{:?}", result);
    Ok(())
}
```

```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>
```

Using the token IDs, you can also get data pertaining to an [individual file token](/docs/references/cloud/server-nodejs/tokens#get).

```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-rust
let token = tokens.get(
    "<TOKEN_ID>",
).await?;
```

```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>
```

### 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.

**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**.

![Update file token expiry](/images/docs/storage/update-file-token-expiry.avif)

**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/partners/project/api-keys) with the `tokens.write` scope enabled.

```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-rust
use appwrite::Client;
use appwrite::services::tokens::Tokens;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let 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

    let tokens = Tokens::new(&client);

    let result = tokens.update(
        "<TOKEN_ID>",
        Some(""), // expire (optional)
    ).await?;

    println!("{:?}", result);
    Ok(())
}
```

```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": 
}
```

### Delete file tokens

You can use the Appwrite Console or one of the Server SDKs to delete a file token.

**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**.

![Delete file token](/images/docs/storage/delete-file-token.avif)

**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/partners/project/api-keys) with the `tokens.write` scope enabled.

```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-rust
use appwrite::Client;
use appwrite::services::tokens::Tokens;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let 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

    let tokens = Tokens::new(&client);

    tokens.delete(
        "<TOKEN_ID>",
    ).await?;

    println!("Token deleted successfully");
    Ok(())
}
```

```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>
```

---

## Folders

URL: https://appwrite.io/docs/products/storage/folders

Appwrite Storage lets you organize the files inside a bucket using virtual folders.
Folders work like key prefixes in S3-compatible storage services: they are derived from the paths of your files, so you never create or delete folders explicitly.

### How folders work

A bucket doesn't store folders as records. Instead, every file has a `folder` attribute, a path like `photos/2026/`, and folders are derived from these paths: a folder exists whenever at least one file's `folder` path places the file inside it.
For example, uploading a single file with the folder `photos/2026` is what brings both the `photos/` and `photos/2026/` folders into existence.

A file's folder is set once, when the file is uploaded, and defaults to the bucket root (an empty string).
Folder paths are stored in a canonical form that always ends with a trailing slash, like `photos/2026/`.
Each file also exposes a computed `key` attribute, which is the file's full virtual path: the folder followed by the file name, like `photos/2026/Pink.png`.

Because folders are derived from files, they exist implicitly. A folder appears as soon as the first file is uploaded into it and disappears when the last file inside it is deleted.
There are no empty folders, no folder permissions, and no folder metadata to manage.

Unlike in S3, uploading a file with the same name to the same folder does not overwrite the existing file.
Files are identified by their file ID, so multiple files can share the same `key`.

### Upload files to a folder

To place a file inside a folder, pass the optional `folder` parameter when [uploading the file](/docs/products/storage/upload-download#create-file).
Nest folders using `/`, for example `photos/2026`. The trailing slash is optional on input and is added automatically when stored.

  ```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 file = await storage.createFile({
      bucketId: '<BUCKET_ID>',
      fileId: ID.unique(),
      file: document.getElementById('uploader').files[0],
      folder: 'photos/2026'
  });

  console.log(file.folder); // 'photos/2026/'
  console.log(file.key);    // 'photos/2026/Pink.png'
  ```

  ```server-nodejs
  const sdk = require('node-appwrite');
  const { InputFile } = require('node-appwrite/file');

  const client = new sdk.Client()
      .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
      .setProject('<PROJECT_ID>')
      .setKey('<API_KEY>');

  const storage = new sdk.Storage(client);

  const file = await storage.createFile({
      bucketId: '<BUCKET_ID>',
      fileId: sdk.ID.unique(),
      file: InputFile.fromPath('/path/to/Pink.png', 'Pink.png'),
      folder: 'photos/2026'
  });

  console.log(file.folder); // 'photos/2026/'
  console.log(file.key);    // 'photos/2026/Pink.png'
  ```

  ```client-flutter
  import 'package:appwrite/appwrite.dart';

  void main() async {
    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/Pink.png', filename: 'Pink.png'),
      folder: 'photos/2026',
    );
  }
  ```

  ```client-android-kotlin
  import io.appwrite.Client
  import io.appwrite.ID
  import io.appwrite.models.InputFile
  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 = InputFile.fromPath("./path-to-files/Pink.png"),
          folder = "photos/2026",
      )
  }
  ```

  ```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: "Pink.png",
              mimeType: "image/png"
          ),
          folder: "photos/2026"
      )
  }
  ```

  ```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 file = await storage.createFile({
      bucketId: '<BUCKET_ID>',
      fileId: ID.unique(),
      file: {
          name: 'Pink.png',
          type: 'image/png',
          size: 1234567,
          uri: 'file:///path/to/Pink.png',
      },
      folder: 'photos/2026'
  });
  ```

**Folder naming rules**

Folder paths are `/`-separated segments. A folder path must not start with `/`, must not contain empty, `.`, or `..` segments or control characters, and can be at most 2,048 characters long including the trailing slash.

A file's folder can't be changed after upload. To move a file into a different folder, create the file again with the new folder and delete the original.

### List files in a folder

Filter files by folder using [queries](/docs/products/databases/queries) on the `folder` attribute when listing files.

| Goal | Query |
| ---- | ----- |
| Files directly inside `photos/2026/` | `Query.equal('folder', ['photos/2026/'])` |
| Files at the bucket root only | `Query.equal('folder', [''])` |
| Files anywhere under `photos/`, including nested folders | `Query.startsWith('folder', 'photos/')` |

Query values must match the stored form of the folder path, which always includes the trailing slash.
For example, `Query.equal('folder', ['photos/2026/'])` matches files in `photos/2026/`, but `Query.equal('folder', ['photos/2026'])` matches nothing.

  ```client-web
  import { Client, Storage, Query } from "appwrite";

  const client = new Client()
      .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
      .setProject('<PROJECT_ID>');

  const storage = new Storage(client);

  const result = await storage.listFiles({
      bucketId: '<BUCKET_ID>',
      queries: [
          Query.equal('folder', ['photos/2026/'])
      ]
  });

  console.log(result.files);
  ```

  ```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 storage = new sdk.Storage(client);

  const result = await storage.listFiles({
      bucketId: '<BUCKET_ID>',
      queries: [
          sdk.Query.equal('folder', ['photos/2026/'])
      ]
  });

  console.log(result.files);
  ```

  ```client-flutter
  import 'package:appwrite/appwrite.dart';

  void main() async {
    final client = Client()
        .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
        .setProject('<PROJECT_ID>');

    final storage = Storage(client);

    final result = await storage.listFiles(
      bucketId: '<BUCKET_ID>',
      queries: [
        Query.equal('folder', ['photos/2026/'])
      ],
    );
  }
  ```

  ```client-android-kotlin
  import io.appwrite.Client
  import io.appwrite.Query
  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 result = storage.listFiles(
          bucketId = "<BUCKET_ID>",
          queries = listOf(
              Query.equal("folder", listOf("photos/2026/"))
          )
      )
  }
  ```

  ```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 result = try await storage.listFiles(
          bucketId: "<BUCKET_ID>",
          queries: [
              Query.equal("folder", value: ["photos/2026/"])
          ]
      )
  }
  ```

  ```client-react-native
  import { Client, Storage, Query } 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 = await storage.listFiles({
      bucketId: '<BUCKET_ID>',
      queries: [
          Query.equal('folder', ['photos/2026/'])
      ]
  });
  ```

### List folders

Folders are aggregated from the files inside a bucket.
To browse them, [paginate](/docs/products/databases/pagination) through the bucket's files and collect the unique folder paths from each file's `folder` attribute, including the implied parent folders.

  ```client-web
  import { Client, Storage, Query } from "appwrite";

  const client = new Client()
      .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
      .setProject('<PROJECT_ID>');

  const storage = new Storage(client);

  const limit = 100;
  let cursor = '';
  const folders = new Set();

  while (true) {
      const page = await storage.listFiles({
          bucketId: '<BUCKET_ID>',
          queries: [
              Query.limit(limit),
              ...(cursor ? [Query.cursorAfter(cursor)] : [])
          ]
      });

      for (const file of page.files) {
          let path = '';

          // Add the folder and its implied parent folders
          for (const part of file.folder.split('/').filter(Boolean)) {
              path += `${part}/`;
              folders.add(path);
          }
      }

      if (page.files.length < limit) {
          break; // All files scanned
      }

      cursor = page.files[page.files.length - 1].$id;
  }

  console.log([...folders].sort());
  ```

  ```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 storage = new sdk.Storage(client);

  const limit = 100;
  let cursor = '';
  const folders = new Set();

  while (true) {
      const page = await storage.listFiles({
          bucketId: '<BUCKET_ID>',
          queries: [
              sdk.Query.limit(limit),
              ...(cursor ? [sdk.Query.cursorAfter(cursor)] : [])
          ]
      });

      for (const file of page.files) {
          let path = '';

          // Add the folder and its implied parent folders
          for (const part of file.folder.split('/').filter(Boolean)) {
              path += `${part}/`;
              folders.add(path);
          }
      }

      if (page.files.length < limit) {
          break; // All files scanned
      }

      cursor = page.files[page.files.length - 1].$id;
  }

  console.log([...folders].sort());
  ```

  ```client-flutter
  import 'package:appwrite/appwrite.dart';

  void main() async {
    final client = Client()
        .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
        .setProject('<PROJECT_ID>');

    final storage = Storage(client);

    const limit = 100;
    var cursor = '';
    final folders = <String>{};

    while (true) {
      final page = await storage.listFiles(
        bucketId: '<BUCKET_ID>',
        queries: [
          Query.limit(limit),
          if (cursor.isNotEmpty) Query.cursorAfter(cursor),
        ],
      );

      for (final file in page.files) {
        var path = '';

        // Add the folder and its implied parent folders
        for (final part in file.folder.split('/').where((p) => p.isNotEmpty)) {
          path += '$part/';
          folders.add(path);
        }
      }

      if (page.files.length < limit) {
        break; // All files scanned
      }

      cursor = page.files.last.$id;
    }

    print(folders.toList()..sort());
  }
  ```

  ```client-android-kotlin
  import io.appwrite.Client
  import io.appwrite.Query
  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 limit = 100
      var cursor = ""
      val folders = mutableSetOf<String>()

      while (true) {
          val page = storage.listFiles(
              bucketId = "<BUCKET_ID>",
              queries = listOfNotNull(
                  Query.limit(limit),
                  if (cursor.isNotEmpty()) Query.cursorAfter(cursor) else null
              )
          )

          for (file in page.files) {
              var path = ""

              // Add the folder and its implied parent folders
              for (part in file.folder.split("/").filter { it.isNotEmpty() }) {
                  path += "$part/"
                  folders.add(path)
              }
          }

          if (page.files.size < limit) {
              break // All files scanned
          }

          cursor = page.files.last().id
      }
  }
  ```

  ```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 limit = 100
      var cursor = ""
      var folders = Set<String>()

      while true {
          var queries = [Query.limit(limit)]
          if !cursor.isEmpty {
              queries.append(Query.cursorAfter(cursor))
          }

          let page = try await storage.listFiles(
              bucketId: "<BUCKET_ID>",
              queries: queries
          )

          for file in page.files {
              var path = ""

              // Add the folder and its implied parent folders
              for part in file.folder.split(separator: "/") {
                  path += "\(part)/"
                  folders.insert(path)
              }
          }

          if page.files.count < limit {
              break // All files scanned
          }

          cursor = page.files.last!.id
      }
  }
  ```

  ```client-react-native
  import { Client, Storage, Query } from 'react-native-appwrite';

  const client = new Client()
      .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
      .setProject('<PROJECT_ID>');

  const storage = new Storage(client);

  const limit = 100;
  let cursor = '';
  const folders = new Set();

  while (true) {
      const page = await storage.listFiles({
          bucketId: '<BUCKET_ID>',
          queries: [
              Query.limit(limit),
              ...(cursor ? [Query.cursorAfter(cursor)] : [])
          ]
      });

      for (const file of page.files) {
          let path = '';

          // Add the folder and its implied parent folders
          for (const part of file.folder.split('/').filter(Boolean)) {
              path += `${part}/`;
              folders.add(path);
          }
      }

      if (page.files.length < limit) {
          break; // All files scanned
      }

      cursor = page.files[page.files.length - 1].$id;
  }

  console.log([...folders].sort());
  ```

For example, a file with the folder `photos/2026/july/` produces three folders:

```text
photos/
photos/2026/
photos/2026/july/
```

Scanning every file works well for small buckets.
For buckets with many files, maintain your own folder index instead, for example in a [Databases](/docs/products/databases) table that you update whenever you upload or delete files.

### Permissions

Folders don't carry their own permissions. Access is derived from the files inside them.
When a bucket uses [file security](/docs/products/storage/permissions), listing files returns only the files a user can read, so aggregating folders discovers only the folders that contain at least one such file.

[Learn more about storage permissions](/docs/products/storage/permissions)

---

## Image transformations

URL: 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.

**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.

#### 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`, or `auto`. When set to `auto`, Appwrite detects the main subject of the image and keeps it in view while cropping. |
| 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
Here are some examples using [Client SDKs](/docs/sdks#client).

```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"
    />
);
```

---

## Storage permissions

URL: 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 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/security/permissions)

### 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/security/permissions)

---

## Start with Storage

URL: https://appwrite.io/docs/products/storage/quick-start

You can create your first bucket, upload, and download your first file in minutes.

### 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

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.

  ```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
  });
  ```

  ```server-rust
  use appwrite::Client;
  use appwrite::services::storage::Storage;
  use appwrite::input_file::InputFile;
  use appwrite::id::ID;

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

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

      let file = InputFile::from_path("/path/to/file.jpg", None).await?;
      let result = storage.create_file(
          "<BUCKET_ID>",
          ID::unique(),
          file,
          None, // permissions (optional)
      ).await?;

      println!("{:?}", result);
      Ok(())
  }
  ```

  ```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--
  ```

### Download file
To download a file, use the `getFileDownload` method.

```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
```

---

## S3 API

URL: https://appwrite.io/docs/products/storage/s3

Appwrite Storage exposes an S3-compatible API, so you can point the AWS CLI, the AWS SDKs, and third-party tools like rclone or s3cmd at your buckets and files. The API uses AWS Signature Version 4 and maps standard S3 operations onto Appwrite Storage, so most existing S3 code works after you change three settings: the endpoint, the credentials, and the region. Requests can be signed with an `Authorization` header or handed to a client without Appwrite credentials as a [presigned URL](#presigned-urls), including from a [browser](#browser-access).

The S3 API needs a project ID and an [API key](/docs/advanced/security/api-keys) with the [storage scopes](#api-key-scopes) described below. It also works alongside your existing data: buckets and files are addressable over both the native Storage API and the S3 API at the same time, with no migration required.

### Connect an S3 client

Configure your client with the following values. S3 clients authenticate with an access key ID and a secret access key, which Appwrite maps to your project ID and an API key secret.

| Setting | Value |
| --- | --- |
| Endpoint | `https://<REGION>.cloud.appwrite.io/v1/s3` |
| Access key ID | Your Appwrite project ID |
| Secret access key | An Appwrite API key secret |
| Region | `auto` or your project's Appwrite region code |
| Signature version | AWS Signature Version 4 (SigV4) |
| Addressing style | Path-style only |

The `<REGION>` in the endpoint is your Appwrite Cloud region (for example, `fra` or `nyc`), the same region you use for the rest of the Appwrite API. The S3 `region` setting accepts `auto` or that same region code; some clients require a region to sign requests, and either value works.

The examples below apply these settings in the [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/cli-services-s3.html) and the AWS SDKs.

```bash
### AWS CLI
aws configure set aws_access_key_id <PROJECT_ID>
aws configure set aws_secret_access_key <API_KEY_SECRET>
aws configure set region auto

### Appwrite serves path-style URLs only, so force path addressing
aws configure set default.s3.addressing_style path

### Pass the Appwrite endpoint on every command
aws s3 ls --endpoint-url https://<REGION>.cloud.appwrite.io/v1/s3
```

```js
// Node.js: @aws-sdk/client-s3 (v3)
import { S3Client } from '@aws-sdk/client-s3';

const client = new S3Client({
    endpoint: 'https://<REGION>.cloud.appwrite.io/v1/s3',
    region: 'auto',
    forcePathStyle: true,
    credentials: {
        accessKeyId: '<PROJECT_ID>',
        secretAccessKey: '<API_KEY_SECRET>'
    }
});
```

```python
### Python: boto3
import boto3
from botocore.config import Config

s3 = boto3.client(
    's3',
    endpoint_url='https://<REGION>.cloud.appwrite.io/v1/s3',
    region_name='auto',
    aws_access_key_id='<PROJECT_ID>',
    aws_secret_access_key='<API_KEY_SECRET>',
    config=Config(signature_version='s3v4', s3={'addressing_style': 'path'})
)
```

```go
// Go: aws-sdk-go-v2
package main

import (
    "context"
    "log"

    "github.com/aws/aws-sdk-go-v2/aws"
    "github.com/aws/aws-sdk-go-v2/config"
    "github.com/aws/aws-sdk-go-v2/credentials"
    "github.com/aws/aws-sdk-go-v2/service/s3"
)

func main() {
    cfg, err := config.LoadDefaultConfig(context.TODO(),
        config.WithRegion("auto"),
        config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(
            "<PROJECT_ID>", "<API_KEY_SECRET>", "",
        )),
    )
    if err != nil {
        log.Fatal(err)
    }

    client := s3.NewFromConfig(cfg, func(o *s3.Options) {
        o.BaseEndpoint = aws.String("https://<REGION>.cloud.appwrite.io/v1/s3")
        o.UsePathStyle = true
    })

    buckets, err := client.ListBuckets(context.TODO(), &s3.ListBucketsInput{})
    if err != nil {
        log.Fatal(err)
    }

    for _, bucket := range buckets.Buckets {
        log.Println(*bucket.Name)
    }
}
```

```php
// PHP: aws/aws-sdk-php
use Aws\S3\S3Client;

$client = new S3Client([
    'version' => 'latest',
    'region' => 'auto',
    'endpoint' => 'https://<REGION>.cloud.appwrite.io/v1/s3',
    'use_path_style_endpoint' => true,
    'credentials' => [
        'key' => '<PROJECT_ID>',
        'secret' => '<API_KEY_SECRET>',
    ],
]);
```

### API key scopes

Create an API key in the Appwrite Console under **Overview** > **Integrations** > **API keys**, then grant the storage scopes your workload needs. The project ID identifies your project, while the API key secret both authenticates the request and determines what it is allowed to do.

| Access level | Required scopes |
| --- | --- |
| Read-only (list and download) | `buckets.read`, `files.read` |
| Write (create buckets, upload, delete) | `buckets.write`, `files.write` |
| Copy (`CopyObject`, `UploadPartCopy`) | `files.read`, `files.write` |
| Full read and write | `buckets.read`, `files.read`, `buckets.write`, `files.write` |

Copying an object reads the source and writes the destination, so `CopyObject` and `UploadPartCopy` need both `files.read` and `files.write`. A request signed with a secret that does not match a project API key, or a key missing the required scope, is rejected with `AccessDenied` (HTTP 403).

### Buckets and objects

S3 buckets map directly to Appwrite Storage buckets, and S3 objects map to files. Creating a bucket over S3 creates an Appwrite bucket that uses the S3 bucket name as both its ID and its name, with Appwrite's default settings. Any bucket you already have in your project is usable over S3 immediately.

Object keys are file names, used directly. Uploading to the key `reports/january.pdf` stores a file named `reports/january.pdf`, and read, copy, and delete operations address that object by the same key. Nothing has to be looked up first, and files created through the native Storage API or the Console are addressable over S3 under the names they already have.

Because the key is the name, file names have to be unique within a bucket. Uploading to a key that already exists replaces that object in place: its bytes, size, content type, and user metadata are overwritten, while the underlying Appwrite file keeps its ID and permissions, so anything referencing it through the native Storage API stays valid. Concurrent uploads to the same key are serialized, and the last write wins.

**File names must be unique per bucket**

S3 itself has no concept of two objects sharing a key: [an object key is "the unique identifier for an object within a bucket"](https://docs.aws.amazon.com/AmazonS3/latest/userguide/Welcome.html#BasicsKeys), and every object in a bucket has exactly one. Appwrite Storage does let several files in a bucket share a name and folder. When that happens, the S3 API does not return an error: a request whose key resolves to more than one file is served from the first matching file, and a listing shows that key once. The other files with the same name are not reachable over S3, and an overwrite or delete of that key only affects the first match.

So if your workload depends on repeating file names within a bucket, the S3 API is not the right interface for it. Use the [native Storage API](/docs/products/storage/upload-download) instead, which addresses files by ID and lets names repeat freely.

### Folders

Appwrite Storage organizes the files in a bucket with [virtual folders](/docs/products/storage/folders), derived from the paths of your files and never created or deleted on their own. Over the S3 API, folders appear as `/`-separated prefixes in object keys: uploading to `reports/2024/january.pdf` stores a single object whose name contains those prefixes, and does not create `reports/` or `reports/2024/` as separate entities.

To browse a bucket like a directory tree, list objects with the `/` delimiter and, optionally, a `prefix`. Appwrite groups object names at each `/` boundary: names sharing a prefix up to the next `/` collapse into a single entry under `CommonPrefixes` (the "folders"), while objects at the current level are returned under `Contents` (the "files").

Pass a `prefix` such as `reports/` to open that folder and list one level deeper, or list without a delimiter to enumerate a bucket recursively.

Objects under `Contents` are returned with their full keys; use those to read, copy, or delete a file. `CommonPrefixes` are folder paths only, with nothing to download at the prefix itself.

`/` is the only supported delimiter. Setting `delimiter` to any other value returns `NotImplemented` (HTTP 501).

```bash
### List the top level of a bucket: immediate folders (CommonPrefixes) and files
aws s3api list-objects-v2 --bucket my-bucket --delimiter / \
  --endpoint-url https://<REGION>.cloud.appwrite.io/v1/s3

### Open the "reports" folder and list one level down
aws s3api list-objects-v2 --bucket my-bucket --prefix reports/ --delimiter / \
  --endpoint-url https://<REGION>.cloud.appwrite.io/v1/s3

### aws s3 ls uses the / delimiter automatically; omit --recursive to browse by folder
aws s3 ls s3://my-bucket/reports/ \
  --endpoint-url https://<REGION>.cloud.appwrite.io/v1/s3
```

### Example commands

Once your client is configured, everyday S3 commands work as usual. These AWS CLI examples assume you have run `aws configure` as shown above; each command passes the endpoint explicitly with `--endpoint-url`.

```bash
### Create a bucket
aws s3api create-bucket --bucket my-bucket \
  --endpoint-url https://<REGION>.cloud.appwrite.io/v1/s3

### Upload a file (uses multipart automatically for large files)
aws s3 cp ./january.pdf s3://my-bucket/reports/january.pdf \
  --endpoint-url https://<REGION>.cloud.appwrite.io/v1/s3

### List objects
aws s3 ls s3://my-bucket --recursive \
  --endpoint-url https://<REGION>.cloud.appwrite.io/v1/s3

### Download an object by its key
aws s3 cp s3://my-bucket/reports/january.pdf ./january.pdf \
  --endpoint-url https://<REGION>.cloud.appwrite.io/v1/s3
```

### Bucket operations

All operations are addressed with path-style URLs under `/v1/s3`. The following bucket operations are supported.

| Operation | Description |
| --- | --- |
| `ListBuckets` | List the project's buckets. |
| `CreateBucket` | Create a bucket. The S3 bucket name becomes the Appwrite bucket's ID and name, with Appwrite's default settings. |
| `HeadBucket` | Check that a bucket exists and is accessible. |
| `DeleteBucket` | Delete an empty bucket. Deleting a bucket that still contains objects returns `BucketNotEmpty` (HTTP 409). |
| `GetBucketLocation` | Returns the added region. |
| `GetBucketAcl`, `PutBucketAcl` | Compatibility responses only. See [Limitations](#limitations). |

### Object operations

The following object operations are supported.

| Operation | Description |
| --- | --- |
| `PutObject` | Upload an object, replacing anything already stored under that key. Returns an `ETag`. Honors `Content-Type`, `x-amz-meta-*` user metadata, and `x-amz-server-side-encryption`. |
| `GetObject` | Download an object. Supports `Range` requests (HTTP 206), and the `If-None-Match` (HTTP 304) and `If-Match` (HTTP 412) conditional headers. |
| `HeadObject` | Retrieve an object's metadata (size, content type, `ETag`, user metadata) without the body. |
| `CopyObject` | Copy an object using the `x-amz-copy-source` header. Supports `x-amz-metadata-directive: REPLACE` to replace metadata. |
| `DeleteObject` | Delete a single object. |
| `DeleteObjects` | Delete multiple objects in one request, including quiet mode. |
| `ListObjects` | List objects in a bucket, with `prefix` filtering. |
| `ListObjectsV2` | List objects with `prefix`, `delimiter` (only `/`), `max-keys`, `continuation-token`, and `start-after`. Use `delimiter=/` to browse [folders](#folders). |
| `GetObjectAcl`, `PutObjectAcl` | Compatibility responses only. See [Limitations](#limitations). |

Uploads sent with the AWS SDKs' default integrity protections, which frame the body as `Content-Encoding: aws-chunked` with a streaming payload signature, are decoded transparently, and a CRC32 trailer checksum is verified against the stored bytes when the client sends one.

Content type is taken from the `Content-Type` header you send. When that is missing or generic (`application/octet-stream`), Appwrite infers a type from the file extension. Buckets keep their constraints under the S3 API: uploads that exceed the bucket's maximum file size or use a disallowed extension are rejected, and disabled buckets are treated as not found.

### Multipart uploads

Large files are uploaded in parts. Standard SDK multipart helpers, such as the AWS CLI's `aws s3 cp` and the SDK transfer managers, use these operations automatically.

| Operation | Description |
| --- | --- |
| `CreateMultipartUpload` | Begin a multipart upload and receive an `UploadId`. |
| `UploadPart` | Upload a single part. Returns the part's `ETag`. |
| `UploadPartCopy` | Upload a part by copying a byte range from another object, using `x-amz-copy-source` and `x-amz-copy-source-range`. |
| `CompleteMultipartUpload` | Assemble the uploaded parts into the final object. |
| `AbortMultipartUpload` | Discard an in-progress multipart upload and its parts. |
| `ListMultipartUploads` | List in-progress multipart uploads in a bucket. |
| `ListParts` | List the parts already uploaded for an `UploadId`. |

**Multipart requirements**

Parts must be contiguous and start at part number 1. Completing an upload with non-contiguous part numbers (for example, parts 1, 5, and 100) is rejected. SDK multipart helpers number parts contiguously, so they are unaffected, and parts can be uploaded in parallel: the AWS CLI and the SDK transfer managers do this by default. In-progress uploads are kept for 24 hours before they are cleaned up, so complete or abort within that window.

### Presigned URLs

A presigned URL carries its AWS Signature Version 4 authentication in the query string instead of an `Authorization` header, so it can be handed to a client that has no Appwrite credentials of its own: a browser, a mobile app, or anyone you send the link to. Appwrite accepts presigned URLs for the supported S3 operations, generated by the standard helpers in the AWS SDKs, the AWS CLI, rclone, and s5cmd.

```bash
### AWS CLI (GET only)
aws s3 presign s3://my-bucket/reports/january.pdf --expires-in 900 \
  --endpoint-url https://<REGION>.cloud.appwrite.io/v1/s3

### rclone
rclone link --expire 15m appwrite:my-bucket/reports/january.pdf

### s5cmd
s5cmd --endpoint-url https://<REGION>.cloud.appwrite.io/v1/s3 \
  presign --expire 15m s3://my-bucket/reports/january.pdf
```

```js
// Node.js: @aws-sdk/client-s3 with @aws-sdk/s3-request-presigner
import { GetObjectCommand, PutObjectCommand } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';

const downloadUrl = await getSignedUrl(
    client,
    new GetObjectCommand({ Bucket: 'my-bucket', Key: 'reports/january.pdf' }),
    { expiresIn: 900 }
);

const uploadUrl = await getSignedUrl(
    client,
    new PutObjectCommand({
        Bucket: 'my-bucket',
        Key: 'reports/january.pdf',
        ContentType: 'application/pdf'
    }),
    { expiresIn: 900 }
);
```

```python
### Python: boto3
download_url = s3.generate_presigned_url(
    'get_object',
    Params={'Bucket': 'my-bucket', 'Key': 'reports/january.pdf'},
    ExpiresIn=900,
)

upload_url = s3.generate_presigned_url(
    'put_object',
    Params={
        'Bucket': 'my-bucket',
        'Key': 'reports/january.pdf',
        'ContentType': 'application/pdf',
    },
    ExpiresIn=900,
)
```

```php
// PHP: aws/aws-sdk-php
$command = $client->getCommand('GetObject', [
    'Bucket' => 'my-bucket',
    'Key' => 'reports/january.pdf',
]);

$url = (string) $client->createPresignedRequest($command, '+15 minutes')->getUri();
```

The signature covers the HTTP method, the object key, and every other query parameter in the URL, so a presigned URL authorizes exactly one request. A URL signed for `GetObject` cannot be replayed as a `PutObject`, and changing the key or any signed parameter invalidates the signature.

The request body is not signed. A presigned upload URL is generated before the bytes exist, so Appwrite verifies it as `UNSIGNED-PAYLOAD` and the client supplies the body afterwards. Anything else the client has to send, such as `Content-Type`, must be signed into the URL when you generate it and then sent unchanged with the request.

#### Expiry

Set the lifetime when you generate the URL. Appwrite accepts 1 second to 604800 seconds (7 days), the same ceiling AWS applies to SigV4 presigned URLs, and rejects anything outside that range. The URL is valid from the timestamp it was signed with until that timestamp plus the expiry; a request that arrives after the window closes returns `AccessDenied` (HTTP 403).

Presigned URLs are stateless, so an individual URL cannot be revoked once issued. To cut one short, delete or rotate the API key that signed it, which invalidates every URL signed with that secret at once. Keep expiry windows as short as your workload allows.

#### What a presigned URL grants

A presigned URL is signed with an API key secret and inherits that key's [scopes](#api-key-scopes) for the single operation it encodes. The secret is never part of the URL, and the URL cannot be used to recover it or to perform any other operation. Anyone holding the URL can still make that one request until it expires, so treat it as a credential: send it over HTTPS, keep it out of logs, and share it only with the client that needs it.

**Sign presigned URLs on the server**

Generating a presigned URL requires the API key secret, so do it on a server or in an [Appwrite Function](/docs/products/functions), never in a browser or mobile app where that secret would be exposed. Hand the finished URL to the client instead.

#### Required parameters

SDK and CLI helpers add these for you. If you sign URLs yourself, all six are required, and the request must not also carry an `Authorization` header.

| Parameter | Value |
| --- | --- |
| `X-Amz-Algorithm` | `AWS4-HMAC-SHA256`. Any other algorithm is rejected. |
| `X-Amz-Credential` | `<PROJECT_ID>/<YYYYMMDD>/<REGION>/s3/aws4_request`. The date must match `X-Amz-Date`. |
| `X-Amz-Date` | The request timestamp, in `YYYYMMDDTHHMMSSZ` format. |
| `X-Amz-Expires` | The lifetime in seconds, from `1` to `604800`. |
| `X-Amz-SignedHeaders` | Lowercase and semicolon-separated, sorted, and including `host`. |
| `X-Amz-Signature` | The 64-character hexadecimal SigV4 signature. |

A URL that omits one of these parameters, repeats one, or combines query authentication with an `Authorization` header is rejected with `AccessDenied` (HTTP 403).

### Browser access

The S3 endpoint answers cross-origin requests, so a presigned URL can be used directly from a web page. Downloading an object, uploading to a presigned `PutObject` URL, and streaming a `Range` request all work from browser JavaScript with no proxy in front.

Requests are allowed from any origin, and preflight `OPTIONS` requests are answered by the endpoint without reaching your buckets. Because every request authenticates from its own signature, cross-origin responses are credential-free: cookies are neither sent nor accepted (`Access-Control-Allow-Credentials` is `false`), and you do not need to register the origin as a platform in your project. Preflight responses are cacheable for 24 hours.

These response headers are exposed to browser JavaScript, so client code can read them off a `fetch` response:

| Exposed header | Use |
| --- | --- |
| `Content-Length` | The size of the object body. |
| `Accept-Ranges`, `Content-Range` | Range request support, and the byte range returned. |
| `ETag` | The object's entity tag, for conditional requests and integrity checks. |
| `Last-Modified` | When the object was last written. |
| `x-amz-server-side-encryption` | The server-side encryption applied to the object. |

Browser requests may send the standard content and caching headers, including `Content-Type`, `Range`, and `Cache-Control`. Custom `x-amz-*` request headers are not permitted cross-origin, so set user metadata such as `x-amz-meta-*` from a server-side client rather than from the browser.

**Never ship an API key secret to a browser**

Cross-origin support does not make it safe to configure an S3 client with your API key secret in front-end code. A secret in a browser is readable by anyone who opens the page, and it grants every scope on the key. Use [presigned URLs](#presigned-urls) signed on a server instead.

### Events and usage

Writes over the S3 API are ordinary Storage writes, so they take part in the rest of your project. Uploading, replacing, or deleting an object fires the matching `buckets.[bucketId].files.[fileId].create`, `.update`, or `.delete` [event](/docs/advanced/platform/events), and creating or deleting a bucket fires the corresponding bucket event. Those events reach [webhooks](/docs/advanced/platform/webhooks), [realtime](/docs/apis/realtime) subscribers, and event-triggered [functions](/docs/products/functions/execute) exactly as native Storage writes do.

S3 traffic is metered like the rest of your project's API traffic: requests and inbound and outbound bandwidth accrue to your project's network usage.

### Limitations

The S3 API targets the operations most clients depend on. Keep the following in mind.

- **Path-style addressing only.** Virtual-hosted-style URLs (`https://<bucket>.host/...`) are not supported. Enable path-style addressing in your client, as shown in [Connect an S3 client](#connect-an-s3-client).
- **Signature Version 4 only.** Requests are authenticated with SigV4, either with an `Authorization` header or as a [presigned URL](#presigned-urls) that carries the signature in the query string, but never both at once. A header-signed request must carry a timestamp within 15 minutes of the server's clock, while a presigned URL is valid for up to 7 days from the timestamp it was signed with.
- **File names must be unique within a bucket.** Object keys are file names, so a bucket cannot hold two objects under one key, and uploading to an existing key replaces the object stored there. If a bucket already contains files sharing a name and folder, a request for that key is served from the first matching file and a listing shows the key once. The other files are not reachable over S3. See [Buckets and objects](#buckets-and-objects).
- **ACLs are compatibility responses.** `GetObjectAcl`, `PutObjectAcl`, `GetBucketAcl`, and `PutBucketAcl` return a canned private ACL and are accepted for tooling compatibility. They do not change access. Manage access with [Appwrite permissions](/docs/products/storage/permissions) and API key scopes instead.
- **No folder resource or folder markers.** Folders are virtual: they are `/`-separated prefixes in object keys, browsed by listing with `delimiter=/`. See [Folders](#folders). Using any other delimiter value or uploading a folder-marker object (a key ending in `/`) returns `NotImplemented` (HTTP 501).
- **Prefer `ListObjectsV2` for large buckets.** `ListObjects` returns every matching object in a single response, while `ListObjectsV2` honors `max-keys` (capped at 1000) and paginates with `IsTruncated` and a `NextContinuationToken`. No objects are silently dropped.
- **No bucket CORS configuration.** The `cors` bucket sub-resource is not implemented. It is not needed to call the endpoint from a browser: cross-origin requests are allowed from any origin by default. See [Browser access](#browser-access).
- **Unsupported S3 features.** Object tagging, lifecycle, bucket policies and policy status, encryption configuration, ownership controls, notifications, versioning, object locking, and restores are not supported. Any request carrying a sub-resource or query parameter the API does not implement returns `NotImplemented` (HTTP 501) rather than being partially applied.

### Error responses

Errors are returned as standard S3 XML error documents with an S3 error code and HTTP status.

| S3 error code | HTTP status | Meaning |
| --- | --- | --- |
| `NoSuchBucket` | 404 | The bucket does not exist, is disabled, or is not accessible. |
| `NoSuchKey` | 404 | The object key does not exist. |
| `BucketNotEmpty` | 409 | The bucket still contains objects and cannot be deleted. |
| `BucketAlreadyOwnedByYou` | 409 | A bucket with that ID already exists in your project. |
| `AccessDenied` | 403 | The signature is invalid, the API key is expired or missing a required scope, or a presigned URL has expired or is malformed. |
| `InvalidRange` | 416 | The requested `Range` cannot be satisfied. |
| `PreconditionFailed` | 412 | An `If-Match` precondition did not hold. |
| `NotImplemented` | 501 | The requested S3 feature is not supported. |
| `InvalidRequest` | 400 | The request was malformed or violated a bucket constraint. |
| `InternalError` | 500 | An unexpected server error occurred. |

---

## Upload and download

URL: 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

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:

  ```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"
          )
      )
  }
  ```

  ```server-rust
  use appwrite::Client;
  use appwrite::services::storage::Storage;
  use appwrite::input_file::InputFile;
  use appwrite::id::ID;

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

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

      let file = InputFile::from_path("/path/to/file.jpg", None).await?;
      let result = storage.create_file(
          "<BUCKET_ID>",
          ID::unique(),
          file,
          None, // permissions (optional)
      ).await?;

      println!("{:?}", result);
      Ok(())
  }
  ```

  ```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--
  ```

When uploading, you can pass the optional `folder` parameter to organize files into virtual folders, for example `photos/2026`.

[Learn more about folders](/docs/products/storage/folders)

### 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
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

**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]
});
```

**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. |

**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. |

**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. |

**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,
    };
  }
};
```

### Server SDKs

**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. |

**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. |

**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. |

**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. |

**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. |

**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. |

**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. |

**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. |

**.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. |

### Get file
To get a metadata about a file, use the `getFile` method.

```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
});
```

### Download file
To download a file, use the `getFileDownload` method.

```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
```

### Get File Preview
To get a file preview image , use the `getFilePreview` method.

```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
```

### View File

To view a file, use the `getFileView` method.

```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
```

---

## Start with Android (Kotlin)

URL: 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).

**Using Java?**

Check out the [Start with Android (Java)](/docs/quick-starts/android-java) guide.

#### 1. 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.

#### 2. Create Appwrite project

Head to the [Appwrite Console](https://cloud.appwrite.io/console).

![Create project screen](/images/docs/quick-starts/create-project.avif)

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.

![Add a platform](/images/docs/quick-starts/add-platform.avif)

You can skip optional steps.

#### 3. 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>
```

#### 4. Create Appwrite Singleton

Find your project's ID in the **Settings** page.

![Project settings screen](/images/docs/quick-starts/project-id.avif)

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")
    }
}
```

#### 5. 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")
                            }
                        }
                    }
                }
            }
        }
    }
}
```

#### 6. 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}")
}
```

**Generate types automatically**

Use the [Appwrite CLI](/docs/products/databases/type-generation) to generate model classes automatically: `appwrite types ./models`

#### 7. All set

Run your project by clicking **Run app** in Android Studio.

---

## Start with Android (Java)

URL: 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.

**Using Kotlin?**

Check out the [Start with Android (Kotlin)](/docs/quick-starts/android) guide.

#### 1. 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.

#### 2. Create Appwrite project

Head to the [Appwrite Console](https://cloud.appwrite.io/console).

![Create project screen](/images/docs/quick-starts/create-project.avif)

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.

![Add a platform](/images/docs/quick-starts/add-platform.avif)

You can skip optional steps.

#### 3. 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>
```

#### 4. Create Appwrite helper class

Find your project's ID in the **Settings** page.

![Project settings screen](/images/docs/quick-starts/project-id.avif)

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, error) -> {
                if (error != null) {
                    callback.onError(error);
                    return;
                }
                callback.onSuccess(result);
            })
        );
    }

    public void register(String email, String password, final AuthCallback<User<Map<String, Object>>> callback) {
        account.create(
            ID.unique(),
            email,
            password,
            new CoroutineCallback<>((result, error) -> {
                if (error != null) {
                    callback.onError(error);
                    return;
                }
                callback.onSuccess(result);
            })
        );
    }

    public void logout(final AuthCallback<Object> callback) {
        account.deleteSession(
            "current",
            new CoroutineCallback<>((result, error) -> {
                if (error != null) {
                    callback.onError(error);
                    return;
                }
                callback.onSuccess(result);
            })
        );
    }
}
```

#### 5. 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>
```

#### 6. 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);
    }
}
```

#### 7. All set

Run your project by clicking **Run app** in Android Studio.

---

## Start with Angular

URL: https://appwrite.io/docs/quick-starts/angular

Learn how to setup your first Angular project powered by Appwrite.

#### 1. Create project

Head to the [Appwrite Console](https://cloud.appwrite.io/console).

![Create project screen](/images/docs/quick-starts/create-project.avif)

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`.

**Cross-Origin Resource Sharing (CORS)**

Adding `localhost` as a platform lets your local app talk to Appwrite. For production, add your live domain to avoid CORS errors.

Learn more in our [CORS error guide](/blog/post/cors-error).

![Add a platform](/images/docs/quick-starts/add-platform.avif)

You can skip optional steps.

#### 2. 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
```

#### 3. Install Appwrite

Install the JavaScript Appwrite SDK.

```sh
npm install appwrite
```

#### 4. Import Appwrite

Find your project's ID in the **Settings** page.

![Project settings screen](/images/docs/quick-starts/project-id.avif)
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';
```

#### 5. 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;
  }
}
```

#### 6. All set

Run your project with `ng serve --port 3000` and open [Localhost on Port 3000](http://localhost:3000) in your browser.

---

## Start with Apple

URL: 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).

#### 1. 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.

#### 2. Create Appwrite project

Head to the [Appwrite Console](https://cloud.appwrite.io/console).

![Create project screen](/images/docs/quick-starts/create-project.avif)

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.

![Add a platform](/images/docs/quick-starts/add-platform.avif)

You can skip optional steps.

#### 3. 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)
}
```

#### 4. Create Appwrite Singleton

Find your project's ID in the **Settings** page.

![Project settings screen](/images/docs/quick-starts/project-id.avif)
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"
        )
    }
    
}

```

#### 5. 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()
    }
}
```

#### 6. 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)")
}
```

**Generate types automatically**

Use the [Appwrite CLI](/docs/products/databases/type-generation) to generate model structs automatically: `appwrite types ./models`

#### 7. All set

Run your project by clicking **Start active scheme** in Xcode.

---

## Start with Astro

URL: 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

URL: https://appwrite.io/docs/quick-starts/dart

Learn how to setup your first Dart project powered by Appwrite.

#### 1. Create project

Head to the [Appwrite Console](https://cloud.appwrite.io/console).

![Create project screen](/images/docs/quick-starts/create-project.avif)

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 | 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.

![Create project screen](/images/docs/quick-starts/integrate-server.avif)

#### 2. 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.

#### 3. Install Appwrite

Install the Dart Appwrite SDK.

```sh
dart pub add dart_appwrite:16.0.0
```

#### 4. Import Appwrite

Find your project ID in the **Settings** page.

![Project settings screen](/images/docs/quick-starts/project-id.avif)

 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>");
}
```

#### 5. Initialize database

Once the Appwrite Client is initialized, create a function to configure a todo table.

```dart
var tablesDB;
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.createVarcharColumn(
    databaseId: todoDatabase.$id,
    tableId: todoTable.$id,
    key: 'title',
    size: 255,
    xrequired: true
  );

  await tablesDB.createTextColumn(
    databaseId: todoDatabase.$id,
    tableId: todoTable.$id,
    key: 'description',
    xrequired: false,
    xdefault: 'This is a test description'
  );

  await tablesDB.createBooleanColumn(
    databaseId: todoDatabase.$id,
    tableId: todoTable.$id,
    key: 'isComplete',
    xrequired: true
  );
}
```

#### 6. 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
  );
}
```

#### 7. 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();
}
```

#### 8. All set

Run your project with `dart run bin/my_app.dart` and view the response in your console.

---

## Start with Deno

URL: https://appwrite.io/docs/quick-starts/deno

**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.

Learn how to setup your first Deno project powered by Appwrite.

#### 1. 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.

![Create project screen](/images/docs/quick-starts/create-project.avif)

Then, under **Integrate with your server**, add an **API Key** with the following scopes.

![Create project screen](/images/docs/quick-starts/integrate-server.avif)

| Category | 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.

#### 2. Create Deno project

Create a Deno CLI application.

```sh
mkdir my-app
cd my-app
echo "console.log('Hello, Deno!');" > mod.ts
```

#### 3. 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";
```

#### 4. 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.

![Project settings screen](/images/docs/quick-starts/project-id.avif)

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>");
```

#### 5. 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.createVarcharColumn({
        databaseId: todoDatabase.$id,
        tableId: todoTable.$id,
        key: 'title',
        size: 255,
        required: true
    });

    await tablesDB.createTextColumn({
        databaseId: todoDatabase.$id,
        tableId: todoTable.$id,
        key: 'description',
        required: false,
        xdefault: 'This is a test description'
    });

    await tablesDB.createBooleanColumn({
        databaseId: todoDatabase.$id,
        tableId: todoTable.$id,
        key: 'isComplete',
        required: true
    });
}
```

#### 6. 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
    });
}
```

#### 7. 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();
```

#### 8. All set

Run your project with `deno mod.ts` and view the response in your console.

---

## Start with .NET

URL: https://appwrite.io/docs/quick-starts/dotnet

Learn how to setup your first .NET project powered by Appwrite.

#### 1. 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.

![Create project screen](/images/docs/quick-starts/create-project.avif)

Then, under **Integrate with your server**, add an **API Key** with the following scopes.

![Server integrations](/images/docs/quick-starts/integrate-server.avif)
| Category | 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.

![Project settings screen](/images/docs/quick-starts/project-id.avif)

#### 2. Create .NET project

Create a .NET CLI application.

```sh
dotnet new console -o MyApp
cd MyApp
```

#### 3. Install Appwrite

Install the .NET Appwrite SDK.

```sh
dotnet add package Appwrite --version 0.13.0
```

#### 4. 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.

![Project settings screen](/images/docs/quick-starts/project-id.avif)

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>");
```

#### 5. 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.CreateVarcharColumn(
    databaseId: todoDatabase.Id,
    tableId: todoTable.Id,
    key: "title",
    size: 255,
    required: true
);

await tablesDB.CreateTextColumn(
    databaseId: todoDatabase.Id,
    tableId: todoTable.Id,
    key: "description",
    required: false,
    xdefault: "This is a test description"
);

await tablesDB.CreateBooleanColumn(
    databaseId: todoDatabase.Id,
    tableId: todoTable.Id,
    key: "isComplete",
    required: true
);
```

#### 6. 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
);
```

#### 7. 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.Rows)
{
    Console.WriteLine($"Title: {todo.Data["title"]}\nDescription: {todo.Data["description"]}\nIs Todo Complete: {todo.Data["isComplete"]}\n\n");
}
```

#### 8. All set

Run your project with `dotnet run` and view the response in your console.

---

## Start with Flutter

URL: https://appwrite.io/docs/quick-starts/flutter

Learn how to setup your first Flutter project powered by Appwrite.

#### 1. Create Flutter project

Create a Flutter project.

```sh
flutter create my_app && cd my_app
```

#### 2. Create project

Head to the [Appwrite Console](https://cloud.appwrite.io/console).

![Create project screen](/images/docs/quick-starts/create-project.avif)

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.

**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</title>
<p>Authentication is complete. If this does not happen automatically, please close the window.</p>
<script>
  window.opener.postMessage({
    'flutter-web-auth-2': window.location.href
  }, window.location.origin);
  window.close();
</script>
```

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.

**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/products/network/custom-domains) for each project.

**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

**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 `<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="com.linusu.flutter_web_auth_2.CallbackActivity" android:exported="true">
      <intent-filter android:label="flutter_web_auth_2">
        <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>
```

**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.

**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
<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>
```

**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.

![Add a platform](/images/docs/quick-starts/add-platform.avif)

You can skip optional steps.

#### 3. Install Appwrite

Install the Appwrite SDK for Flutter.

```sh
flutter pub add appwrite:17.0.0
```

#### 4. Import Appwrite

Find your project's ID in the **Settings** page.

![Project settings screen](/images/docs/quick-starts/project-id.avif)
Open the generated `lib/main.dart` and add the following code to it, replace `<PROJECT_ID>` 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://<REGION>.cloud.appwrite.io/v1")
      .setProject("<PROJECT_ID>");
  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();
  }
}
```

#### 5. Create a login page

Then, append the following widgets to `lib/main.dart` create your login page.

```dart
class MyAppState extends State<MyApp> {
  models.User? loggedInUser;
  final TextEditingController emailController = TextEditingController();
  final TextEditingController passwordController = TextEditingController();
  final TextEditingController nameController = TextEditingController();

  Future<void> login(String email, String password) async {
    await widget.account.createEmailPasswordSession(
      email: email,
      password: password,
    );
    final user = await widget.account.get();
    setState(() {
      loggedInUser = user;
    });
  }

  Future<void> 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<void> 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: <Widget>[
            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: <Widget>[
                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'),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}
```

#### 6. All set

Run your project with `flutter run` and select a browser, platform, or emulator to run your project.

---

## Start with Go

URL: https://appwrite.io/docs/quick-starts/go

Learn how to set up your first Go project powered by Appwrite.

#### 1. 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.

![Create project screen](/images/docs/quick-starts/create-project.avif)

Then, under **Integrate with your server**, add an **API Key** with the following scopes.

![Create project screen](/images/docs/quick-starts/integrate-server.avif)

| Category | 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.

#### 2. Create Go project

Create a go application.

```sh
mkdir my-app
cd my-app
go mod init go-appwrite/main
```

#### 3. Install Appwrite

Install the Go Appwrite SDK.

```sh
go get github.com/appwrite/sdk-for-go
```

#### 4. 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.

![Project settings screen](/images/docs/quick-starts/project-id.avif)

Create a new file called `app.go`, initialize a function, and initialize the Appwrite Client. Replace `<PROJECT_ID>` with your project ID and `<YOUR_API_KEY>` 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
	tablesDB *tablesdb.TablesDB
)

func main() {
	appwriteClient = appwrite.NewClient(
		appwrite.WithProject("<PROJECT_KEY>"),
		appwrite.WithKey("<API_KEY>"),
	)
}
```

#### 5. 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.CreateVarcharColumn(
		todoDatabase.Id,
		todoTable.Id,
		"title",
		255,
		true,
	)

	tablesDB.CreateTextColumn(
		todoDatabase.Id,
		todoTable.Id,
		"description",
		false,
	)

	tablesDB.CreateBooleanColumn(
		todoDatabase.Id,
		todoTable.Id,
		"isComplete",
		true,
	)
}
```

#### 6. 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,
	)
}
```

#### 7. 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.RowList
	Rows []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.Rows {
		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.Rows {
		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.Rows {
		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("<PROJECT_KEY>"),
		appwrite.WithKey("<API_KEY>"),
	)

	prepareDatabase()
	seedDatabase()
	getTodos()
	getCompletedTodos()
	getIncompleteTodos()
}
```

#### 8. All set

Run your project with `go run .` and view the response in your console.

---

## Start with Kotlin

URL: https://appwrite.io/docs/quick-starts/kotlin

Learn how to setup your first Kotlin project powered by Appwrite.

#### 1. Create project

Head to the [Appwrite Console](https://cloud.appwrite.io/console).

**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).

If this is your first time using Appwrite, create an account and create your first project.

![Create project screen](/images/docs/quick-starts/create-project.avif)

Then, under **Integrate with your server**, add an **API Key** with the following scopes.

![Server integrations](/images/docs/quick-starts/integrate-server.avif)
| Category | 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.

![Project settings screen](/images/docs/quick-starts/project-id.avif)

#### 2. 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.

#### 3. 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")
}
```

#### 4. 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.

![Project settings screen](/images/docs/quick-starts/project-id.avif)

Open the file `Main.kt` and initialize the Appwrite Client. Replace `<PROJECT_ID>` with your project ID and `<YOUR_API_KEY>` 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://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>");
```

#### 5. 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.createVarcharColumn(
        databaseId = todoDatabase?.id!!,
        tableId = todoTable?.id!!,
        key = "title",
        size = 255,
        required = true
    )

    tablesDB.createTextColumn(
        databaseId = todoDatabase?.id!!,
        tableId = todoTable?.id!!,
        key = "description",
        required = false,
        default = "This is a test description."
    )

    tablesDB.createBooleanColumn(
        databaseId = todoDatabase?.id!!,
        tableId = todoTable?.id!!,
        key = "isComplete",
        required = true
    )
}
```

#### 6. 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
    )
}
```

#### 7. 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()
}
```

#### 8. All set

Run your project with IntelliJ and view the response in your console.

---

## Start with Next.js

URL: https://appwrite.io/docs/quick-starts/nextjs

Learn how to set up your first Next.js project with the [Appwrite React library](/docs/products/auth/react). The library ships SSR auth handlers, server helpers, and the same React hooks you use on the client.

#### 1. Create project

Head to the [Appwrite Console](https://cloud.appwrite.io/console).

![Create project screen](/images/docs/quick-starts/create-project.avif)

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`.

**Cross-Origin Resource Sharing (CORS)**

Adding `localhost` as a platform lets your local app talk to Appwrite. For production, add your live domain to avoid CORS errors.

Learn more in our [CORS error guide](/blog/post/cors-error).

![Add a platform](/images/docs/quick-starts/add-platform.avif)

You can skip optional steps.

#### 2. Create an API key

In your project, go to **Overview** > **Integrations** > **API keys** and create a new key with the scopes `users.read`, `users.write`, and `sessions.write`. Copy the key secret. The SSR handler uses this to create sessions on behalf of users; never expose it to the browser.

#### 3. Create Next.js project

Create a Next.js project with TypeScript and the App Router.

```sh
npx create-next-app@latest my-app --ts --app && cd my-app
```

Accept the defaults for the remaining prompts.

#### 4. Install the React library

Install the React library along with the Appwrite Web SDK, Appwrite Node SDK, and `@tanstack/react-query` packages.

```sh
npm install @appwrite.io/react appwrite node-appwrite @tanstack/react-query
```

#### 5. Configure environment variables

Create a `.env.local` file at the project root. Replace `<REGION>`, `<PROJECT_ID>`, and `<API_KEY>` with your own values.

```sh
NEXT_PUBLIC_APPWRITE_ENDPOINT=https://<REGION>.cloud.appwrite.io/v1
NEXT_PUBLIC_APPWRITE_PROJECT_ID=<PROJECT_ID>
APPWRITE_API_KEY=<API_KEY>
```

`NEXT_PUBLIC_*` values are shipped to the browser. `APPWRITE_API_KEY` stays server-only.

#### 6. Mount the auth handler route

Create `app/api/appwrite/[...appwrite]/route.ts`. The handler exposes the `sign-in`, `sign-up`, `sign-out`, and `oauth/callback` endpoints that the React hooks POST to in SSR mode.

```ts
import { createAppwriteHandlers } from "@appwrite.io/react/handlers/next";

export const { GET, POST } = createAppwriteHandlers({
  endpoint: process.env.NEXT_PUBLIC_APPWRITE_ENDPOINT!,
  projectId: process.env.NEXT_PUBLIC_APPWRITE_PROJECT_ID!,
  apiKey: process.env.APPWRITE_API_KEY!,
  basePath: "/api/appwrite",
});
```

#### 7. Wrap your app with AppwriteProvider

Create `app/providers.tsx`. The provider runs in client components and accepts the SSR session secret as a prop so the underlying Web SDK can hydrate authenticated.

```tsx
"use client";

import { AppwriteProvider } from "@appwrite.io/react";

export function Providers({
  session,
  children,
}: {
  session?: string | null;
  children: React.ReactNode;
}) {
  return (
    <AppwriteProvider
      endpoint={process.env.NEXT_PUBLIC_APPWRITE_ENDPOINT!}
      projectId={process.env.NEXT_PUBLIC_APPWRITE_PROJECT_ID!}
      ssr={{ session, basePath: "/api/appwrite" }}
    >
      {children}
    </AppwriteProvider>
  );
}
```

Replace `app/layout.tsx` with the following. It reads the session cookie on the server and passes it into the provider.

```tsx
import { createNextServerHelpers } from "@appwrite.io/react/server/next";
import { Providers } from "./providers";

const appwrite = {
  endpoint: process.env.NEXT_PUBLIC_APPWRITE_ENDPOINT!,
  projectId: process.env.NEXT_PUBLIC_APPWRITE_PROJECT_ID!,
};

export default async function RootLayout({
  children,
}: Readonly<{ children: React.ReactNode }>) {
  const helpers = createNextServerHelpers(appwrite);
  const session = await helpers.readSessionCookie();

  return (
    <html lang="en">
      <body>
        <Providers session={session}>{children}</Providers>
      </body>
    </html>
  );
}
```

#### 8. Read the user on the server

Replace `app/page.tsx`. `getLoggedInUser()` calls the Appwrite API server-side with the session cookie, so the user is rendered with the first byte.

```tsx
import { createNextServerHelpers } from "@appwrite.io/react/server/next";
import { AuthPanel } from "./auth-panel";

const appwrite = {
  endpoint: process.env.NEXT_PUBLIC_APPWRITE_ENDPOINT!,
  projectId: process.env.NEXT_PUBLIC_APPWRITE_PROJECT_ID!,
};

export default async function Page() {
  const helpers = createNextServerHelpers(appwrite);
  const user = await helpers.getLoggedInUser();

  return (
    <main>
      <h1>Appwrite React library on Next.js</h1>
      <p>SSR user: {user?.email ?? "signed out"}</p>
      <AuthPanel />
    </main>
  );
}
```

#### 9. Add the client auth panel

Create `app/auth-panel.tsx`. The hooks POST to the handler route, the server sets a cookie, and `router.refresh()` re-runs the server component so the SSR user updates.

```tsx
"use client";

import { useState } from "react";
import { useAuth } from "@appwrite.io/react";
import { useRouter } from "next/navigation";

export function AuthPanel() {
  const { user, isLoading, signIn, signUp, signOut, error } = useAuth();
  const router = useRouter();
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [name, setName] = useState("");

  if (isLoading) return <p>Loading...</p>;

  if (user) {
    return (
      <div>
        <p>Welcome, {user.name || user.email}</p>
        <button onClick={() => signOut.signOut({ onSuccess: () => router.refresh() })}>
          Sign out
        </button>
      </div>
    );
  }

  return (
    <div>
      <input placeholder="Name" value={name} onChange={(e) => setName(e.target.value)} />
      <input placeholder="Email" value={email} onChange={(e) => setEmail(e.target.value)} />
      <input
        placeholder="Password"
        type="password"
        value={password}
        onChange={(e) => setPassword(e.target.value)}
      />
      <button
        onClick={() =>
          signUp.emailPassword({
            email,
            password,
            name,
            onSuccess: () => router.refresh(),
          })
        }
        disabled={signUp.isPending}
      >
        Sign up
      </button>
      <button
        onClick={() =>
          signIn.emailPassword({
            email,
            password,
            onSuccess: () => router.refresh(),
          })
        }
        disabled={signIn.isPending}
      >
        Sign in
      </button>
      {error && <p style={{ color: "red" }}>{error.message}</p>}
    </div>
  );
}
```

#### 10. Run your app

```sh
npm run dev
```

Open [localhost on port 3000](http://localhost:3000). Sign up, sign out, and sign back in to verify the cookie-based SSR flow.

### Next steps

For server-side admin operations, per-request session clients, OAuth callbacks, and the full hook reference, see the [React library docs](/docs/products/auth/react).

---

## Start with Node.js

URL: https://appwrite.io/docs/quick-starts/node

Learn how to setup your first Node.js project powered by Appwrite.

#### 1. 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.

![Create project screen](/images/docs/quick-starts/create-project.avif)

Then, under **Integrate with your server**, add an **API Key** with the following scopes.

![Create project screen](/images/docs/quick-starts/integrate-server.avif)

| Category | 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.

#### 2. Create Node.js project

Create a Node.js CLI application.

```sh
mkdir my-app
cd my-app
npm init
```

#### 3. Install Appwrite

Install the Node.js Appwrite SDK.

```sh
npm install node-appwrite
```

#### 4. 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.

![Project settings screen](/images/docs/quick-starts/project-id.avif)

Create a new file `app.js` and initialize the Appwrite Client. Replace `<PROJECT_ID>` with your project ID and `<YOUR_API_KEY>` with your API key.

```js
const sdk = require("node-appwrite");

const client = new sdk.Client();

client
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>");
```

#### 5. 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.createVarcharColumn({
        databaseId: todoDatabase.$id,
        tableId: todoTable.$id,
        key: 'title',
        size: 255,
        required: true
    });

    await tablesDB.createTextColumn({
        databaseId: todoDatabase.$id,
        tableId: todoTable.$id,
        key: 'description',
        required: false,
        xdefault: 'This is a test description'
    });

    await tablesDB.createBooleanColumn({
        databaseId: todoDatabase.$id,
        tableId: todoTable.$id,
        key: 'isComplete',
        required: true
    });
}
```

#### 6. 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
    });
}
```

#### 7. 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();
```

#### 8. 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://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

const tablesDB = new TablesDB(client);

// Type-safe database operations
async function getTodos() {
    const todos = await tablesDB.listRows<Todo>({
        databaseId: '<DATABASE_ID>',
        tableId: '<TABLE_ID>'
    });

    todos.rows.forEach(todo => {
        console.log(`Title: ${todo.title} - Complete: ${todo.isComplete}`);
    });
}
```

**Generate types automatically**

Use the [Appwrite CLI](/docs/products/databases/type-generation) to generate TypeScript interfaces automatically: `appwrite types ./types`

#### 9. All set

Run your project with `node app.js` and view the response in your console.

---

## Start with Nuxt

URL: https://appwrite.io/docs/quick-starts/nuxt

Learn how to setup your first Nuxt project powered by Appwrite.

#### 1. Create project

Head to the [Appwrite Console](https://cloud.appwrite.io/console).

![Create project screen](/images/docs/quick-starts/create-project.avif)

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`.

**Cross-Origin Resource Sharing (CORS)**

Adding `localhost` as a platform lets your local app talk to Appwrite. For production, add your live domain to avoid CORS errors.

Learn more in our [CORS error guide](/blog/post/cors-error).

![Add a platform](/images/docs/quick-starts/add-platform.avif)

You can skip optional steps.

#### 2. Create Nuxt project

Create a Nuxt project.

```sh
npx nuxi@latest init my-app && cd my app

```

#### 3. Install Appwrite

Install the JavaScript Appwrite SDK.

```sh
npm install appwrite
```

#### 4. Import Appwrite

Find your project's ID in the **Settings** page.

![Project settings screen](/images/docs/quick-starts/project-id.avif)

Create a new file `utils/appwrite.js` 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';
```

#### 5. Create a login page

Add the following code to `app.vue`.

```html
<script setup>
import { ref } from 'vue';
import { account, ID } from './utils/appwrite.js';

const loggedInUser = ref(null);
const email = ref('');
const password = ref('');
const name = ref('');

const login = async (email, password) => {
  await account.createEmailPasswordSession({
    email,
    password
  });
  loggedInUser.value = await account.get();
};

const register = async () => {
  await account.create({
    userId: ID.unique(),
    email: email.value,
    password: password.value,
    name: name.value
  });
  login(email.value, password.value);
};

const logout = async () => {
  await account.deleteSession({ sessionId: 'current' });
  loggedInUser.value = null;
};
</script>

<template>
  <div>
    <p>
      {{ loggedInUser ? `Logged in as ${loggedInUser.name}` : 'Not logged in' }}
    </p>

    <form>
      <input type="email" placeholder="Email" v-model="email" />
      <input type="password" placeholder="Password" v-model="password" />
      <input type="text" placeholder="Name" v-model="name" />
      <button type="button" @click="login(email, password)">Login</button>
      <button type="button" @click="register">
        Register
      </button>
      <button type="button" @click="logout">
        Logout
      </button>
    </form>
  </div>
</template>
```

#### 6. All set

Run your project with `npm run dev -- --open --port 3000` and open [Localhost on Port 3000](http://localhost:3000) in your browser.

---

## Start with PHP

URL: https://appwrite.io/docs/quick-starts/php

Learn how to setup your first PHP project powered by Appwrite.

#### 1. 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.

![Create project screen](/images/docs/quick-starts/create-project.avif)

Then, under **Integrate with your server**, add an **API Key** with the following scopes.

![Create project screen](/images/docs/quick-starts/integrate-server.avif)

| Category | 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.

#### 2. Create PHP project

Create a PHP CLI application.

```sh
mkdir my-app
cd my-app
composer init
```

#### 3. Install Appwrite

Install the PHP Appwrite SDK.

```sh
composer require appwrite/appwrite:15.0.0
```

#### 4. 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.

![Project settings screen](/images/docs/quick-starts/project-id.avif)

Create a new file `index.php` and initialize the Appwrite Client. Replace `<PROJECT_ID>` with your project ID and `<YOUR_API_KEY>` with your API key.

```php
<?php
require_once 'vendor/autoload.php';

use Appwrite\Client;
use Appwrite\Services\TablesDB;
use Appwrite\ID;

$client = new Client();

$client
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');
```

#### 5. 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->createVarcharColumn(
        databaseId: $todoDatabase['$id'],
        tableId: $todoTable['$id'],
        key: 'title',
        size: 255,
        required: true
    );

    $tablesDB->createTextColumn(
        databaseId: $todoDatabase['$id'],
        tableId: $todoTable['$id'],
        key: 'description',
        required: false,
    );

    $tablesDB->createBooleanColumn(
        databaseId: $todoDatabase['$id'], 
        tableId: $todoTable['$id'], 
        key: 'isComplete', 
        required: true
    );

    return [$todoDatabase, $todoTable];
}
```

#### 6. 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
    );
}
```

#### 7. 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);
```

#### 8. All set

Run your project with `php src/index.php` and view the response in your console.

---

## Start with Python

URL: https://appwrite.io/docs/quick-starts/python

Learn how to setup your first Python project powered by Appwrite.

#### 1. 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.

![Create project screen](/images/docs/quick-starts/create-project.avif)

Then, under **Integrate with your server**, add an **API Key** with the following scopes.

![Server integrations](/images/docs/quick-starts/integrate-server.avif)

| Category | 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.

![Project settings screen](/images/docs/quick-starts/project-id.avif)

#### 2. 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`.

#### 3. Install Appwrite

Install the Python Appwrite SDK.

```sh
pip install appwrite
```

Or with `uv`:

```sh
uv add appwrite
```

#### 4. 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.

![Project settings screen](/images/docs/quick-starts/project-id.avif)

Open `my_app.py` and initialize the Appwrite Client. Replace `<PROJECT_ID>` with your project ID and `<YOUR_API_KEY>` 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://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')
```

#### 5. Initialize database

Once the Appwrite Client is initialized, define a Pydantic model for type-safe data access and create a function to configure a todo table.

```py
from pydantic import BaseModel
from typing import Optional

### Define a Pydantic model matching the table schema
class Todo(BaseModel):
    title: str
    description: Optional[str] = None
    isComplete: bool

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_varchar_column(
    database_id=todoDatabase.id,
    table_id=todoTable.id,
    key='title',
    size=255,
    required=True
  )

  tablesDB.create_text_column(
    database_id=todoDatabase.id,
    table_id=todoTable.id,
    key='description',
    required=False,
    default='This is a test description.'
  )

  tablesDB.create_boolean_column(
    database_id=todoDatabase.id,
    table_id=todoTable.id,
    key='isComplete',
    required=True
  )
```

#### 6. Add rows

Create a function to add some mock data into your new table.

```py
def seed_database():
  testTodo1 = Todo(
    title="Buy apples",
    description="At least 2KGs",
    isComplete=True
  )

  testTodo2 = Todo(
    title="Wash the apples",
    isComplete=True
  )

  testTodo3 = Todo(
    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.model_dump()
  )

  tablesDB.create_row(
    database_id=todoDatabase.id,
    table_id=todoTable.id,
    row_id=ID.unique(),
    data=testTodo2.model_dump()
  )

  tablesDB.create_row(
    database_id=todoDatabase.id,
    table_id=todoTable.id,
    row_id=ID.unique(),
    data=testTodo3.model_dump()
  )
```

#### 7. 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 with type-safe access (default limit is 25)
  todos = tablesDB.list_rows(
    database_id=todoDatabase.id,
    table_id=todoTable.id,
    model_type=Todo
  )
  print("Todos:")
  for todo in todos.rows:
    print(f"Title: {todo.data.title}\nDescription: {todo.data.description}\nIs Todo Complete: {todo.data.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,
    model_type=Todo,
    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.data.title}\nDescription: {todo.data.description}\nIs Todo Complete: {todo.data.isComplete}\n\n")

def get_incomplete_todos():
  # Query for incomplete todos
  todos = tablesDB.list_rows(
    database_id=todoDatabase.id,
    table_id=todoTable.id,
    model_type=Todo,
    queries=[
      Query.equal("isComplete", False),
      Query.order_asc("title")
    ]
  )
  print("Incomplete todos (ordered by title):")
  for todo in todos.rows:
    print(f"Title: {todo.data.title}\nDescription: {todo.data.description}\nIs Todo Complete: {todo.data.isComplete}\n\n")

if __name__ == "__main__":
  prepare_database()
  seed_database()
  get_todos()
  get_completed_todos()
  get_incomplete_todos()
```

#### 8. All set

Run your project with `python my_app.py` and view the response in your console.

---

## Start with Qwik

URL: 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

URL: https://appwrite.io/docs/quick-starts/react

Learn how to set up your first React project with the [Appwrite React library](/docs/products/auth/react).

#### 1. Create project

Head to the [Appwrite Console](https://cloud.appwrite.io/console).

![Create project screen](/images/docs/quick-starts/create-project.avif)

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`.

**Cross-Origin Resource Sharing (CORS)**

Adding `localhost` as a platform lets your local app talk to Appwrite. For production, add your live domain to avoid CORS errors.

Learn more in our [CORS error guide](/blog/post/cors-error).

![Add a platform](/images/docs/quick-starts/add-platform.avif)

You can skip optional steps.

#### 2. Create React project

Create a Vite project.

```sh
npm create vite@latest my-app -- --template react-ts && cd my-app
npm install
```

#### 3. Install the React library

Install the Appwrite React library along with the `appwrite` Web SDK and `@tanstack/react-query` peer dependency.

```sh
npm install @appwrite.io/react appwrite @tanstack/react-query
```

#### 4. Configure environment variables

Find your project's ID in the **Settings** page.

![Project settings screen](/images/docs/quick-starts/project-id.avif)

Create a `.env` file at the project root and add your endpoint and project ID. Replace `<REGION>` and `<PROJECT_ID>` with your own values.

```sh
VITE_APPWRITE_ENDPOINT=https://<REGION>.cloud.appwrite.io/v1
VITE_APPWRITE_PROJECT_ID=<PROJECT_ID>
```

#### 5. Wrap your app with AppwriteProvider

Replace the contents of `src/main.tsx` with the following.

```tsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { AppwriteProvider } from "@appwrite.io/react";
import App from "./App";
import "./index.css";

createRoot(document.getElementById("root")!).render(
  <StrictMode>
    <AppwriteProvider
      endpoint={import.meta.env.VITE_APPWRITE_ENDPOINT}
      projectId={import.meta.env.VITE_APPWRITE_PROJECT_ID}
    >
      <App />
    </AppwriteProvider>
  </StrictMode>,
);
```

`AppwriteProvider` sets up the Appwrite Web SDK client and a per-instance TanStack Query cache so all hooks share the same auth state.

#### 6. Add sign-up, sign-in, and sign-out

Replace the contents of `src/App.tsx` with the following.

```tsx
import { useState } from "react";
import { useAuth } from "@appwrite.io/react";

export default function App() {
  const { user, isLoading, signIn, signUp, signOut, error } = useAuth();
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [name, setName] = useState("");

  if (isLoading) return <p>Loading...</p>;

  if (user) {
    return (
      <main>
        <h1>Welcome, {user.name || user.email}</h1>
        <button onClick={() => signOut.signOut()}>Sign out</button>
      </main>
    );
  }

  return (
    <main>
      <input placeholder="Name" value={name} onChange={(e) => setName(e.target.value)} />
      <input placeholder="Email" value={email} onChange={(e) => setEmail(e.target.value)} />
      <input
        placeholder="Password"
        type="password"
        value={password}
        onChange={(e) => setPassword(e.target.value)}
      />
      <button
        onClick={() => signUp.emailPassword({ email, password, name })}
        disabled={signUp.isPending}
      >
        Sign up
      </button>
      <button
        onClick={() => signIn.emailPassword({ email, password })}
        disabled={signIn.isPending}
      >
        Sign in
      </button>
      {error && <p style={{ color: "red" }}>{error.message}</p>}
    </main>
  );
}
```

`useAuth` returns the current user, loading state, and the `signIn`, `signUp`, and `signOut` mutations. The user state is cached by TanStack Query and stays in sync across components automatically.

#### 7. Run your app

Run your project.

```sh
npm run dev
```

Open [localhost on port 5173](http://localhost:5173) in your browser. Sign up, sign out, and sign back in to verify the flow.

### Next steps

For Next.js or TanStack Start integration, server-side rendering, and the full hook reference, see the [React library docs](/docs/products/auth/react).

---

## Start with React Native

URL: 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.

**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.

#### 1. 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
```

#### 2. Create project

Head to the [Appwrite Console](https://cloud.appwrite.io/console).

![Create project screen](/images/docs/quick-starts/create-project.avif)

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**.

**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)

![Add a platform](/images/docs/quick-starts/add-platform.avif)

**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)

[Learn more about Android app module](https://developer.android.com/build/configure-app-module)

You can skip optional steps.

#### 3. Install Appwrite

Install the Appwrite SDK for React Native and required dependencies.

```sh
npx expo install react-native-appwrite react-native-url-polyfill
```

#### 4. Implement Appwrite

Find your project's ID in the **Settings** page.

![Project settings screen](/images/docs/quick-starts/project-id.avif)

Open `app/(tabs)/index.tsx` and add the following code to it, replace `<PROJECT_ID>` with your project ID and `<YOUR_PLATFORM>` 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://<REGION>.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<Models.User<Models.Preferences> | 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
});

```

#### 5. 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
<View style={styles.root}>
    <Text>
    {loggedInUser ? `Logged in as ${loggedInUser.name}` : 'Not logged in'}
    </Text>
    <View>
    <TextInput
        style={styles.input}
        placeholder="Email"
        value={email}
        onChangeText={(text) => setEmail(text)}
    />
    <TextInput
        style={styles.input}
        placeholder="Password"
        value={password}
        onChangeText={(text) => setPassword(text)}
        secureTextEntry
    />
    <TextInput
        style={styles.input}
        placeholder="Name"
        value={name}
        onChangeText={(text) => setName(text)}
    />

    <TouchableOpacity
        style={styles.button}
        onPress={() => login(email, password)}
    >
        <Text>Login</Text>
    </TouchableOpacity>

    <TouchableOpacity
        style={styles.button}
        onPress={()=> register(email, password, name)}
    >
        <Text>Register</Text>
    </TouchableOpacity>

    <TouchableOpacity
        style={styles.button}
        onPress={async () => {
        await account.deleteSession({ sessionId: 'current' });
        setLoggedInUser(null);
        }}
    >
        <Text>Logout</Text>
    </TouchableOpacity>
    </View>
</View>
```

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',
  },
});
```

#### 6. All set

Run your project with `npx expo start`.

[Explore the React Native playground](https://github.com/appwrite/playground-for-react-native)

---

## Start with Refine

URL: https://appwrite.io/docs/quick-starts/refine

Learn how to setup your first Refine project powered by Appwrite.

#### 1. Create project

Head to the [Appwrite Console](https://cloud.appwrite.io/console).

![Create project screen](/images/docs/quick-starts/create-project.avif)

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`.

**Cross-Origin Resource Sharing (CORS)**

Adding `localhost` as a platform lets your local app talk to Appwrite. For production, add your live domain to avoid CORS errors.

Learn more in our [CORS error guide](/blog/post/cors-error).

![Add a platform](/images/docs/quick-starts/add-platform.avif)

You can skip optional steps.

#### 2. Create Refine project

Create a Refine project with Appwrite support.

```sh
npm create refine-app@latest -- --preset refine-appwrite
```

#### 3. 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).

#### 4. Import Appwrite

Find your project's ID in the **Settings** page.

![Project settings screen](/images/docs/quick-starts/project-id.avif)
Navigate to `src/utility/appwriteClient.ts` and add your API credentials.

```ts
import { Account, Appwrite, Storage } from "@refinedev/appwrite";

const APPWRITE_URL = '<YOUR_API_ENDPOINT>'; // Replace with your Appwrite API Endpoint
const APPWRITE_PROJECT = "<PROJECT_ID>"; // 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 };

```

#### 5. 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 (
        <BrowserRouter>
            <ConfigProvider theme={RefineThemes.Blue}>
                <AntdApp>
                    <Refine
                        dataProvider={dataProvider(appwriteClient, {
                            databaseId: '<APPWRITE_DATABASE_ID>',
                        })}
                        liveProvider={liveProvider(appwriteClient, {
                            databaseId: '<APPWRITE_DATABASE_ID>',
                        })}
                        authProvider={authProvider}
                        routerProvider={routerProvider}
                        notificationProvider={useNotificationProvider}
                    >
                        <Routes>
                            <Route
                                element={
                                    <Authenticated
                                        fallback={
                                            <CatchAllNavigate to="/login" />
                                        }
                                    >
                                        <ThemedLayoutV2>
                                            <Outlet />
                                        </ThemedLayoutV2>
                                    </Authenticated>
                                }
                            ></Route>

                            <Route
                                element={
                                    <Authenticated fallback={<Outlet />}>
                                        <NavigateToResource resource="<APPWRITE_TABLE_ID>" />
                                    </Authenticated>
                                }
                            >
                                <Route path="/login" element={<AuthPage />} />
                                <Route
                                    path="/register"
                                    element={<AuthPage type="register" />}
                                />
                            </Route>

                            <Route
                                element={
                                    <Authenticated>
                                        <ThemedLayoutV2>
                                            <Outlet />
                                        </ThemedLayoutV2>
                                    </Authenticated>
                                }
                            >
                                <Route path="*" element={<ErrorComponent />} />
                            </Route>
                        </Routes>
                    </Refine>
                </AntdApp>
            </ConfigProvider>
        </BrowserRouter>
    );
};

export default App;
```

#### 6. All set

Run your project with `npm run dev -- --open --port 3000` and open [Localhost on Port 3000](http://localhost:3000) in your browser.

---

## Start with Ruby

URL: https://appwrite.io/docs/quick-starts/ruby

Learn how to setup your first Ruby project powered by Appwrite.

#### 1. 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.

![Create project screen](/images/docs/quick-starts/create-project.avif)

Then, under **Integrate with your server**, add an **API Key** with the following scopes.

![Create project screen](/images/docs/quick-starts/integrate-server.avif)

| Category | 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.

#### 2. Create Ruby project

Create a Ruby CLI application.

```sh
mkdir my-app
cd my-app
bundle init
```

#### 3. 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 
```

#### 4. 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.

![Project settings screen](/images/docs/quick-starts/project-id.avif)

Create a new file `app.rb` and initialize the Appwrite Client. Replace `<PROJECT_ID>` with your project ID and `<YOUR_API_KEY>` with your API key.

```ruby
### Initialize the Appwrite client
require 'appwrite'

include Appwrite

client = Client.new()

client
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your Appwrite Endpoint
    .set_project('<PROJECT_ID>') # Your project ID
    .set_key('<YOUR_API_KEY>') # Your secret API key
```

#### 5. 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_varchar_column(
        database_id: todo_database.id,
        table_id: todo_table.id,
        key: 'title',
        size: 255,
        required: true
    )

    tablesDB.create_text_column(
        database_id: todo_database.id,
        table_id: todo_table.id,
        key: 'description',
        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
```

#### 6. 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
```

#### 7. 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)
```

#### 8. All set

Run your project with `ruby app.rb` and view the response in your console.

---

## Start with Rust

URL: https://appwrite.io/docs/quick-starts/rust

Learn how to setup your first Rust project powered by Appwrite.

#### 1. 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.

![Create project screen](/images/docs/quick-starts/create-project.avif)

Then, under **Integrate with your server**, add an **API Key** with the following scopes.

![Server integrations](/images/docs/quick-starts/integrate-server.avif)

| Category | 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.

![Project settings screen](/images/docs/quick-starts/project-id.avif)

#### 2. Create Rust project

Create a new Rust application.

```sh
cargo new my_app
cd my_app
```

#### 3. Install Appwrite

Install the Rust Appwrite SDK and its dependencies.

```sh
cargo add appwrite
cargo add tokio -F full
cargo add serde_json
```

#### 4. 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.

![Project settings screen](/images/docs/quick-starts/project-id.avif)

Open `src/main.rs` and initialize the Appwrite Client. Replace `<PROJECT_ID>` with your project ID and `<YOUR_API_KEY>` with your API key.

```rust
use appwrite::Client;
use appwrite::services::tables_db::TablesDB;
use appwrite::id::ID;
use serde_json::json;

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

    Ok(())
}
```

#### 5. Initialize database

Once the Appwrite Client is initialized, create a function to configure a todo table.

```rust
async fn prepare_database(
    tables_db: &TablesDB,
) -> Result<(String, String), Box<dyn std::error::Error>> {
    let todo_database = tables_db.create(
        ID::unique(),
        "TodosDB",
        None,
    ).await?;

    let todo_table = tables_db.create_table(
        &todo_database.id,
        ID::unique(),
        "Todos",
        None, None, None, None, None,
    ).await?;

    tables_db.create_varchar_column(
        &todo_database.id,
        &todo_table.id,
        "title",
        255,
        true,
        None, None, None,
    ).await?;

    tables_db.create_text_column(
        &todo_database.id,
        &todo_table.id,
        "description",
        false,
        Some("This is a test description."),
        None, None,
    ).await?;

    tables_db.create_boolean_column(
        &todo_database.id,
        &todo_table.id,
        "isComplete",
        true,
        None, None,
    ).await?;

    Ok((todo_database.id, todo_table.id))
}
```

#### 6. Add rows

Create a function to add some mock data into your new table.

```rust
async fn seed_database(
    tables_db: &TablesDB,
    database_id: &str,
    table_id: &str,
) -> Result<(), Box<dyn std::error::Error>> {
    tables_db.create_row(
        database_id,
        table_id,
        ID::unique(),
        json!({
            "title": "Buy apples",
            "description": "At least 2KGs",
            "isComplete": true
        }),
        None, None,
    ).await?;

    tables_db.create_row(
        database_id,
        table_id,
        ID::unique(),
        json!({
            "title": "Wash the apples",
            "isComplete": true
        }),
        None, None,
    ).await?;

    tables_db.create_row(
        database_id,
        table_id,
        ID::unique(),
        json!({
            "title": "Cut the apples",
            "description": "Don't forget to pack them in a box",
            "isComplete": false
        }),
        None, None,
    ).await?;

    Ok(())
}
```

#### 7. Retrieve rows

Create a function to retrieve the mock todo data and execute the functions in `main`.

```rust
use appwrite::query::Query;

async fn get_todos(
    tables_db: &TablesDB,
    database_id: &str,
    table_id: &str,
) -> Result<(), Box<dyn std::error::Error>> {
    // Retrieve rows (default limit is 25)
    let todos = tables_db.list_rows(
        database_id,
        table_id,
        None, None, None, None,
    ).await?;

    println!("Todos:");
    for todo in &todos.rows {
        println!("Title: {}\nDescription: {}\nIs Todo Complete: {}\n",
            todo.get::<String>("title").unwrap_or_default(),
            todo.get::<String>("description").unwrap_or_default(),
            todo.get::<bool>("isComplete").unwrap_or_default(),
        );
    }

    // Use queries to filter completed todos with pagination
    let completed_todos = tables_db.list_rows(
        database_id,
        table_id,
        Some(vec![
            Query::equal("isComplete", true).to_string(),
            Query::order_desc("$createdAt").to_string(),
            Query::limit(5).to_string(),
        ]),
        None, None, None,
    ).await?;

    println!("Completed todos (limited to 5):");
    for todo in &completed_todos.rows {
        println!("Title: {}\nDescription: {}\nIs Todo Complete: {}\n",
            todo.get::<String>("title").unwrap_or_default(),
            todo.get::<String>("description").unwrap_or_default(),
            todo.get::<bool>("isComplete").unwrap_or_default(),
        );
    }

    // Query for incomplete todos
    let incomplete_todos = tables_db.list_rows(
        database_id,
        table_id,
        Some(vec![
            Query::equal("isComplete", false).to_string(),
            Query::order_asc("title").to_string(),
        ]),
        None, None, None,
    ).await?;

    println!("Incomplete todos (ordered by title):");
    for todo in &incomplete_todos.rows {
        println!("Title: {}\nDescription: {}\nIs Todo Complete: {}\n",
            todo.get::<String>("title").unwrap_or_default(),
            todo.get::<String>("description").unwrap_or_default(),
            todo.get::<bool>("isComplete").unwrap_or_default(),
        );
    }

    Ok(())
}
```

Make sure to update `main()` with the functions you created. Your `main()` function should look something like this:

```rust
use appwrite::Client;
use appwrite::services::tables_db::TablesDB;
use appwrite::id::ID;
use appwrite::query::Query;
use serde_json::json;

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

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

    let (database_id, table_id) = prepare_database(&tables_db).await?;
    seed_database(&tables_db, &database_id, &table_id).await?;
    get_todos(&tables_db, &database_id, &table_id).await?;

    Ok(())
}
```

#### 8. All set

Run your project with `cargo run` and view the response in your console.

---

## Start with Solid

URL: https://appwrite.io/docs/quick-starts/solid

Learn how to setup your first Solid project powered by Appwrite.

#### 1. Create project

Head to the [Appwrite Console](https://cloud.appwrite.io/console).

![Create project screen](/images/docs/quick-starts/create-project.avif)

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`.

**Cross-Origin Resource Sharing (CORS)**

Adding `localhost` as a platform lets your local app talk to Appwrite. For production, add your live domain to avoid CORS errors.

Learn more in our [CORS error guide](/blog/post/cors-error).

![Add a platform](/images/docs/quick-starts/add-platform.avif)

You can skip optional steps.

#### 2. Create Solid project

Create a Vite project.

```sh
npm create vite@latest my-app -- --template solid && cd my-app
```

#### 3. Install Appwrite

Install the JavaScript Appwrite SDK.

```sh
npm install appwrite
```

#### 4. Import Appwrite

Find your project's ID in the **Settings** page.

![Project settings screen](/images/docs/quick-starts/project-id.avif)

Create a new file `src/lib/appwrite.js` 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';
```

#### 5. 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 (
            <div>
                <p>Logged in as {loggedInUser().name}</p>
                <button onClick={logout}>Logout</button>
            </div>
        );
    }

    return (
        <div>
            <p>Not logged in</p>
            <form>
                <input type="email" placeholder="Email" value={email()} onChange={e => setEmail(e.target.value)} />
                <input type="password" placeholder="Password" value={password()} onChange={e => setPassword(e.target.value)} />
                <input type="text" placeholder="Name" value={name()} onChange={e => setName(e.target.value)} />
                <button type="button" onClick={() => login(email(), password())}>
                    Login
                </button>
                <button type="button" onClick={() => register(email(), password(), name())}>
                    Register
                </button>
            </form>
        </div>
    );
};

export default App;
```

#### 6. All set

Run your project with `npm run dev -- --open --port 3000` and open [Localhost on Port 3000](http://localhost:3000) in your browser.

---

## Start with SvelteKit

URL: https://appwrite.io/docs/quick-starts/sveltekit

Learn how to setup your first SvelteKit project powered by Appwrite.

#### 1. Create project

Head to the [Appwrite Console](https://cloud.appwrite.io/console).

![Create project screen](/images/docs/quick-starts/create-project.avif)

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`.

**Cross-Origin Resource Sharing (CORS)**

Adding `localhost` as a platform lets your local app talk to Appwrite. For production, add your live domain to avoid CORS errors.

Learn more in our [CORS error guide](/blog/post/cors-error).

![Add a platform](/images/docs/quick-starts/add-platform.avif)

You can skip optional steps.

#### 2. Create SvelteKit project

Create a SvelteKit project.

```sh
npx sv create
```

#### 3. Install Appwrite

Install the JavaScript Appwrite SDK.

```sh
npm install appwrite
```

#### 4. Import Appwrite

Find your project's ID in the **Settings** page.

![Project settings screen](/images/docs/quick-starts/project-id.avif)
Create a new file `src/lib/appwrite.js` 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';
```

#### 5. Create a login page

Replace the contents of `src/routes/+page.svelte` with the following code.

```html
<script>
    import { account, ID } from '$lib/appwrite';

    let loggedInUser = null;

    async function login(email, password) {
        await account.createEmailPasswordSession({
            email,
            password
        });
        loggedInUser = await account.get();
    }

    async function register(email, password) {
        await account.create({
            userId: ID.unique(),
            email,
            password
        });
        login(email, password);
    }

    function submit(e) {
        e.preventDefault();
        const formData = new FormData(e.target);
        const type = e.submitter.dataset.type;

        if (type === "login") {
            login(formData.get('email'), formData.get('password'));
        } else if (type === "register") {
            register(formData.get('email'), formData.get('password'));
        }
    }

    async function logout() {
        await account.deleteSession({ sessionId: 'current' });
        loggedInUser = null;
    }
</script>

<p>
    {loggedInUser ? `Logged in as ${loggedInUser.name}` : 'Not logged in'}
</p>

<form on:submit={submit}>
    <input type="email" placeholder="Email" name="email" required />
    <input type="password" placeholder="Password" name="password" required />

    <button type="submit" data-type="login">Login</button>
    <button type="submit" data-type="register">Register</button>
</form>

<button on:click={logout}>Logout</button>
```

#### 6. All set

Run your project with `npm run dev` and open [localhost on port 5173](http://localhost:5173) in your browser.

---

## Start with Swift

URL: https://appwrite.io/docs/quick-starts/swift

Learn how to setup your first Swift project powered by Appwrite.

**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).

#### 1. 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.

![Create project screen](/images/docs/quick-starts/create-project.avif)

Then, under **Integrate with your server**, add an **API Key** with the following scopes.

![Server integrations](/images/docs/quick-starts/integrate-server.avif)
| Category | 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.

![Project settings screen](/images/docs/quick-starts/project-id.avif)

#### 2. 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.

#### 3. 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**.

#### 4. 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.

![Project settings screen](/images/docs/quick-starts/project-id.avif)

Open the file `main.swift` and initialize the Appwrite Client. Replace `<PROJECT_ID>` with your project ID and `<YOUR_API_KEY>` with your API key.

```swift
import Foundation
import Appwrite
import AppwriteModels

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")
```

#### 5. 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.createVarcharColumn(
        databaseId: todoDatabase!.id,
        tableId: todoTable!.id,
        key: "title",
        size: 255,
        required: true
    )
    try? await tablesDB.createTextColumn(
        databaseId: todoDatabase!.id as! String,
        tableId: todoTable!.id as! String,
        key: "description",
        required: false,
        default: "This is a test description."
    )
    try? await tablesDB.createBooleanColumn(
        databaseId: todoDatabase!.id as! String,
        tableId: todoTable!.id as! String,
        key: "isComplete",
        required: true
    )
    
    return (todoDatabase, todoTable)
}
```

#### 6. 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
    )
}
```

#### 7. 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)
```

#### 8. All set

Run your project with XCode and see the results in the console.

---

## Start with TanStack Start

URL: https://appwrite.io/docs/quick-starts/tanstack-start

Learn how to set up your first TanStack Start project with the [Appwrite React library](/docs/products/auth/react). The library exposes a TanStack file-route handler, server helpers, and the same React hooks you use on the client.

#### 1. Create project

Head to the [Appwrite Console](https://cloud.appwrite.io/console).

![Create project screen](/images/docs/quick-starts/create-project.avif)

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`.

**Cross-Origin Resource Sharing (CORS)**

Adding `localhost` as a platform lets your local app talk to Appwrite. For production, add your live domain to avoid CORS errors.

Learn more in our [CORS error guide](/blog/post/cors-error).

![Add a platform](/images/docs/quick-starts/add-platform.avif)

You can skip optional steps.

#### 2. Create an API key

In your project, go to **Overview** > **Integrations** > **API keys** and create a new key with the scopes `users.read`, `users.write`, and `sessions.write`. Copy the key secret. The SSR handler uses this to create sessions on behalf of users; never expose it to the browser.

#### 3. Create TanStack Start project

Create a TanStack Start project.

```sh
npx @tanstack/cli create my-app --framework react && cd my-app
```

#### 4. Install the React library

Install the React library along with the Appwrite Web SDK, Appwrite Node SDK, and `@tanstack/react-query` packages.

```sh
npm install @appwrite.io/react appwrite node-appwrite @tanstack/react-query
```

#### 5. Configure environment variables

Create a `.env` file at the project root. Replace `<REGION>`, `<PROJECT_ID>`, and `<API_KEY>` with your own values.

```sh
VITE_APPWRITE_ENDPOINT=https://<REGION>.cloud.appwrite.io/v1
VITE_APPWRITE_PROJECT_ID=<PROJECT_ID>
APPWRITE_API_KEY=<API_KEY>
```

`VITE_*` values are shipped to the browser. `APPWRITE_API_KEY` stays server-only.

#### 6. Mount the auth handler route

Create `src/routes/api/appwrite/$.ts`. The TanStack file route exposes the library's `sign-in`, `sign-up`, `sign-out`, and `oauth/callback` endpoints under `/api/appwrite/*`.

```ts
import { createFileRoute } from "@tanstack/react-router";
import { createAppwriteHandlers } from "@appwrite.io/react/handlers/tanstack";

export const Route = createFileRoute("/api/appwrite/$")({
  server: {
    handlers: createAppwriteHandlers({
      endpoint: import.meta.env.VITE_APPWRITE_ENDPOINT,
      projectId: import.meta.env.VITE_APPWRITE_PROJECT_ID,
      apiKey: process.env.APPWRITE_API_KEY!,
      basePath: "/api/appwrite",
    }),
  },
});
```

#### 7. Read auth state in a server function

Replace `src/routes/index.tsx` with the following. A `createServerFn` loader reads the session cookie server-side; the page receives the result via `Route.useLoaderData()` and passes it into `AppwriteProvider`.

```tsx
import { useState } from "react";
import { createFileRoute, useRouter } from "@tanstack/react-router";
import { createServerFn } from "@tanstack/react-start";
import { AppwriteProvider, useAuth } from "@appwrite.io/react";
import { createTanStackServerHelpers } from "@appwrite.io/react/server/tanstack";

const getAuthSnapshot = createServerFn({ method: "GET" }).handler(async () => {
  const helpers = createTanStackServerHelpers({
    endpoint: import.meta.env.VITE_APPWRITE_ENDPOINT,
    projectId: import.meta.env.VITE_APPWRITE_PROJECT_ID,
  });
  return {
    session: helpers.readSessionCookie() ?? null,
    user: await helpers.getLoggedInUser(),
  };
});

export const Route = createFileRoute("/")({
  loader: () => getAuthSnapshot(),
  component: Page,
});

function Page() {
  const { session, user } = Route.useLoaderData();

  return (
    <AppwriteProvider
      endpoint={import.meta.env.VITE_APPWRITE_ENDPOINT}
      projectId={import.meta.env.VITE_APPWRITE_PROJECT_ID}
      ssr={{ session, basePath: "/api/appwrite" }}
    >
      <main>
        <h1>Appwrite React library on TanStack Start</h1>
        <p>SSR user: {user?.email ?? "signed out"}</p>
        <AuthPanel />
      </main>
    </AppwriteProvider>
  );
}

function AuthPanel() {
  const { user, isLoading, signIn, signUp, signOut, error } = useAuth();
  const router = useRouter();
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [name, setName] = useState("");

  if (isLoading) return <p>Loading...</p>;

  if (user) {
    return (
      <div>
        <p>Welcome, {user.name || user.email}</p>
        <button onClick={() => signOut.signOut({ onSuccess: () => router.invalidate() })}>
          Sign out
        </button>
      </div>
    );
  }

  return (
    <div>
      <input placeholder="Name" value={name} onChange={(e) => setName(e.target.value)} />
      <input placeholder="Email" value={email} onChange={(e) => setEmail(e.target.value)} />
      <input
        placeholder="Password"
        type="password"
        value={password}
        onChange={(e) => setPassword(e.target.value)}
      />
      <button
        onClick={() =>
          signUp.emailPassword({
            email,
            password,
            name,
            onSuccess: () => router.invalidate(),
          })
        }
        disabled={signUp.isPending}
      >
        Sign up
      </button>
      <button
        onClick={() =>
          signIn.emailPassword({
            email,
            password,
            onSuccess: () => router.invalidate(),
          })
        }
        disabled={signIn.isPending}
      >
        Sign in
      </button>
      {error && <p style={{ color: "red" }}>{error.message}</p>}
    </div>
  );
}
```

After every auth mutation, `router.invalidate()` re-runs the loader so the SSR user reflects the new session cookie.

**Multi-route apps**

In a multi-route TanStack Start app, move `AppwriteProvider` (and the auth loader) into `src/routes/__root.tsx` so the TanStack Query cache and auth state persist across navigations. Mounting the provider in a single route works for this quickstart, but resets the cache every time the route changes.

#### 8. Run your app

```sh
npm run dev
```

Open [localhost on port 3000](http://localhost:3000). Sign up, sign out, and sign back in to verify the cookie-based SSR flow.

### Next steps

For server-side admin operations, per-request session clients, OAuth callbacks, and the full hook reference, see the [React library docs](/docs/products/auth/react).

---

## Start with Vue.js

URL: https://appwrite.io/docs/quick-starts/vue

Learn how to setup your first Vue project powered by Appwrite.

#### 1. Create project

Head to the [Appwrite Console](https://cloud.appwrite.io/console).

![Create project screen](/images/docs/quick-starts/create-project.avif)

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`.

**Cross-Origin Resource Sharing (CORS)**

Adding `localhost` as a platform lets your local app talk to Appwrite. For production, add your live domain to avoid CORS errors.

Learn more in our [CORS error guide](/blog/post/cors-error).

![Add a platform](/images/docs/quick-starts/add-platform.avif)

You can skip optional steps.

#### 2. Create Vue project

Create a Vue project.

```sh
npm init vue@latest my-app && cd my-app
```

#### 3. Install Appwrite

Install the JavaScript Appwrite SDK.

```sh
npm install appwrite
```

#### 4. Import Appwrite

Find your project's ID in the **Settings** page.

![Project settings screen](/images/docs/quick-starts/project-id.avif)

Create a new file `src/lib/appwrite.js` 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';
```

#### 5. Create a login page

Add the following code to `src/App.vue`.

```html
<template>
  <div>
    <p>
      {{ loggedInUser ? `Logged in as ${loggedInUser.name}` : 'Not logged in' }}
    </p>

    <form>
      <input type="email" placeholder="Email" v-model="email" />
      <input type="password" placeholder="Password" v-model="password" />
      <input type="text" placeholder="Name" v-model="name" />
      <button type="button" @click="login(email, password)">Login</button>
      <button type="button" @click="register">
        Register
      </button>
      <button type="button" @click="logout">
        Logout
      </button>
    </form>
  </div>
</template>

<script setup>
import { ref } from 'vue';
import { account, ID } from './lib/appwrite.js';

const loggedInUser = ref(null);
const email = ref('');
const password = ref('');
const name = ref('');

const login = async (email, password) => {
  await account.createEmailPasswordSession({
    email,
    password
  });
  loggedInUser.value = await account.get();
};

const register = async () => {
  await account.create({
    userId: ID.unique(),
    email: email.value,
    password: password.value,
    name: name.value
  });
  login(email.value, password.value);
};

const logout = async () => {
  await account.deleteSession({
    sessionId: 'current'
  });
  loggedInUser.value = null;
};
</script>
```

#### 6. All set

Run your project with `npm run dev -- --open --port 3000` and open [Localhost on Port 3000](http://localhost:3000) in your browser.

---

## Start with Web

URL: https://appwrite.io/docs/quick-starts/web

Learn how to add Appwrite to your web apps.

#### 1. Create project

Head to the [Appwrite Console](https://cloud.appwrite.io/console).

![Create project screen](/images/docs/quick-starts/create-project.avif)

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.

**Cross-Origin Resource Sharing (CORS)**

Adding `localhost` as a platform lets your local app talk to Appwrite. For production, add your live domain to avoid CORS errors.

Learn more in our [CORS error guide](/blog/post/cors-error).

![Add a platform](/images/docs/quick-starts/add-platform.avif)

You can skip optional steps.

#### 2. 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
<script src="https://cdn.jsdelivr.net/npm/appwrite@17.0.0"></script>
```

#### 3. 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://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>'); // 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('<PROJECT_ID>') // Replace with your project ID

const account = new Appwrite.Account(client)
const tablesDB = new Appwrite.TablesDB(client)
```

#### 4. 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://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>'); // 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
});
```

#### 5. 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[];
```

#### 6. All set

The Appwrite SDK works with your favorite Web frameworks.

Learn to use Appwrite by adding authentication to a simple web app.

- [Next.js](/docs/quick-starts/nextjs): Get started with Appwrite and Next.js
- [React](/docs/quick-starts/react): Get started with Appwrite and React
- [Vue.js](/docs/quick-starts/vue): Get started with Appwrite and Vue.js
- [Nuxt](/docs/quick-starts/nuxt): Get started with Appwrite and Nuxt
- [SvelteKit](/docs/quick-starts/sveltekit): Get started with Appwrite and SvelteKit
- [Angular](/docs/quick-starts/angular): Get started with Appwrite and Angular

Learn to use Appwrite by building an idea tracker app.

- [React](/docs/tutorials/react): Get started with Appwrite and React
- [Vue.js](/docs/tutorials/vue): Get started with Appwrite and Vue.js
- [Nuxt](/docs/tutorials/nuxt): Get started with Appwrite and Nuxt
- [SvelteKit](/docs/tutorials/sveltekit): Get started with Appwrite and SvelteKit

#### 7. 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://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>');

const databases = new TablesDB(client);

// Type-safe database operations
try {
    const users = await databases.listRows<User>({
        databaseId: '[DATABASE_ID]',
        tableId: '[TABLE_ID]'
    });

    users.rows.forEach(user => {
        console.log(`User: ${user.name} (${user.email})`);
    });
} catch (error) {
    console.log(error);
}
```

**Generate types automatically**

Use the [Appwrite CLI](/docs/products/databases/type-generation) to generate TypeScript interfaces automatically: `appwrite types ./types`

---

## API reference

URL: 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 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/security/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/partners/project/api-keys) scope, ignoring user permissions.

#### APIs
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/partners/project/api-keys).

![Project settings screen](/images/docs/quick-starts/project-id.avif)

- [Account](/docs/references/cloud/client-web/account): Add signup, login, logout, and other common authentication methods to client apps.
- [Users](/docs/references/cloud/server-nodejs/users): Manage users from an admin scope to build server integrations.
- [Teams](/docs/references/cloud/client-web/teams): Group users to share access to rows, files, and functions.
- [Databases](/docs/references/cloud/client-web/databases): Scalable and robust database backed by your favorite technologies.
- [TablesDB](/docs/references/cloud/client-web/tablesDB): Create structured tables of rows with validated columns and fine-grained permissions.
- [Sites](/docs/references/cloud/server-nodejs/sites): Build and deploy websites on the internet at scale.
- [Storage](/docs/references/cloud/client-web/storage): Securely store files with advanced compression, encryption and image transformations.
- [Functions](/docs/references/cloud/client-web/functions): Deploy and scale serverless functions in secure, isolated runtimes.
- [Messaging](/docs/references/cloud/server-nodejs/messaging): Send push notifications, SMS, or emails to users or groups of users using your app.
- [Tokens](/docs/references/cloud/server-nodejs/tokens): Create and manage temporary access tokens for secure file sharing and resource access control.
- [Localization](/docs/references/cloud/client-web/locale): Utility APIs to customize your app based on your users' location.
- [Avatars](/docs/references/cloud/client-web/avatars): Complete everyday tasks related to your app image, icons, and avatars.
- [Presences](/docs/references/cloud/client-web/presences): Track which signed-in users are active right now and broadcast their status in realtime.

#### 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/apis/response-codes#error-handling) and [Response codes](/docs/apis/response-codes) documentation.

---

## Quick start

URL: 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.

If you are choosing Appwrite among [BaaS platforms](/blog/post/backend-as-a-service) or mapping [backend infrastructure](/blog/post/best-backend-as-a-service-platforms) options first, skim that guide, then return here to wire SDKs.

#### 1. 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.

#### 2. 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.

![Create project screen](/images/docs/quick-starts/create-project.avif)

Then, configure your project depending on use case.
You can follow all three flows to enable all three use cases.

**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.

![Add a platform](/images/docs/quick-starts/add-platform.avif)

You can skip optional steps.

**Server**

Under **Integrate with your server**, add an **API Key**.

![Create project screen](/images/docs/quick-starts/integrate-server.avif)

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.

**SSR**

Under **Integrate with your server**, add an **API Key** with the following scopes.

![Create project screen](/images/docs/quick-starts/integrate-server.avif)

| Category | Required scopes | Purpose |
|-----------|-----------------------|---------|
| Sessions | `sessions.write` | Allows API key to create, update, and delete sessions. |

#### 3. 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.

**Client**

Client apps need to be configured with endpoint and project ID,
so the Appwrite SDK knows which endpoint and project to connect to.

```client-web
import { Client } from "appwrite";

const client = new Client();

client
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')    // Your API Endpoint
    .setProject('<PROJECT_ID>')                // Your project ID
;
```
```client-flutter
import 'package:appwrite/appwrite.dart';

Client client = Client();

client
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')    // Your API Endpoint
    .setProject('<PROJECT_ID>')                // Your project ID
;
```
```client-apple
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")    // Your API Endpoint
    .setProject("<PROJECT_ID>")                            // Your project ID
```
```client-android-kotlin
import io.appwrite.Client

val client = Client(context)
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")    // Your API Endpoint
    .setProject("<PROJECT_ID>")                // Your project ID
```

**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.

```server-nodejs
const sdk = require('node-appwrite');

// Init SDK
const client = new sdk.Client();

client
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')                // Your API Endpoint
    .setProject('<PROJECT_ID>')                            // Your project ID
    .setJWT('<YOUR_API_KEY>')                                   // Your secret JSON Web Token
;
```
```deno
import * as sdk from "npm:node-appwrite";

// Init SDK
let client = new sdk.Client();

client
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')                // Your API Endpoint
    .setProject('<PROJECT_ID>')                            // Your project ID
    .setJWT('<YOUR_API_KEY>')                                   // Your secret JSON Web Token
;
```
```php
<?php

use Appwrite\Client;

$client = new Client();

$client
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')               // Your API Endpoint
    ->setProject('<PROJECT_ID>')                           // Your project ID
    ->setJWT('<YOUR_API_KEY>')                                  // Your secret JSON Web Token
;
```
```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_jwt('<YOUR_API_KEY>')                                    # Your secret JSON Web Token
)
```
```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('<YOUR_API_KEY>')                                  # Your secret JSON Web Token
```
```csharp
using Appwrite;
using Appwrite.Services;

var client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")                // Your API Endpoint
    .SetProject("<PROJECT_ID>")                            // Your project ID
    .SetJWT("<YOUR_API_KEY>");                                  // Your secret JSON Web Token
```
```dart
import 'package:dart_appwrite/dart_appwrite.dart';

void main() {                                                   // Init SDK
  Client client = Client();

  client
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')                // Your API Endpoint
    .setProject('<PROJECT_ID>')                            // Your project ID
    .setJWT('<YOUR_API_KEY>')                                   // Your secret JSON Web Token
  ;
}
```
```kotlin
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;

Client client = new Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")                // Your API Endpoint
    .setProject("<PROJECT_ID>")                            // Your project ID
    .setJWT("<YOUR_API_KEY>");                                  // Your secret JSON Web Token
```
```java
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;

Client client = new Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")                // Your API Endpoint
    .setProject("<PROJECT_ID>")                            // Your project ID
    .setJWT("<YOUR_API_KEY>");                                  // Your secret JSON Web Token
```
```swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")                // Your API Endpoint
    .setProject("<PROJECT_ID>")                            // Your project ID
    .setJWT("<YOUR_API_KEY>")                                   // Your secret JSON Web Token
```
```server-rust
use appwrite::Client;

let client = Client::new()
    .set_endpoint("https://<REGION>.cloud.appwrite.io/v1")  // Your API Endpoint
    .set_project("<PROJECT_ID>")                            // Your project ID
    .set_jwt("<YOUR_API_KEY>");                             // Your secret JSON Web Token
```

**SSR**

Appwrite uses Server SDKs for SSR apps. The initialization is different
#### Admin client

**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).

To initialize the admin client, we'll need to first [generated an API key](/docs/partners/project/api-keys#create-api-key).
The API key should have the following scope in order to perform authentication:

| Category | Required scopes | Purpose |
|-----------|---------------------|---------|
| Sessions | `sessions.write` | Allows API key to create, update, and delete sessions. |

```server-nodejs
import { Client } from "node-appwrite"; // Using the server SDK

const adminClient = 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
```
```php
use Appwrite\Client;
use Appwrite\Services\Account;

$adminClient = (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


```
```server-rust
use appwrite::Client;

let admin_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
```

It is important to use an API key, as this will allow your server requests to bypass [rate limits](/docs/advanced/security/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

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.

```server-nodejs
const sessionClient = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    .setProject('<PROJECT_ID>');            // 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://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
    ->setProject('<PROJECT_ID>');            // Your project ID

$session = $_COOKIE['session']; // Get the session cookie from the request
if ($session) {
    $sessionClient->setSession($session);
}
```
```server-rust
use appwrite::Client;

let session_client = Client::new()
    .set_endpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
    .set_project("<PROJECT_ID>");            // Your project ID

// Get the session cookie from the request
// This depends on your HTTP framework (e.g. actix-web, axum, rocket)
let session = req.cookie("a_session_<PROJECT_ID>");
session_client.set_session(session);
```

You will use the initialized client in all requests you make to Appwrite.

If you're using Appwrite without an SDK, follow the guides for the [REST API](/docs/apis/rest) or [GraphQL API](/docs/apis/graphql).

#### 4. Examples

If you prefer to explore examples, follow one of the following quick starts.

#### Client apps
Examples when building with Client APIs

- [Web](/docs/quick-starts/web): Just plain JavaScript and TypeScript.
- [Next.js](/docs/quick-starts/nextjs): Get started with Appwrite and Next.js
- [React](/docs/quick-starts/react): Get started with Appwrite and React
- [Vue.js](/docs/quick-starts/vue): Get started with Appwrite and Vue.js
- [Nuxt](/docs/quick-starts/nuxt): Get started with Appwrite and Nuxt
- [SvelteKit](/docs/quick-starts/sveltekit): Get started with Appwrite and SvelteKit
- [Angular](/docs/quick-starts/angular): Get started with Appwrite and Angular
- [Flutter](/docs/quick-starts/flutter): Get started with Appwrite and Flutter
- [Apple](/docs/quick-starts/apple): Get started with Appwrite and Apple
- [Android](/docs/quick-starts/android): Get started with Appwrite and Android

#### Server apps
Examples when building with Server APIs

- [Node.js](/docs/quick-starts/node): Get started with Appwrite and Node.js
- [Python](/docs/quick-starts/python): Get started with Appwrite and Python
- [Dart](/docs/quick-starts/dart): Get started with Appwrite and Dart
- [PHP](/docs/quick-starts/php): Get started with Appwrite and PHP
- [Ruby](/docs/quick-starts/ruby): Get started with Appwrite and Ruby
- [.NET](/docs/quick-starts/dotnet): Get started with Appwrite and .NET
- [Deno](/docs/quick-starts/deno): Get started with Appwrite and Deno
- [Go](/docs/quick-starts/go): Get started with Appwrite and Go
- [Swift](/docs/quick-starts/swift): Get started with Appwrite and Swift
- [Kotlin](/docs/quick-starts/kotlin): Get started with Appwrite and Kotlin

---

## SDKs

URL: 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 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.

| &nbsp; | Platform | GitHub Repository | &nbsp; |
| --- | --- | --- | --- |
| ![Javascript logo](/images/platforms/light/javascript.svg) | Web SDK `23.0.0` | [appwrite/sdk-for-web](https://github.com/appwrite/sdk-for-web) | |
| ![Flutter logo](/images/platforms/light/flutter.svg) | Flutter SDK `22.0.0` | [appwrite/sdk-for-flutter](https://github.com/appwrite/sdk-for-flutter) | |
| ![React logo](/images/platforms/light/react.svg) | React library `0.1.0` | [appwrite/sdk-for-react](https://github.com/appwrite/sdk-for-react) | `beta` |
| ![React logo](/images/platforms/light/react.svg) | React Native SDK `0.25.0` | [appwrite/sdk-for-react-native](https://github.com/appwrite/sdk-for-react-native) | `beta` |
| ![Apple logo](/images/platforms/light/apple.svg) | Apple SDK `15.0.0` | [appwrite/sdk-for-apple](https://github.com/appwrite/sdk-for-apple) | |
| ![Android logo](/images/platforms/light/android.svg) | Android SDK `13.0.0` | [appwrite/sdk-for-android](https://github.com/appwrite/sdk-for-android) | |

### 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.

| &nbsp; | Platform | GitHub Repository | &nbsp; |
| --- | --- | --- | --- |
| ![Node.js logo](/images/platforms/light/node.svg) | Node.js SDK `22.1.3` | [appwrite/sdk-for-node](https://github.com/appwrite/sdk-for-node) | |
| ![Python logo](/images/platforms/light/python.svg) | Python SDK `16.0.0` | [appwrite/sdk-for-python](https://github.com/appwrite/sdk-for-python) | |
| ![Dart logo](/images/platforms/light/dart.svg) | Dart SDK `21.3.0` | [appwrite/sdk-for-dart](https://github.com/appwrite/sdk-for-dart) | |
| ![PHP logo](/images/platforms/light/php.svg) | PHP SDK `20.2.1` | [appwrite/sdk-for-php](https://github.com/appwrite/sdk-for-php) | |
| ![Ruby logo](/images/platforms/light/ruby.svg) | Ruby SDK `21.1.0` | [appwrite/sdk-for-ruby](https://github.com/appwrite/sdk-for-ruby) | |
| ![.NET logo](/images/platforms/light/dotnet.svg) | .NET SDK `1.0.0` | [appwrite/sdk-for-dotnet](https://github.com/appwrite/sdk-for-dotnet) | |
| ![Go logo](/images/platforms/light/go.svg) | Go SDK `1.0.0` | [appwrite/sdk-for-go](https://github.com/appwrite/sdk-for-go) | |
| ![Swift logo](/images/platforms/light/swift.svg) | Swift SDK `15.2.0` | [appwrite/sdk-for-swift](https://github.com/appwrite/sdk-for-swift) | |
| ![Kotlin logo](/images/platforms/light/kotlin.svg) | Kotlin SDK `14.1.0` | [appwrite/sdk-for-kotlin](https://github.com/appwrite/sdk-for-kotlin) | |
| ![Rust logo](/images/platforms/light/rust.svg) | Rust SDK `0.2.0` | [appwrite/sdk-for-rust](https://github.com/appwrite/sdk-for-rust) | |

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
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:

- [Realtime API](/docs/apis/realtime): Integrate with the Appwrite Realtime API

- [REST API](/docs/apis/rest): Integrate with the Appwrite REST API

- [GraphQL API](/docs/apis/graphql): Integrate with the Appwrite GraphQL API

### 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
Appwrite has utility classes to help you handle IDs.
These generate the correct ID format to be passed to the Appwrite APIs.

**Client SDKs**

```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")
```

**Server SDKs**

```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
<?php

use Appwrite\ID;

// Generate a unique ID
ID::unique()

// Generate a custom ID
ID::custom("my-custom-id")
```
```python
from appwrite.id import ID

### Generate a unique ID
ID.unique()

### Generate a custom ID
ID.custom("my-custom-id")
```
```csharp
using Appwrite.ID;

// Generate a unique ID
ID.unique()

// Generate a custom ID
ID.custom("my-custom-id")
```
```dart
import 'package:dart_appwrite/dart_appwrite.dart';

// Generate a unique ID
ID.unique()

// Generate a custom ID
ID.custom("my-custom-id")
```
```java
io.appwrite.ID
// Generate a unique ID
ID.unique()

// Generate a custom ID
ID.custom("my-custom-id")
```
```kotlin
io.appwrite.ID
// Generate a unique ID
ID.unique()

// Generate a custom ID
ID.custom("my-custom-id")
```
```ruby
require 'appwrite'

include Appwrite
ß
### Generate a unique ID
ID.unique()

### Generate a custom ID
ID.custom("my-custom-id")
```
```swift
import Appwrite
// Generate a unique ID
ID.unique()

// Generate a custom ID
ID.custom("my-custom-id")
```
```rust
use appwrite::id::ID;

// Generate a unique ID
ID::unique()

// Generate a custom ID
ID::custom("my-custom-id")
```

Appwrite generates unique IDs that are both **random** and **ordered**.
Appwrite IDs combine a timestamp prefix based on microseconds and a random UID string, formatted as `<timestamp_hex><uid_hex>`.

#### 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.

**Client SDKs**

You can discover the available enums in each SDK at the source.

| &nbsp; | Platform | Enums | &nbsp; |
| --- | --- | --- | --- |
| ![Javascript logo](/images/platforms/light/javascript.svg) | Web SDK `23.0.0` | [appwrite/sdk-for-web](https://github.com/appwrite/sdk-for-web/tree/dev/src/enums) | |
| ![Flutter logo](/images/platforms/light/flutter.svg) | Flutter SDK `22.0.0` | [appwrite/sdk-for-flutter](https://github.com/appwrite/sdk-for-flutter/tree/dev/lib/src/enums) | |
| ![React logo](/images/platforms/light/react.svg) | React Native SDK `0.25.0` | [appwrite/sdk-for-react-native](https://github.com/appwrite/sdk-for-react-native) | |
| ![Apple logo](/images/platforms/light/apple.svg) | Apple SDK `15.0.0` | [appwrite/sdk-for-apple](https://github.com/appwrite/sdk-for-apple/tree/dev/Sources/AppwriteEnums) | |
| ![Android logo](/images/platforms/light/android.svg) | Android SDK `13.0.0` | [appwrite/sdk-for-android](https://github.com/appwrite/sdk-for-android/tree/dev/library/src/main/java/io/appwrite/enums) | |

**Server SDKs**

You can discover the available enums in each SDK at the source.

| &nbsp; | Platform | Enums | &nbsp; |
| --- | --- | --- | --- |
| ![Node.js logo](/images/platforms/light/node.svg) | Node.js SDK `22.1.3` | [appwrite/sdk-for-node](https://github.com/appwrite/sdk-for-node/tree/dev/lib/enums) | |
| ![Python logo](/images/platforms/light/python.svg) | Python SDK `16.0.0` | [appwrite/sdk-for-python](https://github.com/appwrite/sdk-for-python/tree/dev/appwrite/enums) | |
| ![Dart logo](/images/platforms/light/dart.svg) | Dart SDK `21.3.0` | [appwrite/sdk-for-dart](https://github.com/appwrite/sdk-for-dart/blob/dev/lib/enums.dart) | |
| ![PHP logo](/images/platforms/light/php.svg) | PHP SDK `20.2.1` | [appwrite/sdk-for-php](https://github.com/appwrite/sdk-for-php/tree/dev/src/Appwrite/Enums) | |
| ![Ruby logo](/images/platforms/light/ruby.svg) | Ruby SDK `21.1.0` | [appwrite/sdk-for-ruby](https://github.com/appwrite/sdk-for-ruby/tree/dev/lib/appwrite/enums) | |
| ![.NET logo](/images/platforms/light/dotnet.svg) | .NET SDK `1.0.0` | [appwrite/sdk-for-dotnet](https://github.com/appwrite/sdk-for-dotnet/tree/dev/src/Appwrite/Enums) | |
| ![Go logo](/images/platforms/light/go.svg) | Go SDK `1.0.0` | [appwrite/sdk-for-go](https://github.com/appwrite/sdk-for-go) | |
| ![Swift logo](/images/platforms/light/swift.svg) | Swift SDK `15.2.0` | [appwrite/sdk-for-swift](https://github.com/appwrite/sdk-for-swift/tree/dev/Sources/AppwriteEnums) | |
| ![Kotlin logo](/images/platforms/light/kotlin.svg) | Kotlin SDK `14.1.0` | [appwrite/sdk-for-kotlin](https://github.com/appwrite/sdk-for-kotlin/tree/dev/src/main/kotlin/io/appwrite/enums) | |
| ![Rust logo](/images/platforms/light/rust.svg) | Rust SDK `0.2.0` | [appwrite/sdk-for-rust](https://github.com/appwrite/sdk-for-rust/tree/dev/src/enums) | |

#### 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/security/permissions) in their own pages.

#### File I/O
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
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).

---

## Tooling

URL: https://appwrite.io/docs/tooling

Appwrite tooling covers everything you use to build, automate, and operate projects. The CLI, Terraform, Command Center, MCP server, and agent skills sit alongside the IDEs and vibe coding platforms that connect to Appwrite.

### Tools

First-party tools for the terminal, Console, infrastructure, and AI agents. Use them together; they are all part of the same Appwrite toolchain.

- [MCP server](/docs/tooling/ai/mcp-servers): Give AI tools direct access to your Appwrite project and docs.
- [CLI](/docs/tooling/command-line/installation): Create projects, manage resources, and deploy Functions and Sites from your terminal.
- [Agent skills](/docs/tooling/ai/skills): Pre-built Appwrite knowledge for AI agents across supported languages and SDKs.
- [Terraform](/docs/tooling/terraform): Declare Appwrite infrastructure as code and apply it with the official provider.
- [AGENTS.md](/docs/tooling/ai/agents-md): Instruction files that help AI agents understand your project's structure and conventions.
- [Command Center](/docs/tooling/command-center): Search, shortcuts, and AI-assisted navigation in the Appwrite Console.
- [Quickstart prompts](/docs/tooling/ai/quickstart-prompts): Pre-built prompts to quickly generate your first Appwrite project with AI assistants.
- [Appwrite Arena](/docs/tooling/ai/arena): Open-source benchmark evaluating how well AI models understand Appwrite's APIs and SDKs.
- [Appwrite Agent](/docs/products/agent): Chat in the Console to inspect your project, explain issues, and run approved actions.
- [The Appwriter](/docs/tooling/appwriter): A mechanical keyboard designed for the Appwrite Console and its shortcuts.

### IDEs

AI-powered IDEs and code editors provide intelligent code completion and context-aware assistance as you write code. These tools support the Appwrite MCP server, giving AI agents direct access to your Appwrite project and documentation.

- [Claude Code](/docs/tooling/ai/agents/claude-code)

- [Codex](/docs/tooling/ai/agents/codex)

- [Cursor](/docs/tooling/ai/agents/cursor)

- [VS Code](/docs/tooling/ai/agents/vscode)

- [Zed](/docs/tooling/ai/agents/zed)

- [OpenCode](/docs/tooling/ai/agents/opencode)

- [Antigravity](/docs/tooling/ai/agents/antigravity)

- [Grok Build](/docs/tooling/ai/agents/grok-build)

### Vibe coding

Vibe coding platforms let you build applications through natural language. Describe what you want to build and the AI generates the application for you.

- [Claude Desktop](/docs/tooling/ai/vibe-coding/claude-desktop)

- [Lovable](/docs/tooling/ai/vibe-coding/lovable)

- [Emergent](/docs/tooling/ai/vibe-coding/emergent)

- [Bolt](/docs/tooling/ai/vibe-coding/bolt)

- [Zenflow](/docs/tooling/ai/vibe-coding/zenflow)

### Guides

Guides for building AI-powered features on top of Appwrite, from running models in Functions to building full agent pipelines.

- [AI in Functions](/docs/tooling/ai/ai-in-functions): Run AI models inside Appwrite Functions with providers like OpenAI and Anthropic.
- [Vector DB and embeddings](/docs/tooling/ai/vector-db-and-embeddings): Store and query vector embeddings for semantic search and RAG pipelines.
- [Persistent agents](/docs/tooling/ai/persistent-agents-with-realtime): Build stateful AI agents with continuous conversations using Appwrite Realtime.

---

## AGENTS.md

URL: https://appwrite.io/docs/tooling/ai/agents-md

`AGENTS.md` files are instruction files that developers place in their repositories to provide context and guidelines to AI agents. These files help AI tools understand your project structure, coding conventions, and preferences, resulting in more accurate and consistent code suggestions. Most IDEs and agents support this file.

When an AI agent encounters an `AGENTS.md` file, it uses the instructions to tailor its responses to your specific project requirements. This includes details about your tech stack, file organization, naming conventions, and architectural patterns.

### Benefits

- **Project-specific context**: AI agents receive tailored instructions for your exact Appwrite setup, including which SDKs, services, and frameworks you use.
- **Accurate code generation**: With the right context, AI agents generate code that follows your project's patterns and uses the correct SDK methods.
- **No repetitive prompting**: Instead of explaining your setup in every conversation, the `AGENTS.md` file provides this context automatically.
- **Framework-aware**: Generated rules include framework-specific patterns like SSR authentication, server actions, and routing conventions.
- **API references included**: The generated file links directly to the relevant Appwrite API references for your selected services.

### Generate AGENTS.md

Use the [AGENTS.md generator](https://agentsmd.appwrite.io) to create a custom `AGENTS.md` file for your project.

![AGENTS.md generator](/images/docs/ai/agents-md/generator-dark.avif)

#### 1. Select your SDK

Choose the Appwrite SDK you use in your project. The generator supports all Appwrite client and server SDKs.

#### 2. Select your framework

Choose the framework you are building with. The generated rules will include framework-specific patterns and best practices.

#### 3. Select Appwrite features

Check the Appwrite services your project uses, such as Auth, Database, Storage, Functions, Messaging, Sites, and Realtime. The generated rules will only include context for the services you select.

#### 4. Generate and download

Click **Generate Rules** to create your `AGENTS.md` file. You can copy the output or download it directly. Place the file in the root of your repository so AI agents can find it automatically.

---

## Google Antigravity

URL: https://appwrite.io/docs/tooling/ai/agents/antigravity

#### 1. Quick start prompts

Get started quickly with these pre-built prompts for common Appwrite integrations:

- [TanStack Start](/docs/tooling/ai/quickstart-prompts/tanstack-start)
- [Next.js](/docs/tooling/ai/quickstart-prompts/nextjs)

[Browse all quick start prompts](/docs/tooling/ai/quickstart-prompts)

#### 2. Add the MCP server

Connect the Appwrite MCP server to Antigravity for deeper integration with the Appwrite API and documentation. It is a remote HTTP server, so there are no additional pre-requisites to install.

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.

Update the `mcp_config.json` file to include the Appwrite MCP server:

```json
{
  "mcpServers": {
    "appwrite": {
      "serverUrl": "https://mcp.appwrite.io/"
    }
  }
}
```

**Use the serverUrl key**

Antigravity requires the `serverUrl` key for remote MCP servers. Using fields like `url` or `httpUrl` will cause tool registration to fail silently.

The server uses OAuth for authentication. When Antigravity first connects to the server, your browser opens so you can sign in to your Appwrite account and authorize access.

Head back to the Managed MCP Server page and click refresh.

#### 3. Test the integration

Open **Agent Manager** in Antigravity to test your MCP integration. You can try out the following example prompts:

**Example prompts:**

- `Create a new user in my Appwrite project`
- `List all databases in my project`
- `Show me the tables in my database`
- `How do I set up real-time subscriptions in Appwrite?`
- `Show me how to authenticate users with OAuth`
- `How do I implement file uploads with Appwrite Storage?`

![Antigravity Agent chat](/images/docs/mcp/antigravity/agent-chat.avif)

---

## Claude Code

URL: https://appwrite.io/docs/tooling/ai/agents/claude-code

#### 1. Install the Appwrite plugin

The fastest way to get started with Appwrite in Claude Code is to install the [**Appwrite plugin**](https://claude.com/plugins/appwrite) from the official marketplace. The plugin includes agent skills for the CLI and all major SDKs, giving Claude Code the context it needs to work with your Appwrite projects.

To install the plugin, run the following command in your terminal:

```bash
claude plugin install appwrite@claude-plugins-official
```

Once installed, run Claude Code and configure the plugin:

- Run `/plugins` in Claude Code.
- Go to the **Installed** tab.
- Select the **Appwrite** plugin from the list.
- Choose **Configure options**.
- Enter your Appwrite endpoint, project ID, and API key when prompted.

![Configure the Appwrite plugin in Claude Code](/images/docs/mcp/claude-code/configure-plugin.avif)

After saving the configuration, run `/reload-plugins` to apply the changes to your current session. The plugin options configure agent skills. Add the remote Appwrite MCP server in [Step 3](#step-3) so Claude Code can call Appwrite APIs and search the documentation through OAuth.

**Prefer to skip the plugin?**

If you'd rather configure the MCP server on its own, skip to [Step 3](#step-3).

#### 2. Quick start prompts

Get started quickly with these pre-built prompts for common Appwrite integrations:

- [TanStack Start](/docs/tooling/ai/quickstart-prompts/tanstack-start)
- [Next.js](/docs/tooling/ai/quickstart-prompts/nextjs)

[Browse all quick start prompts](/docs/tooling/ai/quickstart-prompts)

#### 3. Add the MCP server

Connect the Appwrite MCP server to Claude Code for deeper integration with the Appwrite API and documentation. It is a remote HTTP server, so there are no additional pre-requisites to install.

Run the following command in your terminal to add the MCP server:

```bash
claude mcp add --transport http appwrite https://mcp.appwrite.io/
```

The server uses OAuth for authentication. Run `/mcp` in Claude Code, select **appwrite**, and choose **Authenticate**. Your browser opens so you can sign in to your Appwrite account and authorize access.

If you run Claude Code on a remote host, log in from the shell with `--no-browser`:

```bash
claude mcp login appwrite --no-browser
```

Open the printed authorization URL in a browser on your local machine, sign in, and authorize access. After you approve, copy the full `http://localhost:.../callback` URL from the address bar and paste it into the Claude Code prompt. Use an interactive SSH session (`ssh -t`) so the prompt can accept the paste. This is the same localhost-callback pattern described in [Authenticate from a remote machine](/docs/tooling/ai/mcp-servers#remote).

#### 4. Verify MCP tools

Run the following command in your terminal (where Claude Code is running).

```bash
/mcp
```

You should see the added MCP server listed there.

![Verify MCP tools](/images/docs/mcp/claude-code/verify-mcp-tools.avif)

#### 5. Test the integration

Try out the following example prompts:

**Example prompts:**

- `Create a new user in my Appwrite project`
- `List all databases in my project`
- `Show me the tables in my database`
- `How do I set up real-time subscriptions in Appwrite?`
- `Show me how to authenticate users with OAuth`
- `How do I implement file uploads with Appwrite Storage?`

![Implement file uploads](/images/docs/mcp/claude-code/implement-file-uploads.avif)

---

## Codex

URL: https://appwrite.io/docs/tooling/ai/agents/codex

#### 1. Install the Appwrite plugin

The fastest way to get started with Appwrite in Codex is to install the **Appwrite plugin** from the Appwrite marketplace. The plugin includes agent skills for the Appwrite CLI and all major SDKs, giving Codex the context it needs to follow the latest Appwrite code patterns.

Add the Appwrite marketplace to Codex by running the following command in your terminal:

```bash
codex plugin marketplace add appwrite/codex-plugin
```

Then run `codex` and open the plugins menu to install the Appwrite plugin:

- Run `/plugins` in Codex.
- Select the **Appwrite** plugin from the marketplace listing.
- Confirm the install.

Once installed, the language and deployment skills load automatically based on your task. Add the remote Appwrite MCP server in [Step 3](#step-3) so Codex can call Appwrite APIs and search the documentation.

**Prefer manual setup?**

If you'd rather configure the MCP server individually, skip to [Step 3](#step-3).

#### 2. Quick start prompts

Get started quickly with these pre-built prompts for common Appwrite integrations:

- [TanStack Start](/docs/tooling/ai/quickstart-prompts/tanstack-start)
- [Next.js](/docs/tooling/ai/quickstart-prompts/nextjs)

[Browse all quick start prompts](/docs/tooling/ai/quickstart-prompts)

#### 3. Add the MCP server

Connect the Appwrite MCP server to Codex for deeper integration with the Appwrite API and documentation. It is a remote HTTP server, so there are no additional pre-requisites to install.

Run the following command in your terminal to add the MCP server:

```bash
codex mcp add appwrite --url https://mcp.appwrite.io/
```

The server uses OAuth for authentication. In case Codex does not automatically prompt you to login, run the following command to sign in to your Appwrite account and authorize access:

```bash
codex mcp login appwrite
```

#### 4. Test the integration

Try out the following example prompts:

**Example prompts:**

- `Create a new user in my Appwrite project`
- `List all databases in my project`
- `Show me the tables in my database`
- `How do I set up real-time subscriptions in Appwrite?`
- `Show me how to authenticate users with OAuth`
- `How do I implement file uploads with Appwrite Storage?`

![Codex listing users via the Appwrite MCP server](/images/docs/mcp/codex/test-integration.avif)

---

## Cursor

URL: https://appwrite.io/docs/tooling/ai/agents/cursor

#### 1. Install the Appwrite plugin

The fastest way to get started with Appwrite in Cursor is to install the **Appwrite plugin** from the Cursor Marketplace. The plugin includes agent skills and commands, giving Cursor's AI agents the context they need to work with your Appwrite projects.

To install the plugin:

1. Visit the [Appwrite plugin](https://cursor.com/marketplace/appwrite) page on the Cursor Marketplace.
2. Sign in with your Cursor account.
3. Click **Add to Cursor**.
4. The plugin will be added to your editor automatically.

Once installed, add the remote Appwrite MCP server in [Step 3](#step-3) so Cursor can call Appwrite APIs and search the documentation.

**Prefer manual setup?**

If you'd rather configure the MCP server individually, skip to [Step 3](#step-3).

#### 2. Quick start prompts

Get started quickly with these pre-built prompts for common Appwrite integrations:

- [TanStack Start](/docs/tooling/ai/quickstart-prompts/tanstack-start)
- [Next.js](/docs/tooling/ai/quickstart-prompts/nextjs)

[Browse all quick start prompts](/docs/tooling/ai/quickstart-prompts)

#### 3. Add the MCP server

Connect the Appwrite MCP server to Cursor for deeper integration with the Appwrite API and documentation. It is a remote HTTP server, so there are no additional pre-requisites to install.

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.

Update the `mcp.json` file to include the Appwrite MCP server:

```json
{
    "mcpServers": {
        "appwrite": {
            "url": "https://mcp.appwrite.io/"
        }
    }
}
```

The server uses OAuth for authentication. After you save the file, click **Needs login** next to the server in the MCP settings. Your browser opens so you can sign in to your Appwrite account and authorize access.

You can also **directly add the MCP server to Cursor** using the following link:

- [Add to Cursor](https://cursor.com/install-mcp?name=appwrite&config=eyJ1cmwiOiJodHRwczovL21jcC5hcHB3cml0ZS5pby8ifQ==)

Once you save the details, Cursor will connect with the MCP server and load all available tools. You may need to restart Cursor if it is unable to start the MCP server.

#### 4. Test the integration

Open Cursor Agent and test your MCP integration. You can try out the following example prompts:

**Example prompts:**

- `Create a new user in my Appwrite project`
- `List all databases in my project`
- `Show me the tables in my database`
- `How do I set up real-time subscriptions in Appwrite?`
- `Show me how to authenticate users with OAuth`
- `How do I implement file uploads with Appwrite Storage?`

![Create a new user in Appwrite project](/images/docs/mcp/cursor/cursor-create-user.avif)

---

## Grok Build

URL: https://appwrite.io/docs/tooling/ai/agents/grok-build

#### 1. Quick start prompts

Get started quickly with these pre-built prompts for common Appwrite integrations:

- [TanStack Start](/docs/tooling/ai/quickstart-prompts/tanstack-start)
- [Next.js](/docs/tooling/ai/quickstart-prompts/nextjs)

[Browse all quick start prompts](/docs/tooling/ai/quickstart-prompts)

#### 2. Add the MCP server

Connect the Appwrite MCP server to Grok Build for deeper integration with the Appwrite API and documentation. It is a remote HTTP server, so there are no additional pre-requisites to install.

Run the following command in your terminal to add the MCP server:

```bash
grok mcp add --transport http appwrite https://mcp.appwrite.io/
```

The server uses OAuth for authentication. When Grok Build first connects to the server, your browser opens so you can sign in to your Appwrite account and authorize access. In case you are not prompted automatically, run `/mcps` in Grok Build, select **appwrite**, and press `i` to authenticate.

You can verify the server is configured by running `grok mcp list` in your terminal or `/mcps` in a Grok Build session.

#### 3. Test the integration

Open a Grok Build session by running `grok` in your terminal and test your MCP integration. You can try out the following example prompts:

**Example prompts:**

- `Create a new user in my Appwrite project`
- `List all databases in my project`
- `Show me the tables in my database`
- `How do I set up real-time subscriptions in Appwrite?`
- `Show me how to authenticate users with OAuth`
- `How do I implement file uploads with Appwrite Storage?`

---

## OpenCode

URL: https://appwrite.io/docs/tooling/ai/agents/opencode

#### 1. Quick start prompts

Get started quickly with these pre-built prompts for common Appwrite integrations:

- [TanStack Start](/docs/tooling/ai/quickstart-prompts/tanstack-start)
- [Next.js](/docs/tooling/ai/quickstart-prompts/nextjs)

[Browse all quick start prompts](/docs/tooling/ai/quickstart-prompts)

#### 2. Add the MCP server

Connect the Appwrite MCP server to OpenCode for deeper integration with the Appwrite API and documentation. It is a remote HTTP server, so there are no additional pre-requisites to install.

Use the following configuration in your `opencode.json` file to use the Appwrite MCP server.

```json
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "appwrite": {
      "type": "remote",
      "enabled": true,
      "url": "https://mcp.appwrite.io/"
    }
  }
}
```

The server uses OAuth for authentication. When OpenCode first connects to the server, complete the sign-in flow in your browser to sign in to your Appwrite account and authorize access.

#### 3. Test the integration

Try out the following example prompts:

**Example prompts:**

- `Create a new user in my Appwrite project`
- `List all databases in my project`
- `Show me the tables in my database`
- `How do I set up real-time subscriptions in Appwrite?`
- `Show me how to authenticate users with OAuth`
- `How do I implement file uploads with Appwrite Storage?`

![OAuth question in OpenCode](/images/docs/mcp/opencode/oauth-question.avif)

---

## VS Code

URL: https://appwrite.io/docs/tooling/ai/agents/vscode

#### 1. Quick start prompts

Get started quickly with these pre-built prompts for common Appwrite integrations:

- [TanStack Start](/docs/tooling/ai/quickstart-prompts/tanstack-start)
- [Next.js](/docs/tooling/ai/quickstart-prompts/nextjs)

[Browse all quick start prompts](/docs/tooling/ai/quickstart-prompts)

#### 2. Add the MCP server

Connect the Appwrite MCP server to VS Code for deeper integration with the Appwrite API and documentation. It is a remote HTTP server, so there are no additional pre-requisites to install.

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.

Update the `mcp.json` file to include the Appwrite MCP server:

```json
{
  "servers": {
    "appwrite": {
      "url": "https://mcp.appwrite.io/",
      "type": "http"
    }
  }
}
```

The server uses OAuth for authentication. When the server starts, VS Code prompts you to authenticate, and your browser opens so you can sign in to your Appwrite account and authorize access.

You can also **directly add the MCP server to VS Code** using the following link:

Once you save the configuration, Copilot Chat will connect with the MCP server and load all available tools.

#### 3. Test the integration

Open **Copilot Chat** in VS Code and switch to **Agent Mode** to test your MCP integration. You can try out the following example prompts:

**Example prompts:**

- `Create a new user in my Appwrite project`
- `List all databases in my project`
- `Show me the tables in my database`
- `How do I set up real-time subscriptions in Appwrite?`
- `Show me how to authenticate users with OAuth`
- `How do I implement file uploads with Appwrite Storage?`

![Search for portfolio site in Appwrite project](/images/docs/mcp/vscode/copilot-chat.avif)

---

## Windsurf

URL: https://appwrite.io/docs/tooling/ai/agents/windsurf

#### 1. Quick start prompts

Get started quickly with these pre-built prompts for common Appwrite integrations:

- [TanStack Start](/docs/tooling/ai/quickstart-prompts/tanstack-start)
- [Next.js](/docs/tooling/ai/quickstart-prompts/nextjs)

[Browse all quick start prompts](/docs/tooling/ai/quickstart-prompts)

#### 2. Add the MCP server

Connect the Appwrite MCP server to Windsurf for deeper integration with the Appwrite API and documentation. It is a remote HTTP server, so there are no additional pre-requisites to install.

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.

![Windsurf add MCP server](/images/docs/mcp/windsurf/windsurf-add-mcp-server.avif)

Update the `mcp_config.json` file to include the Appwrite MCP server:

```json
{
  "mcpServers": {
    "appwrite": {
      "serverUrl": "https://mcp.appwrite.io/"
    }
  }
}
```

The server uses OAuth for authentication. When Windsurf first connects to the server, you are prompted to log in, and your browser opens so you can sign in to your Appwrite account and authorize access.

Once you save the details, head back to the MCP Servers section in the Windsurf Settings and click on the **Refresh** button.

#### 3. Test the integration

Open Cascade chat in the Windsurf Editor and test your MCP integration. You can try out the following example prompts:

**Example prompts:**

- `Create a new user in my Appwrite project`
- `List all databases in my project`
- `Show me the tables in my database`
- `How do I set up real-time subscriptions in Appwrite?`
- `Show me how to authenticate users with OAuth`
- `How do I implement file uploads with Appwrite Storage?`

![Windsurf Cascade chat](/images/docs/mcp/windsurf/windsurf-cascade-chat.avif)

---

## Zed

URL: https://appwrite.io/docs/tooling/ai/agents/zed

#### 1. Quick start prompts

Get started quickly with these pre-built prompts for common Appwrite integrations:

- [TanStack Start](/docs/tooling/ai/quickstart-prompts/tanstack-start)
- [Next.js](/docs/tooling/ai/quickstart-prompts/nextjs)

[Browse all quick start prompts](/docs/tooling/ai/quickstart-prompts)

#### 2. Add the MCP server

Connect the Appwrite MCP server to Zed for deeper integration with the Appwrite API and documentation. It is a remote HTTP server, so there are no additional pre-requisites to install.

In Zed, open the **Command Palette** (press `CMD + Shift + P` on MacOS or `CTRL + Shift + P` on Linux) and run the `agent: add context server` action.

In the **Remote** tab, update the JSON configuration to include the Appwrite MCP server:

```json
{
  "appwrite": {
    "url": "https://mcp.appwrite.io/"
  }
}
```

The server uses OAuth for authentication. When Zed first connects to the server, your browser opens so you can sign in to your Appwrite account and authorize access.

Click **Add Server** after adding the configuration. You can also review the configured server in the **Model Context Protocol (MCP) Servers** section of Zed's Agent settings.

#### 3. Install Appwrite skills

Install Appwrite skills to give Zed's agent SDK-specific knowledge about Appwrite.

Run the following command in your project directory:

```bash
npx skills add appwrite/skills
```

When prompted:

1. Select the Appwrite skills that match the SDKs you use in your project.
2. Select **Zed** as one of the AI tools that should use the installed skills.
3. Choose whether to install the skills at the project level or globally.
4. Select **symlink** as the installation method.

Zed loads skills from `~/.agents/skills/` for global installs and `<worktree>/.agents/skills/` for project-local installs. Each skill must be a direct child of the `skills` folder and include a `SKILL.md` file.

#### 4. Test the integration

Open the Agent panel in Zed and test your MCP integration. You can try out the following example prompts:

**Example prompts:**

- `Create a new user in my Appwrite project`
- `List all databases in my project`
- `Show me the tables in my database`
- `How do I set up real-time subscriptions in Appwrite?`
- `Show me how to authenticate users with OAuth`
- `How do I implement file uploads with Appwrite Storage?`

![Zed Agent using the Appwrite MCP server](/images/docs/mcp/zed/zed-agent-chat.avif)

---

## AI in Functions

URL: https://appwrite.io/docs/tooling/ai/ai-in-functions

Appwrite Functions let you run AI workloads on the server side, keeping API keys secure and giving you full control over how your application interacts with AI providers. Using the [Vercel AI SDK](https://sdk.vercel.ai), you can integrate with providers like OpenAI, Anthropic, Google, and others through a unified interface.

This guide shows how to build an Appwrite Function that generates text using the Vercel AI SDK with OpenAI.

**Streaming not yet supported**

Appwrite Functions do not currently support streaming responses. Support for streaming is coming soon. For now, use `generateText` to return complete responses.

### Prerequisites

- An Appwrite project
- An [OpenAI API key](https://platform.openai.com/account/api-keys)

#### 1. 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.

![Create function screen](/images/docs/functions/template.avif)

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). 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.

#### 2. Add dependencies

Once the function is created, navigate to the freshly created repository and clone it to your local machine.

Install the `ai` package and the OpenAI provider:

```bash
npm install ai @ai-sdk/openai
```

The `ai` package is the core Vercel AI SDK, and `@ai-sdk/openai` is the provider that connects it to OpenAI's API.

#### 3. Create the function

Replace the contents of `src/main.js` with the following code:

```js
import { generateText } from "ai";
import { openai } from "@ai-sdk/openai";

export default async ({ req, res, log, error }) => {
  if (req.path === "/api/generate") {
    if (req.method !== "POST") {
      return res.status(405).json({ error: "Method not allowed" });
    }

    const { prompt } = req.bodyJson;
    const result = await generateText({
      model: openai("gpt-5-mini"),
      prompt,
    });

    return res.json({ text: result.text });
  }

  return res.status(404).json({ error: "Not found" });
};
```

The function exposes a `POST /api/generate` endpoint. It extracts the prompt from the request body, passes it to OpenAI using `generateText`, and returns the generated text as JSON.

The OpenAI provider reads the `OPENAI_API_KEY` environment variable automatically. Make sure you have set this variable in your function settings in the Appwrite Console.

#### 4. Using other providers

The Vercel AI SDK supports many providers beyond OpenAI. You can swap providers by installing the relevant package and changing the model import.

For example, to use Anthropic:

```bash
npm install @ai-sdk/anthropic
```

```js
import { anthropic } from '@ai-sdk/anthropic';

const result = await generateText({
    model: anthropic('claude-sonnet-4-5-20250929'),
    prompt,
});
```

Or to use Google's Gemini:

```bash
npm install @ai-sdk/google
```

```js
import { google } from '@ai-sdk/google';

const result = await generateText({
    model: google('gemini-2.0-flash'),
    prompt,
});
```

Add the corresponding API key as an environment variable in your function settings for each provider.

#### 5. Test the function

Now that the function is deployed, test it by sending a `POST` request to the function's URL:

```bash
curl -X POST https://FUNCTION_DOMAIN/api/generate \
  -H "Content-Type: application/json" \
  -d '{"prompt": "Explain quantum computing in one sentence."}'
```

You should receive a JSON response with the generated text:

```json
{
  "text": "Quantum computing uses quantum mechanical phenomena..."
}
```

---

## Appwrite Arena

URL: https://appwrite.io/docs/tooling/ai/arena

[Appwrite Arena](https://arena.appwrite.io) is an open-source benchmark that evaluates how well AI models understand Appwrite. It tests models across real-world Appwrite usage scenarios, covering services, SDKs, and APIs, to help you choose the best model for building with Appwrite.

Arena ranks models by their ability to answer questions drawn from actual Appwrite platform usage, both with and without access to Appwrite [skills](/docs/tooling/skills). This makes it easy to see which models generate the most accurate Appwrite code out of the box and which benefit most from added context.

All questions, answers, and scores are fully open source and available on [GitHub](https://github.com/appwrite/arena).

### How it works

Arena evaluates each model using a pool of **191 questions** spanning **9 Appwrite service categories**:

- Foundation
- Auth
- Databases
- Functions
- Storage
- Sites
- Messaging
- Realtime
- CLI

Each model is tested in two contexts:

- **Without Skills**: The model answers using only its built-in training data.
- **With Skills**: The model answers with access to Appwrite's [skills files](/docs/tooling/skills), which provide up-to-date SDK and API context.

The gap between these two scores reveals how effectively a model can leverage provided documentation to improve its responses.

All benchmark runs are configured with:

- **Temperature 0**: Makes the entire benchmark deterministic, so identical inputs always produce identical outputs.
- **Extended thinking set to high**: Matches the configuration most commonly used in real-world programming contexts.

### Scoring

Arena uses two complementary scoring methods to evaluate model performance:

#### Deterministic (MCQ)

165 multiple-choice questions structured like a typical exam, each with a single correct answer:

- The model receives a system prompt and the question.
- Four tools are provided, one for each possible answer.
- The model submits its response by calling the correct tool, avoiding issues with verbose or unparseable output.

Scores are fully reproducible with no judge bias.

#### AI-judged (open-ended)

26 open-ended questions scored from 0 to 1 by an AI judge using rubrics and reference answers:

- Tests reasoning and real-world usage patterns that multiple-choice cannot capture.
- Scores may have slight variance due to the nature of AI-based evaluation.

### Use cases

- **Choosing a model**: Compare models to find the one that best fits your Appwrite development workflow and budget.
- **Cost vs. performance**: Determine whether a top-tier model justifies its price for your project, or whether a cheaper or faster model gets you close enough.
- **Measuring skill impact**: See how much a model improves when given Appwrite skills, helping you decide whether to install skills for your AI tools.
- **Comparing response duration**: Models with similar token throughput can have very different benchmark durations. A slower run often indicates the model is spending more tokens to reach the same answer, resulting in a slower development experience.
- **Staying up to date**: Arena is rerun as new models and updates are released, serving as a living reference you can return to whenever you need to re-evaluate your choice.
- **Contributing**: Since Arena is open source, you can submit new questions, suggest improvements to scoring rubrics, or add new models to the benchmark.

---

## Appwrite Agent

URL: https://appwrite.io/docs/tooling/ai/assistant

The Console **Appwrite Assistant** has been replaced by **Appwrite Agent**.

Appwrite Agent is the AI chat built into Appwrite Cloud. It inspects your project context, answers how-to questions, clarifies ambiguous requests, and can run approved actions through Appwrite MCP.

[Appwrite Agent documentation](/docs/products/agent)

**Redirect**

Bookmarks to this page should use [Appwrite Agent](/docs/products/agent). Legacy `/docs/tooling/ai/assistant` URLs redirect there.

---

## Docs as Markdown

URL: https://appwrite.io/docs/tooling/ai/docs-as-markdown

Appwrite documentation is available as Markdown, making it easy to use with AI-powered development tools, code editors, and LLMs.

Markdown lets AI tools process more content within their context limits and focus on the documentation itself instead of parsing HTML. This leads to more accurate responses based on official documentation.

### Copy as Markdown

Every page in the Appwrite documentation includes a **Copy page** button that copies the entire page content as Markdown to your clipboard.

This is useful when you want to:

- Paste documentation into an AI chat for context
- Include documentation in your project's reference files

![AI-ready prompts in quick start guides](/images/docs/ai/docs-as-markdown/copy-page-light.avif)

### AI-ready prompts

Some pages, like the quick start guides, include prompts designed for AI agents. These pages have options to open the prompt directly in AI tools like Cursor or copy it to your clipboard.

![AI-ready prompts in quick start guides](/images/docs/ai/docs-as-markdown/quickstart-light.avif)

### Markdown URLs

You can access any documentation page as raw Markdown by appending `.md` to the URL. Here are some examples:

- `https://appwrite.io/docs/apis/graphql` → `https://appwrite.io/docs/apis/graphql.md`
- `https://appwrite.io/docs/products/auth` → `https://appwrite.io/docs/products/auth.md`
- `https://appwrite.io/docs/quick-starts/nextjs` → `https://appwrite.io/docs/quick-starts/nextjs.md`

This allows AI tools and scripts to fetch documentation directly without parsing HTML.

---

## Appwrite MCP server

URL: https://appwrite.io/docs/tooling/ai/mcp-servers

Appwrite offers a [Model Context Protocol](https://modelcontextprotocol.io) (MCP) server that allows LLMs to directly interact with Appwrite's API and docs. Using the MCP server, you can use applications such as Claude Code, Codex, Cursor, Claude Desktop, and others 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.

### Why use the Appwrite MCP server?

Some **popular use cases** for the Appwrite MCP server 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.

The server also provides:

- **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
- **Documentation search**: Lets LLMs semantically search the Appwrite documentation for accurate, up-to-date context
- **Automatic service discovery**: All supported Appwrite services are automatically registered, no configuration needed
- **No local setup**: Runs as a hosted HTTP service, so there is nothing to install or keep updated

### Connection details

The Appwrite MCP server is a remote server that uses the HTTP transport. It is available at the following URL:

```
https://mcp.appwrite.io/
```

The server uses OAuth for authentication. When you add the server to an AI tool and connect for the first time, your browser opens so you can sign in to your Appwrite account and authorize access. You don't need to create or manage API keys.

MCP clients complete that OAuth flow with a localhost callback URL. If you run your AI tool on a remote host, follow [Authenticate from a remote machine](#remote).

### Pre-requisites

Before connecting to the MCP server, you must [set up an Appwrite project](https://cloud.appwrite.io) on Appwrite Cloud. No additional software needs to be installed on your system.

### Installation

You can add the MCP server to various AI tools and code editors:

- [Claude Code](/docs/tooling/ai/agents/claude-code)

- [Codex](/docs/tooling/ai/agents/codex)

- [Cursor](/docs/tooling/ai/agents/cursor)

- [Claude Desktop](/docs/tooling/ai/vibe-coding/claude-desktop)

- [Zenflow](/docs/tooling/ai/vibe-coding/zenflow)

- [VS Code](/docs/tooling/ai/agents/vscode)

- [Zed](/docs/tooling/ai/agents/zed)

- [OpenCode](/docs/tooling/ai/agents/opencode)

- [Google Antigravity](/docs/tooling/ai/agents/antigravity)

- [Grok Build](/docs/tooling/ai/agents/grok-build)

**Want to integrate your AI tool?**

If you are building an AI code-generation tool that can integrate with MCP servers and would like to collaborate, join our [Technology Partners](https://appwrite.io/integrations/technology-partner) program.

#### How it works

The MCP server starts in a compact workflow where only a small set of MCP tools is exposed to the model:

- `appwrite_get_context` - Returns a summary of your workspace, including your account, organization, and projects
- `appwrite_search_tools` - Searches the full Appwrite tool catalog at runtime
- `appwrite_call_tool` - Calls a specific Appwrite tool by name
- `appwrite_search_docs` - Semantically searches the Appwrite documentation

The full Appwrite tool catalog stays internal and is searched at runtime, using less of the model's context.

### Authenticate from a remote machine

MCP clients listen on a localhost callback URL for the OAuth redirect. When the client runs over SSH, on a droplet, or on any host without a local browser, that callback stays on the remote machine's loopback.

Log in from the [Claude Code](/docs/tooling/ai/agents/claude-code) shell with `--no-browser`:

```bash
claude mcp login appwrite --no-browser
```

The command prints an authorization URL. Open it in a browser on your local machine, sign in to Appwrite, and authorize access. After you approve, the browser navigates to a `http://localhost:.../callback` URL. Copy the full URL from the address bar and paste it into the Claude Code prompt.

### Self-hosted Appwrite

The hosted MCP server authenticates against Appwrite Cloud. If you are running a [self-hosted Appwrite instance](/docs/advanced/self-hosting), use the local MCP server instead. It runs on your machine over the stdio transport and connects to your instance with an API key. Follow the [self-hosted MCP server documentation](/docs/advanced/self-hosting/mcp) to set it up.

### 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.avif)

#### 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.avif)

#### 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.avif)

#### Example 4: Search the documentation

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.avif)

---

## Persistent Agents with Realtime

URL: https://appwrite.io/docs/tooling/ai/persistent-agents-with-realtime

AI agents that maintain conversation history across sessions provide more contextual and personalized responses. By storing LLM responses in Appwrite Databases and subscribing to changes through Realtime, you can build chat applications where multiple clients receive updates instantly.

#### Architecture

1. **Store messages**: Save user messages and LLM responses in an Appwrite table
2. **Subscribe to changes**: Use Realtime to listen for new messages
3. **Maintain context**: Load conversation history to provide context to the LLM

#### Set up the messages table

Create a table to store conversation messages with the following columns:

| Key | Type | Description |
| --- | --- | --- |
| `conversationId` | varchar | Groups messages by conversation |
| `role` | varchar | Either "user" or "assistant" |
| `content` | longtext | The message content |
| `$createdAt` | automatic | Timestamp for ordering |

#### Store messages in the database

When a user sends a message or the LLM responds, save it to the database:

```js
import { Client, TablesDB, ID } from 'node-appwrite';

const client = new Client()
  .setEndpoint(process.env.APPWRITE_ENDPOINT ?? 'https://<REGION>.cloud.appwrite.io/v1')
  .setProject(process.env.APPWRITE_FUNCTION_PROJECT_ID)
  .setKey(process.env.APPWRITE_API_KEY);

const tablesDB = new TablesDB(client);

// Save user message
await tablesDB.createRow({
  databaseId: process.env.DATABASE_ID,
  tableId: process.env.MESSAGES_TABLE_ID,
  rowId: ID.unique(),
  data: {
    conversationId: conversationId,
    role: 'user',
    content: userMessage,
  }
});

// Generate LLM response
const response = await generateLLMResponse(userMessage, conversationHistory);

// Save assistant message
await tablesDB.createRow({
  databaseId: process.env.DATABASE_ID,
  tableId: process.env.MESSAGES_TABLE_ID,
  rowId: ID.unique(),
  data: {
    conversationId: conversationId,
    role: 'assistant',
    content: response,
  }
});
```

#### Subscribe to messages with Realtime

On the client side, subscribe to the messages table to receive updates in real time. Use the `Channel` helper to build type-safe channel subscriptions and realtime queries to filter messages by conversation server-side.

```js
import { Client, Realtime, Channel, Query } from 'appwrite';

const client = new Client()
  .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
  .setProject('<PROJECT_ID>');

const realtime = new Realtime(client);

// Subscribe to new messages for the current conversation
const subscription = await realtime.subscribe(
  Channel.tablesdb('<DATABASE_ID>').table('<MESSAGES_TABLE_ID>').row().create(),
  (response) => {
    displayMessage(response.payload);
  },
  [Query.equal('conversationId', [currentConversationId])]
);
```

The `Channel` helper provides a fluent API for building channel strings, replacing manual string concatenation. The `.create()` event filter ensures the callback only fires for new messages, not updates or deletes.

By passing a `Query.equal()` filter, messages are filtered server-side so the callback only receives messages for the current conversation. This removes the need for manual filtering in your callback and reduces unnecessary processing on the client.

#### Load conversation history for context

Before generating an LLM response, load recent messages to provide context:

```js
import { Query } from 'node-appwrite';

const { rows } = await tablesDB.listRows({
  databaseId: process.env.DATABASE_ID,
  tableId: process.env.MESSAGES_TABLE_ID,
  queries: [
    Query.equal('conversationId', conversationId),
    Query.orderAsc('$createdAt'),
    Query.limit(10),
  ]
});

const conversationHistory = rows.map((row) => ({
  role: row.role,
  content: row.content,
}));
```

#### Generate responses with context

Pass the conversation history to the LLM:

```js
const response = await fetch('https://api.openai.com/v1/chat/completions', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
  },
  body: JSON.stringify({
    model: 'gpt-4',
    messages: conversationHistory,
  }),
});

const data = await response.json();
const assistantMessage = data.choices[0].message.content;
```

#### Benefits

- **Persistence**: Conversations survive page refreshes and app restarts
- **Multi-device sync**: Users can continue conversations on different devices
- **Real-time updates**: Multiple users or clients see messages instantly
- **Audit trail**: All messages are stored and can be reviewed later

---

## Quick start prompts

URL: https://appwrite.io/docs/tooling/ai/quickstart-prompts

**Quick start prompts** are pre-built instructions designed to help AI assistants integrate Appwrite into your project. These prompts guide AI tools like Claude Code, Codex, Cursor, and others through the process of setting up authentication, databases, and other Appwrite services in your application.

Quick start prompts offer several advantages when building with Appwrite:

- **Faster setup**: Skip the manual configuration and let AI handle the boilerplate code and SDK integration.
- **Best practices**: Prompts are crafted to follow Appwrite's recommended patterns and conventions.
- **Interactive guidance**: AI assistants will ask for necessary details like your project ID and region rather than making assumptions.

### How to use quick start prompts

1. Open your preferred AI assistant or code editor with AI capabilities (such as Claude Code, Codex, or Cursor) in an existing project or an empty directory.
2. Copy the quickstart prompt for your chosen framework.
3. Paste the prompt and let the AI guide you through the setup process.
4. When prompted, provide your Appwrite project details (region and project ID) from the Appwrite Console.

**MCP server**

For the best experience, consider using Appwrite's [MCP server](/docs/tooling/ai/mcp-servers) alongside quick start prompts. The MCP server allows AI assistants to directly interact with your Appwrite project and access up-to-date documentation.

### Available quick start prompts

Use the following quick start prompts with your preferred AI assistant to integrate Appwrite into your project:

#### Web

- [TanStack Start](/docs/tooling/ai/quickstart-prompts/tanstack-start)

- [Next.js](/docs/tooling/ai/quickstart-prompts/nextjs)

- [React](/docs/tooling/ai/quickstart-prompts/react)

- [Vue](/docs/tooling/ai/quickstart-prompts/vue)

- [SvelteKit](/docs/tooling/ai/quickstart-prompts/sveltekit)

- [Angular](/docs/tooling/ai/quickstart-prompts/angular)

- [Nuxt](/docs/tooling/ai/quickstart-prompts/nuxt)

- [Solid](/docs/tooling/ai/quickstart-prompts/solid)

- [Refine](/docs/tooling/ai/quickstart-prompts/refine)

- [Web](/docs/tooling/ai/quickstart-prompts/web)

#### Mobile

- [React Native](/docs/tooling/ai/quickstart-prompts/react-native)

- [Flutter](/docs/tooling/ai/quickstart-prompts/flutter)

- [Android (Kotlin)](/docs/tooling/ai/quickstart-prompts/android-kotlin)

- [Android (Java)](/docs/tooling/ai/quickstart-prompts/android-java)

- [Apple (Swift)](/docs/tooling/ai/quickstart-prompts/apple)

#### Server

- [Node.js](/docs/tooling/ai/quickstart-prompts/node)

- [Python](/docs/tooling/ai/quickstart-prompts/python)

- [PHP](/docs/tooling/ai/quickstart-prompts/php)

- [Dart](/docs/tooling/ai/quickstart-prompts/dart)

- [Ruby](/docs/tooling/ai/quickstart-prompts/ruby)

- [Deno](/docs/tooling/ai/quickstart-prompts/deno)

- [Go](/docs/tooling/ai/quickstart-prompts/go)

- [.NET](/docs/tooling/ai/quickstart-prompts/dotnet)

- [Swift](/docs/tooling/ai/quickstart-prompts/swift)

- [Kotlin](/docs/tooling/ai/quickstart-prompts/kotlin)

- [Rust](/docs/tooling/ai/quickstart-prompts/rust)

---

## Android (Java)

URL: https://appwrite.io/docs/tooling/ai/quickstart-prompts/android-java



---

## Android (Kotlin)

URL: https://appwrite.io/docs/tooling/ai/quickstart-prompts/android-kotlin



---

## Angular

URL: https://appwrite.io/docs/tooling/ai/quickstart-prompts/angular



---

## Apple (Swift)

URL: https://appwrite.io/docs/tooling/ai/quickstart-prompts/apple



---

## Dart

URL: https://appwrite.io/docs/tooling/ai/quickstart-prompts/dart



---

## Deno

URL: https://appwrite.io/docs/tooling/ai/quickstart-prompts/deno



---

## .NET

URL: https://appwrite.io/docs/tooling/ai/quickstart-prompts/dotnet



---

## Flutter

URL: https://appwrite.io/docs/tooling/ai/quickstart-prompts/flutter



---

## Go

URL: https://appwrite.io/docs/tooling/ai/quickstart-prompts/go



---

## Kotlin

URL: https://appwrite.io/docs/tooling/ai/quickstart-prompts/kotlin



---

## Next.js

URL: https://appwrite.io/docs/tooling/ai/quickstart-prompts/nextjs



---

## Node.js

URL: https://appwrite.io/docs/tooling/ai/quickstart-prompts/node



---

## Nuxt

URL: https://appwrite.io/docs/tooling/ai/quickstart-prompts/nuxt



---

## PHP

URL: https://appwrite.io/docs/tooling/ai/quickstart-prompts/php



---

## Python

URL: https://appwrite.io/docs/tooling/ai/quickstart-prompts/python



---

## React

URL: https://appwrite.io/docs/tooling/ai/quickstart-prompts/react



---

## React Native

URL: https://appwrite.io/docs/tooling/ai/quickstart-prompts/react-native



---

## Refine

URL: https://appwrite.io/docs/tooling/ai/quickstart-prompts/refine



---

## Ruby

URL: https://appwrite.io/docs/tooling/ai/quickstart-prompts/ruby



---

## Rust

URL: https://appwrite.io/docs/tooling/ai/quickstart-prompts/rust



---

## Solid

URL: https://appwrite.io/docs/tooling/ai/quickstart-prompts/solid



---

## SvelteKit

URL: https://appwrite.io/docs/tooling/ai/quickstart-prompts/sveltekit



---

## Swift

URL: https://appwrite.io/docs/tooling/ai/quickstart-prompts/swift



---

## TanStack Start

URL: https://appwrite.io/docs/tooling/ai/quickstart-prompts/tanstack-start



---

## Vue

URL: https://appwrite.io/docs/tooling/ai/quickstart-prompts/vue



---

## Web

URL: https://appwrite.io/docs/tooling/ai/quickstart-prompts/web



---

## Responsible AI

URL: https://appwrite.io/docs/tooling/ai/responsible-ai

Building AI-powered applications comes with responsibility toward your users and their data. Whether you're using AI development tools to build with Appwrite or integrating AI capabilities into your applications, following these best practices helps you build trustworthy and secure experiences.

### Protect user data

When sending data to AI providers like OpenAI, Anthropic, or others, be mindful of what information leaves your application.

- **Avoid sending personal data** to AI providers unless necessary for the feature. Strip personally identifiable information (PII) like names, emails, and addresses from prompts before sending them to an LLM.
- **Review provider data policies** to understand how each AI provider handles the data you send. Some providers use input data for model training unless you opt out.
- **Use Appwrite permissions** to control which users and roles can trigger AI-powered features. Appwrite's [permission system](/docs/advanced/security/permissions) lets you restrict access at the database, storage, and function level.

### Secure your API keys

AI provider API keys grant access to paid services and should be treated with the same care as any other secret.

- **Store API keys as environment variables** in your Appwrite Functions. Never hardcode keys in your source code or expose them to the client side.
- **Use scoped keys** when your AI provider supports them. Restrict keys to only the permissions and models your application needs.
- **Rotate keys regularly** and revoke any keys that may have been exposed.

Learn more about managing secrets in [Appwrite Functions](/docs/products/functions/develop#environment-variables).

### Validate inputs and outputs

AI models can produce unexpected or inappropriate results. Build safeguards into your application to handle these cases.

- **Validate user inputs** before sending them to an AI provider. Set character limits, sanitize content, and reject malicious prompts to prevent prompt injection attacks.
- **Review AI outputs** before displaying them to users or storing them in your database. Implement content filters or moderation layers for user-facing features.
- **Handle errors gracefully** when AI providers are unavailable or return unexpected responses. Your application should function even when AI features fail.

### Be transparent with users

Users should understand when they are interacting with AI-generated content or AI-powered features.

- **Disclose AI usage** in your application. Let users know when content is generated by an AI model or when their input is processed by an AI service.
- **Provide opt-out options** where possible. Give users control over whether their data is used in AI-powered features.
- **Set expectations** about AI limitations. AI-generated content can be inaccurate, and users should understand that responses may not always be correct.

### AI-assisted development

When using AI development tools like [Cursor, VS Code, or Claude Code](/docs/tooling/ai/) to build with Appwrite, keep the following in mind.

- **Review generated code** before committing. AI-generated code may contain security vulnerabilities, incorrect API usage, or outdated patterns.
- **Keep API keys out of prompts** when chatting with AI assistants. Avoid pasting secrets, credentials, or sensitive configuration into AI chat interfaces.
- **Use official documentation** as the source of truth. Point your AI tools to Appwrite's [Markdown documentation](/docs/tooling/ai/docs-as-markdown) for accurate and up-to-date context.

---

## Agent skills

URL: https://appwrite.io/docs/tooling/ai/skills

Skills are open-source Markdown files that give AI agents deep knowledge of Appwrite SDKs and services. When installed, skills provide your AI tools with accurate, language-specific context about Appwrite APIs, so they generate correct code without needing to look up documentation. You can find all Appwrite skills on [GitHub](https://github.com/appwrite/skills/).

Skills work across all major AI dev tools that support them. They are installed per-project or globally, and are available for all Appwrite client and server SDKs. Supported tools include but not limited to:

- Claude Code
- Codex
- Cursor
- Google Antigravity
- OpenCode
- Zed
- *and more...*

### Benefits

- **Accurate code generation**: Skills provide AI agents with correct SDK usage patterns, method signatures, and best practices for your chosen language.
- **No manual context needed**: Instead of pasting documentation into prompts, skills give agents the context they need automatically.
- **Language-specific**: Each skill is tailored to a specific SDK, so agents generate idiomatic code for your language and framework.
- **Always up to date**: Skills are maintained alongside the SDKs, so agents always have access to the latest APIs and patterns.
- **Works with any compatible agent**: Skills are not locked to a single tool. Install once and use across any AI agent that supports them.

### Install skills

**Automatic installation**

When you run `appwrite init project`, the Appwrite CLI auto-detects your project configuration and installs relevant skills automatically. You can also install skills manually using the steps below.

#### 1. Run the install command

Run the following command in your project directory:

```bash
npx skills add appwrite/skills
```

#### 2. Select skills

You will be prompted to select which skills to install. Skills are available for the Appwrite CLI and the following SDKs:

- TypeScript
- Dart
- .NET
- Go
- Kotlin
- PHP
- Python
- Ruby
- Swift

Select the skills that match the SDKs you use in your project.

![Skill selection prompt](/images/docs/skills/skills.avif)

#### 3. Select tools

Choose which AI tools should use the installed skills. This configures the skills for the agents you work with.

![Tool selection prompt](/images/docs/skills/tools.avif)

#### 4. Select scope

Choose whether to install the skills at the **project** level or **globally**:

- **Project**: Skills are available only in the current project. This is useful when different projects use different Appwrite SDKs.
- **Global**: Skills are available across all your projects. This is useful if you use the same SDKs everywhere.

![Scope selection prompt](/images/docs/skills/scope.avif)

#### 5. Choose installation method

Select **symlink** as the installation method. This creates a symbolic link to the skills, so they stay in sync across all your AI tools.

![Installation method prompt](/images/docs/skills/method.avif)

### Try it

Skills teach your agent how to **write** Appwrite code. They do not call your project APIs live. For actions like listing users or creating buckets, use the [MCP server](/docs/tooling/ai/mcp-servers) instead.

After installing, ask your agent something like:

- Implement email/password sign-in with the Appwrite SDK
- Review my Appwrite auth implementation against best practices
- Add Realtime updates to my dashboard with the Appwrite SDK

---

## Vector DB and embeddings

URL: https://appwrite.io/docs/tooling/ai/vector-db-and-embeddings

An embedding is a list of numbers that represents the meaning of a piece of text. Text with similar meaning produces vectors that sit close together, so you can rank content by meaning instead of by matching words. This is what powers semantic search, recommendations, and retrieval for AI applications.

Appwrite VectorsDB stores those vectors and searches them. It generates embeddings with built-in models, keeps each vector next to the content it came from, and returns results ordered by distance. You do not run a separate embedding service or a separate vector database.

In this guide you build a support center where a reader describes a problem in their own words and gets the article that answers it, even when the two share no words at all.

### Prerequisites

- An Appwrite project
- An [API key](/docs/partners/project/api-keys) with the `vectorsdb.write`, `vectorsdb.collections.write`, `vectorsdb.indexes.write`, `vectorsdb.documents.read`, `vectorsdb.documents.write`, and `embeddings.write` scopes
- Node.js 22 or later

#### 1. Create a database

In the Appwrite Console, open **Databases** and click **Create database**. Select **VectorsDB** as the database type, name it `Semantic search`, and create it.

![Create a VectorsDB database](/images/docs/ai/vector-db/create-database-light.avif)

#### 2. Create a collection

Open the database and click **Create collection**. Name it `Articles` and pick an embedding model.

The model decides how many components each vector carries, and a collection stores vectors of one fixed width. `nomic-embed-text` produces 768 components, so the collection accepts 768-component vectors and nothing else. Choose the model you plan to embed with, since changing it later means creating a new collection.

![Create a collection with an embedding model](/images/docs/ai/vector-db/create-collection-light.avif)

Every collection arrives with two fields. `embeddings` holds the vector, and `metadata` holds a JSON object where you keep the content the vector was built from.

#### 3. Add a similarity index

Searches run against an index on the `embeddings` field. Create an HNSW index, an approximate nearest neighbour structure that keeps similarity search fast as the collection grows.

The index type decides how similarity is measured, and it has to match the query you plan to run. Use `hnsw_cosine` with `Query.vectorCosine`, `hnsw_dot` with `Query.vectorDot`, and `hnsw_euclidean` with `Query.vectorEuclidean`. Cosine is the common choice for text, since it compares direction and ignores magnitude.

```js
import { Client, VectorsDB, VectorsDBIndexType } from 'node-appwrite';

const client = new Client()
  .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
  .setProject('<PROJECT_ID>')
  .setKey('<API_KEY>');

const vectorsDB = new VectorsDB(client);

await vectorsDB.createIndex({
  databaseId: '<DATABASE_ID>',
  collectionId: '<COLLECTION_ID>',
  key: 'embeddings_cosine',
  type: VectorsDBIndexType.HnswCosine,
  attributes: ['embeddings']
});
```

#### 4. Create the app

Create a TanStack Start app and install the Appwrite SDK.

```bash
npm create @tanstack/start@latest orbit-support
cd orbit-support
npm install node-appwrite
```

Add the project details to `.env`. The API key is a server credential, so it stays out of any file the browser loads.

```bash
APPWRITE_ENDPOINT=https://<REGION>.cloud.appwrite.io/v1
APPWRITE_PROJECT_ID=<PROJECT_ID>
APPWRITE_API_KEY=<API_KEY>
APPWRITE_DATABASE_ID=<DATABASE_ID>
APPWRITE_COLLECTION_ID=<COLLECTION_ID>
```

#### 5. Generate embeddings

Create `src/lib/search.server.ts`. The `.server.ts` suffix keeps this module, and the API key it reads, on the server.

`createTextEmbeddings` accepts an array of strings and returns one vector per string, so a batch of articles costs a single call.

```ts
import { Client, Embeddings, ID, Query, VectorsDB } from 'node-appwrite';

const client = new Client()
  .setEndpoint(process.env.APPWRITE_ENDPOINT)
  .setProject(process.env.APPWRITE_PROJECT_ID)
  .setKey(process.env.APPWRITE_API_KEY);

const embeddings = new Embeddings(client);
const vectorsDB = new VectorsDB(client);

export async function embed(texts: string[]): Promise<number[][]> {
  const result = await embeddings.createTextEmbeddings({
    texts,
    model: 'nomic-embed-text'
  });

  return result.embeddings.map((entry) => entry.embedding);
}
```

#### 6. Store articles as vectors

Each article becomes one document: the vector in `embeddings`, and the text it came from in `metadata`. Storing the text alongside the vector means a search result carries everything you need to render it, with no second lookup.

Embed the title and body together so the vector represents the whole article.

```ts
export async function addArticle(title: string, body: string): Promise<void> {
  const [embedding] = await embed([`${title}. ${body}`]);

  await vectorsDB.createDocument({
    databaseId: process.env.APPWRITE_DATABASE_ID,
    collectionId: process.env.APPWRITE_COLLECTION_ID,
    documentId: ID.unique(),
    data: {
      embeddings: embedding,
      metadata: { title, body }
    }
  });
}
```

Call it once for each article you want to make searchable. The collection view in the Console shows the stored vector next to its metadata.

![Articles stored as vectors with metadata](/images/docs/ai/vector-db/collection-documents-light.avif)

#### 7. Search by meaning

Searching takes two steps: embed the question with the same model used for the articles, then pass that vector to `listDocuments` as a similarity query.

Each result carries a `$distance`, the cosine distance between the question and the article. Lower means closer in meaning, so results arrive in ascending order and `0` would be an exact match.

```ts
export async function searchArticles(question: string, limit = 5) {
  const [vector] = await embed([question]);

  const result = await vectorsDB.listDocuments({
    databaseId: process.env.APPWRITE_DATABASE_ID,
    collectionId: process.env.APPWRITE_COLLECTION_ID,
    queries: [Query.vectorCosine('embeddings', vector), Query.limit(limit)]
  });

  return result.documents.map((document) => ({
    id: document.$id,
    title: document.metadata.title,
    body: document.metadata.body,
    distance: document.$distance
  }));
}
```

A question and an article have to be embedded by the same model to be comparable. Vectors from different models are not interchangeable, even when they carry the same number of components.

#### 8. Add the search page

Wrap the search in a server function so the query runs on the server, then render the results in a route.

```tsx
import { createFileRoute } from '@tanstack/react-router';
import { createServerFn } from '@tanstack/react-start';
import { useState } from 'react';
import { searchArticles } from '../lib/search.server';

const search = createServerFn({ method: 'POST' })
  .validator((question: string) => question)
  .handler(async ({ data }) => {
    const question = data.trim();
    return question ? searchArticles(question) : [];
  });

export const Route = createFileRoute('/')({ component: Home });

function Home() {
  const [question, setQuestion] = useState('');
  const [results, setResults] = useState([]);

  async function onSubmit(event) {
    event.preventDefault();
    setResults(await search({ data: question }));
  }

  return (
    <main>
      <form onSubmit={onSubmit}>
        <input
          value={question}
          onChange={(event) => setQuestion(event.target.value)}
          placeholder="What do you need help with?"
        />
        <button type="submit">Search</button>
      </form>

      <ol>
        {results.map((article) => (
          <li key={article.id}>
            <h2>{article.title}</h2>
            <span>{article.distance.toFixed(3)}</span>
            <p>{article.body}</p>
          </li>
        ))}
      </ol>
    </main>
  );
}
```

Run `npm run dev` and ask something the articles never say. A question like "I cannot get into my account" returns the password recovery article first, because the meanings match even though the words do not.

![Semantic search results ranked by distance](/images/docs/ai/vector-db/app-search-light.avif)

Showing the distance next to each result is worth keeping while you tune your content. It tells you how far the second result sits from the first, which is the quickest way to see whether your articles are distinct enough from each other.

#### 9. Deploy the app

Push the project to a Git repository and deploy it on [Appwrite Sites](/docs/products/sites/quick-start/tanstack-start). Add the same environment variables in the site's settings, since the search runs on the server side of the deployment.

### Next steps

VectorsDB supports more than similarity search alone. You can filter on `metadata` fields to scope a search to one category, page through results with cursors, and group writes into a transaction.

- [Vector search](/docs/products/databases/vectorsdb/vector-search): Index types, distance functions, and how ranking works.

- [Embeddings](/docs/products/databases/vectorsdb/embeddings): The built-in models and how to generate vectors in batches.

- [Queries](/docs/products/databases/vectorsdb/queries): Filter and paginate results with metadata queries.

---

## Bolt

URL: https://appwrite.io/docs/tooling/ai/vibe-coding/bolt

#### 1. Add the MCP server

Connect the Appwrite MCP server to Bolt for deeper integration with the Appwrite API and documentation. It is a remote HTTP server, so there are no additional pre-requisites to install.

To add the Appwrite MCP server to Bolt:

1. Go to **Settings** → **Connectors (MCP)**.
2. Click **Custom MCP server**.

![Bolt MCP settings](/images/docs/mcp/bolt/light/bolt-mcp-settings.avif)

3. Enter the following details:
    - **Name**: `Appwrite`
    - **URL**: `https://mcp.appwrite.io/`
    - **Transport Type**: HTTP
    - **Authentication**: MCP OAuth
4. Click **Add MCP server**.

![Bolt add MCP server](/images/docs/mcp/bolt/light/bolt-add-mcp.avif)

The server uses OAuth for authentication. When Bolt first connects to the server, complete the sign-in flow in your browser to sign in to your Appwrite account and authorize access.

The Appwrite MCP server will now be available in your Bolt projects.

#### 2. Test the integration

Once connected, you can try out the following example prompts:

**Example prompts:**

- `Create a new user in my Appwrite project`
- `List all databases in my project`
- `Show me the tables in my database`
- `How do I set up real-time subscriptions in Appwrite?`
- `Show me how to authenticate users with OAuth`
- `How do I implement file uploads with Appwrite Storage?`

---

## Claude Desktop

URL: https://appwrite.io/docs/tooling/ai/vibe-coding/claude-desktop

#### 1. Quick start prompts

Get started quickly with these pre-built prompts for common Appwrite integrations:

- [TanStack Start](/docs/tooling/ai/quickstart-prompts/tanstack-start)
- [Next.js](/docs/tooling/ai/quickstart-prompts/nextjs)

[Browse all quick start prompts](/docs/tooling/ai/quickstart-prompts)

#### 2. Add the MCP server

Connect the Appwrite MCP server to Claude Desktop for deeper integration with the Appwrite API and documentation.

Before you begin, ensure you have [Node.js](https://nodejs.org/en/download) and npm installed on your system.

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 remote Appwrite MCP server.

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.avif)

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.

Add the Appwrite MCP server to your configuration:

```json
{
    "mcpServers": {
        "appwrite": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.appwrite.io/"
            ]
        }
    }
}
```

The server uses OAuth for authentication. When Claude Desktop first connects to the server, your browser opens so you can sign in to your Appwrite account and authorize access.

#### 3. 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.avif)

**npx ENOENT error**

In case you see an `npx ENOENT` error, ensure that you either add `npx` to the `PATH` environment variable on your system or use the full path to your `npx` installation in the config file.

#### 4. Test the integration

Try out the following example prompts:

**Example prompts:**

- `Create a new user in my Appwrite project`
- `List all databases in my project`
- `Show me the tables in my database`
- `How do I set up real-time subscriptions in Appwrite?`
- `Show me how to authenticate users with OAuth`
- `How do I implement file uploads with Appwrite Storage?`

![List users in Appwrite project](/images/docs/mcp/claude-desktop/claude-list-users.avif)

---

## Emergent

URL: https://appwrite.io/docs/tooling/ai/vibe-coding/emergent

#### 1. Add the MCP server

To connect the Appwrite MCP server to Emergent:

1. On the homepage, click **Advanced Controls**.
2. Click **Select MCP Tools**.
3. Click **New MCP Server**.
4. Enter a name for your server (e.g., `appwrite`).
5. Paste the following JSON configuration:

```json
{
  "mcpServers": {
    "appwrite": {
      "type": "http",
      "url": "https://mcp.appwrite.io/"
    }
  }
}
```

The server uses OAuth for authentication. When Emergent first connects to the server, complete the sign-in flow in your browser to sign in to your Appwrite account and authorize access.

#### 2. Test the integration

Once connected, you can use natural language to interact with Appwrite. Try prompts like:

**Example prompts:**

- `Create a new user in my Appwrite project`
- `List all databases in my project`
- `Show me the tables in my database`
- `How do I set up real-time subscriptions in Appwrite?`
- `Show me how to authenticate users with OAuth`
- `How do I implement file uploads with Appwrite Storage?`

---

## Lovable

URL: https://appwrite.io/docs/tooling/ai/vibe-coding/lovable

#### 1. Add the MCP server

Connect the Appwrite MCP server to Lovable for deeper integration with the Appwrite API and documentation. It is a remote HTTP server, so there are no additional pre-requisites to install.

To add the Appwrite MCP server to Lovable:

1. Go to **Settings** → **Connectors** → **Personal connectors**.
2. Click **New MCP server**.
3. Enter the following details:
    - **Server name**: `Appwrite`
    - **Server URL**: `https://mcp.appwrite.io/`
    - **Authentication**: Select **OAuth**
4. Click **Add server**.

The server uses OAuth for authentication. When Lovable first connects to the server, complete the sign-in flow in your browser to sign in to your Appwrite account and authorize access.

The Appwrite MCP server will now appear in your list of personal connectors.

#### 2. Test the integration

Once connected, you can try out the following example prompts:

**Example prompts:**

- `Create a new user in my Appwrite project`
- `List all databases in my project`
- `Show me the tables in my database`
- `How do I set up real-time subscriptions in Appwrite?`
- `Show me how to authenticate users with OAuth`
- `How do I implement file uploads with Appwrite Storage?`

---

## Zenflow

URL: https://appwrite.io/docs/tooling/ai/vibe-coding/zenflow

#### 1. Quick start prompts

Get started quickly with these pre-built prompts for common Appwrite integrations:

- [TanStack Start](/docs/tooling/ai/quickstart-prompts/tanstack-start)
- [Next.js](/docs/tooling/ai/quickstart-prompts/nextjs)

[Browse all quick start prompts](/docs/tooling/ai/quickstart-prompts)

#### 2. Add the MCP server

Connect the Appwrite MCP server to Zenflow for deeper integration with the Appwrite API and documentation. It is a remote HTTP server, so there are no additional pre-requisites to install.

To add the Appwrite MCP server, open Zenflow and go to the **Settings** > **MCP servers**. From there, select your agent you want to configure MCP for, and then add your custom MCP server.

```json
{
  "mcpServers": {
    "appwrite": {
      "url": "https://mcp.appwrite.io/",
      "type": "http"
    }
  }
}
```

The server uses OAuth for authentication. When Zenflow first connects to the server, complete the sign-in flow in your browser to sign in to your Appwrite account and authorize access.

Click **Save**. Once you save the configuration, Zenflow will connect with the MCP server and load all available tools.

#### 3. Test the integration

Open **Zenflow Chat** of your existing task to test your MCP integrations. If you don't have an existing task, you can create one by clicking **New Task**, selecting a task type, and writing a task description. Click **Create and Run**.
If you are new to Zenflow, learn more about [how to set up Zenflow](https://docs.zencoder.ai/user-guides/guides/set-up-your-zenflow#step-6:-create-your-first-task).

You can try out the following example prompts:

**Example prompts:**
- `Create a new user in my Appwrite project`
- `List all databases in my project`
- `Show me the tables in my database`
- `How do I set up real-time subscriptions in Appwrite?`
- `Show me how to authenticate users with OAuth`
- `How do I implement file uploads with Appwrite Storage?`

![Search for portfolio site in Appwrite project](/images/docs/mcp/zenflow/zenflow-chat.avif)

---

## The Appwriter

URL: https://appwrite.io/docs/tooling/appwriter

![The Appwriter](/images/docs/keyboard/appwriter.avif)

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

- 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

|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

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

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

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

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

|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

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

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

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)

---

## Arena

URL: https://appwrite.io/docs/tooling/arena

[Appwrite Arena](https://arena.appwrite.io) is an open-source benchmark that evaluates how well AI models understand Appwrite. It tests models across real-world Appwrite usage scenarios, covering services, SDKs, and APIs, to help you choose the best model for building with Appwrite.

Arena ranks models by their ability to answer questions drawn from actual Appwrite platform usage, both with and without access to Appwrite [skills](/docs/tooling/skills). This makes it easy to see which models generate the most accurate Appwrite code out of the box and which benefit most from added context.

All questions, answers, and scores are fully open source and available on [GitHub](https://github.com/appwrite/arena).

### How it works

Arena evaluates each model using a pool of **191 questions** spanning **9 Appwrite service categories**:

- Foundation
- Auth
- Databases
- Functions
- Storage
- Sites
- Messaging
- Realtime
- CLI

Each model is tested in two contexts:

- **Without Skills**: The model answers using only its built-in training data.
- **With Skills**: The model answers with access to Appwrite's [skills files](/docs/tooling/skills), which provide up-to-date SDK and API context.

The gap between these two scores reveals how effectively a model can leverage provided documentation to improve its responses.

All benchmark runs are configured with:

- **Temperature 0**: Makes the entire benchmark deterministic, so identical inputs always produce identical outputs.
- **Extended thinking set to high**: Matches the configuration most commonly used in real-world programming contexts.

### Scoring

Arena uses two complementary scoring methods to evaluate model performance:

#### Deterministic (MCQ)

165 multiple-choice questions structured like a typical exam, each with a single correct answer:

- The model receives a system prompt and the question.
- Four tools are provided, one for each possible answer.
- The model submits its response by calling the correct tool, avoiding issues with verbose or unparseable output.

Scores are fully reproducible with no judge bias.

#### AI-judged (open-ended)

26 open-ended questions scored from 0 to 1 by an AI judge using rubrics and reference answers:

- Tests reasoning and real-world usage patterns that multiple-choice cannot capture.
- Scores may have slight variance due to the nature of AI-based evaluation.

### Use cases

- **Choosing a model**: Compare models to find the one that best fits your Appwrite development workflow and budget.
- **Cost vs. performance**: Determine whether a top-tier model justifies its price for your project, or whether a cheaper or faster model gets you close enough.
- **Measuring skill impact**: See how much a model improves when given Appwrite skills, helping you decide whether to install skills for your AI tools.
- **Comparing response duration**: Models with similar token throughput can have very different benchmark durations. A slower run often indicates the model is spending more tokens to reach the same answer, resulting in a slower development experience.
- **Staying up to date**: Arena is rerun as new models and updates are released, serving as a living reference you can return to whenever you need to re-evaluate your choice.
- **Contributing**: Since Arena is open source, you can submit new questions, suggest improvements to scoring rubrics, or add new models to the benchmark.

---

## Command Center

URL: 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.

![Command center](/images/docs/command-center/command-center.avif)

### 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

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

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

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

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.

---

## Keyboard shortcuts

URL: https://appwrite.io/docs/tooling/command-center/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

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

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

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

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.

---

## Buckets

URL: https://appwrite.io/docs/tooling/command-line/buckets

**Before proceeding**

Ensure you [**install**](/docs/tooling/command-line/installation#getting-started) the CLI, [**log in**](/docs/tooling/command-line/installation#login) to your Appwrite account, and [**initialize**](/docs/tooling/command-line/installation#initialization) your Appwrite project.

The Appwrite CLI allows you to configure and deploy buckets across projects. You can also configure your files using the CLI commands.

### Initialize bucket

Create a new bucket using the following command:

```sh
appwrite init buckets
```

### 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

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": "<PROJECT_ID>",
    "endpoint": "https://<REGION>.cloud.appwrite.io/v1",
    "buckets": [
        {
            "$id": "<BUCKET_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
        }
    ]
}
```

You can also move the `buckets` array into a separate JSON file with the `includes` field.

[Learn more about multi-file configuration](/docs/tooling/command-line/installation#multi-file-configuration)

### Push bucket

Use the `push` command in the folder containing your `appwrite.config.json` file to push the changes you made.

```sh
appwrite push buckets
```

### 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]
```

| 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, including sending multiple chunk requests in parallel when the runtime supports it. |
| `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. |

---

## Commands

URL: https://appwrite.io/docs/tooling/command-line/commands

**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).

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

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

| 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] <output-directory>` | The types command generates type definitions based on your Appwrite database schema. Learn more about [type generation](/docs/products/databases/type-generation). |
| `generate` | The generate command creates a type-safe SDK tailored to your project. It detects your project's language and generates typed helpers based on your database schema. Learn more about [SDK generation](/docs/tooling/command-line/generate). |

#### Account commands

| 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. |

#### Deployment commands

| 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. |

The `init`, `pull`, `push`, and `run` commands support [multi-file project configuration](/docs/tooling/command-line/installation#multi-file-configuration). Use the `includes` field in `appwrite.config.json` to move supported resource arrays into separate JSON files while keeping CLI behavior unchanged.

#### Project commands

| 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. |

#### Command options

| 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 |

### 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
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
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
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
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
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 tables-db get-row \
  --database-id "<DATABASE_ID>" \
  --table-id "<TABLE_ID>" \
  --row-id "<ROW_ID>" \
  --console --open
```

### Filter, sort, and paginate

List commands across services accept a set of dedicated flags for the most common filtering, sorting, and pagination needs, so you don't have to hand-write JSON [query](/docs/products/databases/queries) strings for everyday cases. These flags are supported on `list-*` commands such as `tables-db list-tables`, `tables-db list-rows`, `users list`, `functions list`, `messaging list-messages`, and similar list endpoints across services.

| Flag | Description |
| --- | --- |
| `--where <expression>` | Filter using a simple comparison expression. Supports `field=value`, `field!=value`, `field>value`, `field>=value`, `field<value`, and `field<=value`. Repeat the flag to apply multiple filters. Quote the expression (for example `'year>1999'`) so that `>` and `<` are not interpreted as shell redirection. |
| `--sort-asc <attribute>` | Sort results by an attribute in ascending order. Repeat for multiple sort fields. |
| `--sort-desc <attribute>` | Sort results by an attribute in descending order. Repeat for multiple sort fields. |
| `--limit <number>` | Maximum number of results to return. |
| `--offset <number>` | Number of results to skip from the beginning. |
| `--cursor-after <id>` | Return results after this cursor ID. Use for forward [cursor pagination](/docs/products/databases/pagination). |
| `--cursor-before <id>` | Return results before this cursor ID. Use for backward cursor pagination. |
| `--select <attribute>` | Limit returned attributes on list commands such as `tables-db list-rows`. Repeat the flag to include multiple attributes. |

For example, to fetch the 10 most recently created rows where `year` is greater than 1999:

```sh
appwrite tables-db list-rows \
    --database-id "<DATABASE_ID>" \
    --table-id "<TABLE_ID>" \
    --where 'year>1999' \
    --sort-desc '$createdAt' \
    --limit 10
```

The `--queries` flag is still supported and remains the way to pass raw [Appwrite query](/docs/products/databases/queries) JSON strings for advanced cases, automation pipelines, or operators that the new flags don't cover. When you mix `--queries` with the new flags, the raw queries are sent first and the flag-generated queries are appended after.

```sh
appwrite tables-db list-rows \
    --database-id "<DATABASE_ID>" \
    --table-id "<TABLE_ID>" \
    --queries '[{"method":"search","attribute":"title","values":["Avatar"]}]' \
    --limit 10
```

### Examples

#### 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

To get a list of all your project users, you can use the list command.

```sh
appwrite users list
```

You can narrow the result set with the [filter, sort, and pagination flags](#filter-sort-paginate). For example, to fetch the 25 most recently created users whose email is verified:

```sh
appwrite users list \
    --where 'emailVerification=true' \
    --sort-desc '$createdAt' \
    --limit 25
```

#### List tables

To get a list of all your [tables](/docs/tooling/command-line/tables), you can use the `list-tables` command.

```sh
appwrite tables-db list-tables --database-id "<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 tables-db list-tables --database-id "<DATABASE_ID>" --json
```

#### 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 tables-db get-table --database-id "<DATABASE_ID>" --table-id "<TABLE_ID>"
```

#### Create row

To create a new row in an existing table, use the `create-row` command.

```sh
appwrite tables-db create-row \
    --database-id "<DATABASE_ID>" --table-id "<TABLE_ID>" \
    --row-id 'unique()' --data '{ "Name": "Iron Man" }' \
    --permissions 'read("any")' 'write("team:abc")' 
```

---

## Functions

URL: https://appwrite.io/docs/tooling/command-line/functions

**Before proceeding**

Ensure you [**install**](/docs/tooling/command-line/installation#getting-started) the CLI, [**log in**](/docs/tooling/command-line/installation#login) to your Appwrite account, and [**initialize**](/docs/tooling/command-line/installation#initialization) your Appwrite project.

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

Create a new function using the following command:

```sh
appwrite init functions
```

### 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

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": "<PROJECT_ID>",
    "endpoint": "https://<REGION>.cloud.appwrite.io/v1",
    "functions": [
        {
            "$id": "<FUNCTION_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": "<PROJECT_ID>",
            "vars": [
                {
                    "$id": "eyJhbGciOiJIUzI1N",
                    "$createdAt": "2024-04-22T22:51:51.745+00:00",
                    "$updatedAt": "2024-04-23T00:13:10.886+00:00",
                    "key": "<X_Appwrite_Key>",
                    "value": "<API_KEY>",
                    "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": "<DATABASE_ID>",
                    "value": "<DATABASE_ID>",
                    "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": "<PROJECT_ID>",
                    "value": "<PROJECT_ID>",
                    "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": "<TABLE_USERS_ID>",
                    "value": "<TABLE_ID>",
                    "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"
        }
    ]
}
```

You can also move the `functions` array into a separate JSON file with the `includes` field. When functions are loaded from an included file, each function `path` is resolved relative to that included file.

[Learn more about multi-file configuration](/docs/tooling/command-line/installation#multi-file-configuration)

### Push function

Use the `push` command in the folder containing your `appwrite.config.json` file to push the changes you made.

```sh
appwrite push functions
```

### 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]
```

| 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. |
| `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. |

---

## Generate SDK

URL: https://appwrite.io/docs/tooling/command-line/generate

**Before proceeding**

Ensure you [**install**](/docs/tooling/command-line/installation#getting-started) the CLI, [**log in**](/docs/tooling/command-line/installation#login) to your Appwrite account, and [**initialize**](/docs/tooling/command-line/installation#initialization) your Appwrite project.

The `generate` command creates a type-safe SDK tailored to your Appwrite project. It reads your database schema and generates typed helpers, so you can interact with your tables using auto-completed methods, resulting in a better developer experience.

### Generate SDK

Run the following command in your project directory:

```sh
appwrite generate
```

The CLI automatically detects your project's language and generates the SDK to a `generated/appwrite/` directory.

### Options

| Option | Description |
| --- | --- |
| `-o, --output <directory>` | Output directory for generated files (default: `"generated"`) |
| `-l, --language <language>` | Target language for SDK generation (supported: `typescript`) |
| `--server <mode>` | Override server-side generation (`auto`\|`true`\|`false`) (default: `"auto"`) |
| `-h, --help` | Display help for command |

### Generated files

The generated SDK includes the following files:

| File | Description |
| --- | --- |
| `types.ts` | Type definitions based on your database schema. |
| `databases.ts` | Typed database helpers for querying and mutating rows. |
| `index.ts` | Entry point that exports all generated helpers. |
| `constants.ts` | Configuration constants such as your project endpoint and project ID. Update these values before using the SDK. |

### Usage

After generating the SDK, import it into your project:

```ts
import { databases } from "./generated/appwrite";
```

Configure your SDK constants by setting the values in `./generated/appwrite/constants.ts`.

Use the generated helpers to interact with your tables:

```ts
const customers = databases.use("main").use("customers");

const customer = await customers.create({
    name: "Walter O' Brian",
    email: "walter@example.com"
});
```

The generated helpers provide auto-completion and type checking based on your database schema, reducing errors and improving developer experience.

### Examples

The generated SDK supports all common database operations. Below are examples across different use cases.

#### Get a row

```ts
const customer = await customers.get("customer-id-123");
```

#### List rows with queries

The `list` method accepts a typed query builder that provides auto-completion for your table's columns.

```ts
const results = await customers.list({
    queries: (q) => [
        q.equal("name", "Walter O' Brian"),
        q.orderDesc("$createdAt"),
        q.limit(10)
    ]
});
```

#### Update a row

```ts
await customers.update("customer-id-123", {
    email: "walter@scorpion.com"
});
```

#### Delete a row

```ts
await customers.delete("customer-id-123");
```

#### Bulk operations

Create, update, or delete multiple rows at once.

```ts
await customers.createMany([
    { name: "Walter O' Brian", email: "walter@example.com" },
    { name: "Paige Dineen", email: "paige@example.com" }
]);
```

```ts
await customers.updateMany(
    { email: "updated@example.com" },
    {
        queries: (q) => [q.equal("name", "Walter O' Brian")]
    }
);
```

```ts
await customers.deleteMany({
    queries: (q) => [q.equal("name", "Paige Dineen")]
});
```

#### Permissions

Set row-level permissions when creating or updating rows.

```ts
await customers.create(
    { name: "Walter O' Brian", email: "walter@example.com" },
    {
        permissions: (permission, role) => [
            permission.read(role.any()),
            permission.write(role.user("user-id-123"))
        ]
    }
);
```

---

## Installation

URL: 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

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

If you have npm set up, run the command below to install the CLI.

```sh
npm install -g appwrite-cli
```

#### Install with script

For a completely dependency-free installation, the CLI also ships with a convenient installation script for your operating system

**macOS**

Using [Homebrew](https://brew.sh/)

```sh
brew install appwrite
```

or terminal

```sh
curl -sL https://appwrite.io/cli/install.sh | bash
```

**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
```

**Linux**

```sh
curl -sL https://appwrite.io/cli/install.sh | bash
```

### Update your CLI

**npm**

```sh
npm install -g appwrite-cli
```

**macOS**

Using [Homebrew](https://brew.sh/)

```sh
brew install appwrite
```

or terminal

```sh
curl -sL https://appwrite.io/cli/install.sh | bash
```

**Windows**

```sh
iwr -useb https://appwrite.io/cli/install.ps1 | iex
```

**Linux**

```sh
curl -sL https://appwrite.io/cli/install.sh | bash
```

**Scoop**

```sh
scoop install https://raw.githubusercontent.com/appwrite/sdk-for-cli/master/scoop/appwrite.config.json
```

#### 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

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 "<URL_HERE>"
```
You can log in to multiple accounts or change the **current** account by re-running the command.

### 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. For table column definitions, see [Column types and formats](/docs/tooling/command-line/tables#column-types-and-formats), including email, enum, and floating-point configuration examples.

```json
{
    "projectId": "<PROJECT_ID>",
    "endpoint": "https://<REGION>.cloud.appwrite.io/v1"
}
```

The CLI will also auto-detect your project configuration and automatically install relevant [Appwrite agent skills](/docs/tooling/ai/skills).

You can run your first CLI command after logging in. Try fetching information about your Appwrite project.

```sh
appwrite projects get --project-id "<PROJECT_ID>"
```

### Multi-file configuration

By default, the Appwrite CLI stores your project settings and resource definitions in one `appwrite.config.json` file. In CLI version 20.0.0 and later, you can split top-level resource arrays into separate JSON files with the `includes` field.

Use this when your project has many functions, sites, buckets, teams, topics, tables, or other deployable resources and you want to keep each resource type in its own file.

```json
{
    "projectId": "<PROJECT_ID>",
    "endpoint": "https://<REGION>.cloud.appwrite.io/v1",
    "includes": {
        "functions": "./appwrite/functions.json",
        "sites": "./appwrite/sites.json",
        "buckets": "./appwrite/buckets.json",
        "teams": "./appwrite/teams.json",
        "topics": "./appwrite/topics.json"
    }
}
```

Each included file contains the JSON array that would normally live under that key in `appwrite.config.json`.

```json
[
    {
        "$id": "<FUNCTION_ID>",
        "name": "api",
        "runtime": "node-22",
        "path": "functions/api",
        "entrypoint": "src/main.js",
        "commands": "npm install",
        "execute": [],
        "events": [],
        "schedule": "",
        "timeout": 15,
        "enabled": true,
        "logging": true,
        "ignore": [
            "node_modules",
            ".git"
        ],
        "scopes": [],
        "vars": []
    }
]
```

The CLI resolves resource paths relative to the file that defines the resource. For example, if `functions` is included from `./appwrite/functions.json`, the function `path` above resolves from `./appwrite/`, not from the folder that contains the root `appwrite.config.json`.

The single-file format continues to work. You can split any supported resource arrays over time and keep other arrays in the root config.

Supported include keys are `functions`, `sites`, `databases`, `tablesDB`, `tables`, `topics`, `teams`, `buckets`, `webhooks`, and `messages`.

Include paths must be local JSON files inside your project. They must be relative paths, end in `.json`, and cannot use parent-directory segments, absolute paths, URLs, URL-like schemes, null bytes, or JSON pointer fragments.

**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
```

#### 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

If you get stuck anywhere, you can always use the `help` command to get the usage examples.

```sh
appwrite help
```

### 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://<REGION>.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

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.

**macOS**

Using [Homebrew](https://brew.sh/)

```sh
brew uninstall appwrite
```

or terminal

```sh
rm -f /usr/local/bin/appwrite | bash
```

**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.config
```

**Linux**

```sh
rm -f /usr/local/bin/appwrite | bash
```

You can also remove the configuration, cookies, and API Keys the Appwrite CLI stored. To remove those, run the following command.

**macOS**

```sh
rm -rf ~/.appwrite | bash
```

**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
```

**Linux**

```sh
rm -rf ~/.appwrite | bash
```

---

## Non-interactive

URL: 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://<REGION>.cloud.appwrite.io/v1 \
    --project-id <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
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/partners/project/api-keys).

### 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

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

URL: https://appwrite.io/docs/tooling/command-line/sites

**Before proceeding**

Ensure you [**install**](/docs/tooling/command-line/installation#getting-started) the CLI, [**log in**](/docs/tooling/command-line/installation#login) to your Appwrite account, and [**initialize**](/docs/tooling/command-line/installation#initialization) your Appwrite project.

The CLI handles the creation, deployment, and execution of Appwrite Sites, as well as the configuration of the variables.

### Initialize site

Create a new site using the following command:

```sh
appwrite init sites
```

### 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

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": "<PROJECT_ID>",
    "endpoint": "https://<REGION>.cloud.appwrite.io/v1",
    "sites": [
        {
            "$id": "<SITE_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"
        }
    ]
}
```

You can also move the `sites` array into a separate JSON file with the `includes` field. When sites are loaded from an included file, each site `path` is resolved relative to that included file.

[Learn more about multi-file configuration](/docs/tooling/command-line/installation#multi-file-configuration)

### Push site

Use the `push` command in the folder containing your `appwrite.config.json` file to push the changes you made.

```sh
appwrite push sites
```

### 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]
```

| 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. |

---

## Tables

URL: https://appwrite.io/docs/tooling/command-line/tables

**Before proceeding**

Ensure you [**install**](/docs/tooling/command-line/installation#getting-started) the CLI, [**log in**](/docs/tooling/command-line/installation#login) to your Appwrite account, and [**initialize**](/docs/tooling/command-line/installation#initialization) your Appwrite project.

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

Create a new table using the following command:

```sh
appwrite init tables
```

### 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

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": "<PROJECT_ID>",
    "endpoint": "https://<REGION>.cloud.appwrite.io/v1",
    "tablesDB": [
        {
            "$id": "<DATABASE_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": "<TABLE_ID>",
            "$permissions": [
                "create(\"any\")",
                "read(\"any\")",
                "update(\"any\")",
                "delete(\"any\")"
            ],
            "databaseId": "<DATABASE_ID>",
            "name": "music",
            "enabled": true,
            "rowSecurity": false,
            "columns": [
                {
                    "key": "userID",
                    "type": "varchar",
                    "status": "available",
                    "error": "",
                    "required": false,
                    "array": false,
                    "size": 100,
                    "default": null
                },
                {
                    "key": "name",
                    "type": "varchar",
                    "status": "available",
                    "error": "",
                    "required": false,
                    "array": false,
                    "size": 100,
                    "default": null
                },
                {
                    "key": "cloudinaryId",
                    "type": "varchar",
                    "status": "available",
                    "error": "",
                    "required": false,
                    "array": false,
                    "size": 100,
                    "default": null
                },
                {
                    "key": "user",
                    "type": "varchar",
                    "status": "available",
                    "error": "",
                    "required": false,
                    "array": false,
                    "size": 100,
                    "default": null
                },
                {
                    "key": "audio",
                    "type": "varchar",
                    "status": "available",
                    "error": "",
                    "required": false,
                    "array": false,
                    "size": 200,
                    "default": null
                },
                {
                    "key": "genre",
                    "type": "varchar",
                    "status": "available",
                    "error": "",
                    "required": false,
                    "array": false,
                    "size": 500,
                    "default": null
                },
                {
                    "key": "artist",
                    "type": "varchar",
                    "status": "available",
                    "error": "",
                    "required": false,
                    "array": false,
                    "size": 500,
                    "default": null
                }
            ],
            "indexes": []
        }
    ]
}
```

You can also move the `tablesDB` and `tables` arrays into separate JSON files with the `includes` field.

[Learn more about multi-file configuration](/docs/tooling/command-line/installation#multi-file-configuration)

#### Column types and formats

Each entry in a table's `columns` array uses `type` to select its base data type. For `"type": "string"`, `format` selects a specialized string validator: `email`, `url`, `ip`, or `enum`. For example, an email column is a string with an email format, not `"type": "email"`.

The following values are supported by the [CLI 27.3.0 schema dispatcher](https://github.com/appwrite/sdk-for-cli/blob/9d267aba479afce4a75dee6fefce96ded58f4d50/internal/schema/operations.go#L180-L226). Availability also depends on your Appwrite server version.

| Config `type` | Usage |
| --- | --- |
| `varchar` | Plain text with a maximum `size`. Use this for new plain text columns on servers that support it. |
| `string` | Legacy plain text with a maximum `size`, or a formatted string using one of the formats below. |
| `text`, `mediumtext`, `longtext` | Larger text values. These types do not use `size`. |
| `integer`, `bigint` | Whole numbers, with optional `min` and `max` bounds. |
| `double` | Floating-point numbers, with optional `min` and `max` bounds. Maps to the `float` API endpoint. |
| `boolean` | `true` or `false` values. |
| `datetime` | Date and time values in ISO 8601 format. This is a type, not a string format. |
| `relationship` | Relationships to another table, configured with fields such as `relatedTable` and `relationType`. |
| `point`, `linestring`, `polygon` | Spatial values. `linestring` maps to the `line` API endpoint. |

The CLI commands `create-float-column` and `update-float-column` use **float** in their names, but the corresponding JSON entry must use `"type": "double"`, not `"type": "float"`.

##### String formats

Use `"type": "string"` for all four formats, even when other plain text columns use `varchar`. Adding `format` to `varchar` does not select a formatted-string endpoint.

| Config `format` | Accepted values |
| --- | --- |
| `email` | Email addresses. |
| `url` | URLs. |
| `ip` | IP addresses. |
| `enum` | One of the strings in the required `elements` array. |

Omit `format` for plain text. Formatted strings do not need `size`; enums need `elements` instead. Do not use arbitrary JSON Schema formats such as `uuid`: the CLI only dispatches the four formats listed above, and an unrecognized string format falls back to plain string creation.

`array` is a separate boolean modifier, not a type. For example, `"type": "varchar", "array": true` stores a list of strings. Spatial and relationship creation do not accept this modifier. There is no `"type": "array"` or arbitrary `"type": "object"` in this dispatcher.

##### Column configuration examples

The following JSON array contains complete column definitions for plain text, email, enum, floating-point numbers, and a list of strings. Add the entries you need to a table's `columns` array in `appwrite.config.json`. Keep any existing columns you want to retain: removing a column from the configuration can delete it when you push.

```json
[
    {
        "key": "title",
        "type": "varchar",
        "size": 255,
        "required": true,
        "array": false
    },
    {
        "key": "contactEmail",
        "type": "string",
        "format": "email",
        "required": true,
        "array": false
    },
    {
        "key": "publicationStatus",
        "type": "string",
        "format": "enum",
        "elements": ["draft", "published", "archived"],
        "required": false,
        "array": false,
        "default": "draft"
    },
    {
        "key": "rating",
        "type": "double",
        "required": false,
        "array": false,
        "min": 0,
        "max": 5,
        "default": 2.5
    },
    {
        "key": "tags",
        "type": "varchar",
        "size": 100,
        "required": false,
        "array": true
    }
]
```

Include `key`, `type`, and `required` for these columns, plus `size` for plain `varchar` or legacy `string` columns, and `elements` for enums. An enum's default must be one of its `elements`; a numeric default must fall within its bounds. Do not set a default for required columns or array columns.

The same `type` and `format` distinction applies to legacy collection `attributes`. Keep the plain text type returned by your server when pulling an existing schema; changing `string` to `varchar` is a type change, not just a spelling change. Changing a column's type requires recreation and can lose its data.

### Push table

Use the `push` command in the folder containing your `appwrite.config.json` file to push the changes you made.

```sh
appwrite push tables
```

### Commands

The tables-db command allows you to create structured tables of rows, queries, and filter lists of rows. Appwrite TablesDB CLI commands generally follow the following syntax:

```sh
appwrite tables-db [COMMAND] [OPTIONS]
```

| 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-text-column [options]` | Create a text column. |
| `update-text-column [options]` | Update a text 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. |

---

## Teams

URL: https://appwrite.io/docs/tooling/command-line/teams

**Before proceeding**

Ensure you [**install**](/docs/tooling/command-line/installation#getting-started) the CLI, [**log in**](/docs/tooling/command-line/installation#login) to your Appwrite account, and [**initialize**](/docs/tooling/command-line/installation#initialization) your Appwrite project.

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

Create a new team using the following command:

```sh
appwrite init teams
```

### 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
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": "<PROJECT_ID>",
    "endpoint": "https://<REGION>.cloud.appwrite.io/v1",
    "teams": [
        {
            "$id": "eyJhbGciOiJIUzI1N",
            "name": "hat"
        },
        {
            "$id": "N1IzUIJiOicGbhJye",
            "name": "sun"
        },
        {
            "$id": "OicGbhJyeN1IzUIJi",
            "name": "emit"
        },{
            "$id": "bhJyIJiON1IzUicGe",
            "name": "kue"
        }
    ]
}
```

You can also move the `teams` array into a separate JSON file with the `includes` field.

[Learn more about multi-file configuration](/docs/tooling/command-line/installation#multi-file-configuration)

### Push team

Use the `push` command in the folder containing your `appwrite.config.json` file to push the changes you made.

```sh
appwrite push teams
```

### 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]
```

| 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. |

---

## Topics

URL: https://appwrite.io/docs/tooling/command-line/topics

**Before proceeding**

Ensure you [**install**](/docs/tooling/command-line/installation#getting-started) the CLI, [**log in**](/docs/tooling/command-line/installation#login) to your Appwrite account, and [**initialize**](/docs/tooling/command-line/installation#initialization) your Appwrite project.

The Appwrite CLI can create, update, delete, and get topics, as well as configure the provider and the subscribers.

### Initialize topic

Create a new topic using the following command:

```
appwrite init 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

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": "<PROJECT_ID>",
    "endpoint": "https://<REGION>.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"
            ]
        }
    ]
}
```

You can also move the `topics` array into a separate JSON file with the `includes` field.

[Learn more about multi-file configuration](/docs/tooling/command-line/installation#multi-file-configuration)

### Push topics

Use the `push` command in the folder containing your `appwrite.config.json` file to push the changes you made.

```sh
appwrite push topics
```

### 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]
```

| 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. |

---

## Terraform provider

URL: https://appwrite.io/docs/tooling/terraform

The [Terraform provider for Appwrite](https://github.com/appwrite/terraform-provider-appwrite) lets you declare Appwrite resources in `.tf` files and apply them through [HashiCorp Terraform](https://www.terraform.io/). It covers TablesDB (databases, tables, columns, indexes, rows), dedicated databases (PostgreSQL, MySQL, and MongoDB), DocumentsDB, VectorsDB, Storage (buckets and files), Auth (users and teams), Functions, Sites, Messaging (providers, topics, subscribers), webhooks, backup policies, projects, and custom domains. It is the official way to automate Appwrite project configuration alongside the rest of your stack.

### Resources

Resource types use the `appwrite_` prefix and match the [Terraform Registry](https://registry.terraform.io/providers/appwrite/appwrite/latest/docs) documentation.

| Area | Resources |
|------|-----------|
| TablesDB | `appwrite_tablesdb`, `appwrite_tablesdb_table`, `appwrite_tablesdb_column`, `appwrite_tablesdb_index`, `appwrite_tablesdb_row` |
| Dedicated databases | `appwrite_postgresql_database`, `appwrite_mysql_database`, `appwrite_mongo_database`, and per engine `*_backup_policy`, `*_backup_storage`, `*_branch`, `*_pooler` (PostgreSQL and MySQL), `appwrite_postgresql_extension` |
| DocumentsDB | `appwrite_documentsdb`, `appwrite_documentsdb_collection`, `appwrite_documentsdb_index`, `appwrite_documentsdb_document` |
| VectorsDB | `appwrite_vectorsdb`, `appwrite_vectorsdb_collection`, `appwrite_vectorsdb_index`, `appwrite_vectorsdb_document` |
| Storage | `appwrite_storage_bucket`, `appwrite_storage_file` |
| Auth | `appwrite_auth_user`, `appwrite_auth_team` |
| Functions | `appwrite_function`, `appwrite_function_variable`, `appwrite_function_deployment` |
| Sites | `appwrite_site`, `appwrite_site_variable`, `appwrite_site_deployment` |
| Messaging | `appwrite_messaging_provider`, `appwrite_messaging_topic`, `appwrite_messaging_subscriber` |
| Webhooks | `appwrite_webhook` |
| Backups | `appwrite_backup_policy` |
| Custom domains | `appwrite_proxy_rule` |

### Data sources

Data sources read resources that already exist instead of creating them. Use them to reference identifiers or attributes from resources created outside Terraform or in another state.

| Name | Description |
|------|-------------|
| `appwrite_tablesdb` | Look up a database by ID |
| `appwrite_postgresql_database`, `appwrite_mysql_database`, `appwrite_mongo_database` | Look up a dedicated database by ID, including connection credentials |
| `appwrite_postgresql_databases`, `appwrite_mysql_databases`, `appwrite_mongo_databases` | List dedicated databases, with query filtering |
| `appwrite_postgresql_specifications`, `appwrite_mysql_specifications`, `appwrite_mongo_specifications` | List the compute specifications your plan allows |
| `appwrite_postgresql_database_status`, `appwrite_mysql_database_status`, `appwrite_mongo_database_status` | Read live health, replication, and storage |
| `appwrite_postgresql_backups`, `appwrite_mysql_backups`, `appwrite_mongo_backups` | List backups of a dedicated database |
| `appwrite_postgresql_extensions` | List installed and installable PostgreSQL extensions |
| `appwrite_documentsdb`, `appwrite_vectorsdb` | Look up a DocumentsDB or VectorsDB database by ID |
| `appwrite_documentsdb_specifications`, `appwrite_vectorsdb_specifications` | List the specifications each product allows |
| `appwrite_storage_bucket` | Look up a storage bucket by ID |
| `appwrite_auth_user` | Look up a user by ID |
| `appwrite_auth_team` | Look up a team by ID |
| `appwrite_function` | Look up a function by ID |
| `appwrite_site` | Look up a site by ID |
| `appwrite_messaging_topic` | Look up a messaging topic by ID |
| `appwrite_webhook` | Look up a webhook by ID |

**Looking for self-hosting?**

This section is about **Appwrite resources inside a project** (for example databases and tables) using Terraform. If you want to install or operate the Appwrite server itself (Docker, configuration, TLS, scaling), go to the [self-hosting documentation](/docs/advanced/self-hosting) instead.

### What is Terraform?

[Terraform](https://www.terraform.io/) is an infrastructure-as-code tool. You write **configuration** in a declarative language (HCL) that describes what should exist; Terraform figures out **how** to create or update it.

If you are new to the idea:

- **Provider**: A plugin that teaches Terraform how to talk to a specific API (here, Appwrite).
- **Resource**: Something Terraform should create and manage (for example an Appwrite database or table).
- **State**: Terraform remembers what it already created so the next run can update or destroy only what changed.
- **Plan / apply**: You run `terraform plan` to preview changes, then `terraform apply` to execute them.

You still use the [Appwrite Console](https://cloud.appwrite.io) and SDKs for day-to-day app development; the provider is for **repeatable, version-controlled** setup of project resources across environments.

### Official repository and registry

Source code, issues, and contribution guidelines:

- [GitHub](https://github.com/appwrite/terraform-provider-appwrite): `appwrite/terraform-provider-appwrite`: source and issues.
- [Terraform Registry](https://registry.terraform.io/providers/appwrite/appwrite/latest/docs): `appwrite/appwrite`: install, versions, generated schemas.

The provider works with **Appwrite Cloud** and **Community Edition** (self-hosted Appwrite). You point it at your API endpoint and authenticate with a project API key.

### Next steps

- [Configuration](/docs/tooling/terraform/provider): Install the provider, set endpoints, environment variables, and optional per-resource `project_id`.
- [TablesDB](/docs/tooling/terraform/resources/databases): TablesDB: databases, tables, columns, indexes, and rows.
- [Dedicated databases](/docs/tooling/terraform/resources/dedicated-databases): PostgreSQL, MySQL, and MongoDB on reserved infrastructure.
- [DocumentsDB](/docs/tooling/terraform/resources/documentsdb): Databases, collections, indexes, and JSON documents.
- [VectorsDB](/docs/tooling/terraform/resources/vectorsdb): Collections of embeddings searched by similarity.
- [Storage](/docs/tooling/terraform/resources/storage): Buckets and uploaded files.
- [Messaging](/docs/tooling/terraform/resources/messaging): Providers, topics, and subscribers.
- [Auth](/docs/tooling/terraform/resources/auth): Users and teams.
- [Functions](/docs/tooling/terraform/resources/functions): Functions and environment variables.
- [Sites](/docs/tooling/terraform/resources/sites): Sites and site variables.
- [Webhooks](/docs/tooling/terraform/resources/webhooks): Event delivery to your HTTP endpoints.
- [Backups](/docs/tooling/terraform/resources/backups): Backup policies where supported.
- [Custom domains](/docs/tooling/terraform/resources/domains): Proxy rules for sites and functions.

---

## Configuration

URL: https://appwrite.io/docs/tooling/terraform/provider

The Appwrite provider is published as `appwrite/appwrite` on the [Terraform Registry](https://registry.terraform.io/providers/appwrite/appwrite/latest). The registry hosts **generated reference docs** for the provider and every resource and data source: [latest docs](https://registry.terraform.io/providers/appwrite/appwrite/latest/docs). Full examples and attribute tables also live in the [provider repository](https://github.com/appwrite/terraform-provider-appwrite).

### Terraform block

Declare the provider source in a `terraform` block. You can add a `version` constraint when you want to pin a release; see published versions on the [registry provider page](https://registry.terraform.io/providers/appwrite/appwrite/latest).

```hcl
terraform {
  required_providers {
    appwrite = {
      source = "appwrite/appwrite"
    }
  }
}
```

### Appwrite Cloud

Replace `<REGION>` with your project’s region subdomain (see [Regions](/docs/products/network/regions)).

```hcl
provider "appwrite" {
  endpoint   = "https://<REGION>.cloud.appwrite.io/v1"
  project_id = "project-id"
  api_key    = "api-key"
}
```

### Community Edition

For self-hosted Appwrite instances, set your instance URL and enable `self_signed` when you use a certificate that is not trusted by default (common in local or internal deployments):

```hcl
provider "appwrite" {
  endpoint    = "https://appwrite-instance.com/v1"
  project_id  = "project-id"
  api_key     = "api-key"
  self_signed = true
}
```

### Environment variables

You can supply credentials via environment variables instead of hard-coding them in `.tf` files (recommended for CI and local development):

```bash
export APPWRITE_ENDPOINT="https://<REGION>.cloud.appwrite.io/v1"
export APPWRITE_PROJECT_ID="project-id"
export APPWRITE_API_KEY="api-key"
```

When an environment variable is set, the matching provider argument does not need to appear in the configuration. Values set directly in the `provider` block take precedence over the environment.

| Provider argument | Environment variable | Required | Description |
|-------------|------------------------|----------|-------------|
| `endpoint` | `APPWRITE_ENDPOINT` | yes | Appwrite API endpoint |
| `project_id` | `APPWRITE_PROJECT_ID` | no | Default project ID for resources (omit if you set `project_id` on each resource) |
| `api_key` | `APPWRITE_API_KEY` | yes | API key with permissions for the resources you manage |
| `self_signed` | - | no | Accept self-signed TLS certificates (Community Edition) |
| `http_timeout_seconds` | - | no | How long to wait for a single API response. Defaults to 120 |

### Request timeouts

`http_timeout_seconds` sets how long the provider waits for a single API response before giving up. It defaults to 120 seconds.

```hcl
provider "appwrite" {
  endpoint             = "https://<REGION>.cloud.appwrite.io/v1"
  project_id           = "project-id"
  api_key              = "api-key"
  http_timeout_seconds = 180
}
```

Some requests do their work inline rather than in the background. Updating a [connection pooler](/docs/tooling/terraform/resources/dedicated-databases#connection-pooling) restarts the sidecar, for example. A short timeout can then make Terraform report a failure for work the server already applied. This setting is separate from the waits the provider performs itself. Provisioning a dedicated database or building an index polls until the resource settles, and this timeout does not bound that.

### Project scoping

You can set `project_id` on the **provider** as the default for all resources, or set `project_id` on **individual resources** when one Terraform configuration manages multiple Appwrite projects.

### API keys

Use a key with the scopes required for the resources you manage (for example TablesDB, Storage, Messaging, Functions, Sites, Auth, webhooks, and backups). Follow the principle of least privilege and rotate keys stored outside Terraform.

**DocumentsDB and VectorsDB have their own scopes**

[DocumentsDB](/docs/tooling/terraform/resources/documentsdb) and [VectorsDB](/docs/tooling/terraform/resources/vectorsdb) do not use the TablesDB scopes (`tables.*`, `rows.*`). Each product has its own set, split by level: `documentsdb.read` and `documentsdb.write` for databases, `documentsdb.collections.*` for collections and indexes, and `documentsdb.documents.*` for documents. VectorsDB uses the same shape under `vectorsdb.*`.

### Sensitive state

[Dedicated databases](/docs/tooling/terraform/resources/dedicated-databases) export `connection_string` and `connection_password` as sensitive read-only attributes, and branches export their own credentials. Terraform writes these to state, so use a remote backend with encryption and access control, and mark any output that carries them `sensitive = true`.

### Related

- [Overview](/docs/tooling/terraform): full resource list
- [TablesDB](/docs/tooling/terraform/resources/databases): databases, tables, columns, indexes, and rows
- [Dedicated databases](/docs/tooling/terraform/resources/dedicated-databases): PostgreSQL, MySQL, and MongoDB
- [DocumentsDB](/docs/tooling/terraform/resources/documentsdb) and [VectorsDB](/docs/tooling/terraform/resources/vectorsdb): document and embedding collections
- [Storage](/docs/tooling/terraform/resources/storage): buckets and files
- [Messaging](/docs/tooling/terraform/resources/messaging): providers, topics, and subscribers
- [Self-hosting](/docs/advanced/self-hosting): install and run the Appwrite server (not the same as configuring project resources with this provider)

---

## Auth

URL: https://appwrite.io/docs/tooling/terraform/resources/auth

The provider exposes **Auth** resources so you can align users and teams with the rest of your infrastructure-as-code workflow.

For generated schemas and import syntax, see the Terraform Registry: [auth_user](https://registry.terraform.io/providers/appwrite/appwrite/latest/docs/resources/auth_user) and [auth_team](https://registry.terraform.io/providers/appwrite/appwrite/latest/docs/resources/auth_team). The [provider repository](https://github.com/appwrite/terraform-provider-appwrite) contains source and examples.

### Resources

| Resource | Purpose |
|----------|---------|
| `appwrite_auth_user` | Create and manage users |
| `appwrite_auth_team` | Create and manage teams |

Use these together with your normal [Auth](/docs/products/auth) and permission models; scope API keys appropriately when Terraform manages identity resources.

### Examples

#### Users

Pass credentials through [Terraform variables](https://developer.hashicorp.com/terraform/language/values/variables) so the values are not committed to `.tf` files. `labels` let you tag users for access control or downstream automation.

```hcl
resource "appwrite_auth_user" "john" {
  name     = "john doe"
  email    = "john@example.com"
  password = var.user_password
}

resource "appwrite_auth_user" "admin" {
  name     = "admin"
  email    = "admin@example.com"
  password = var.admin_password
  labels   = ["admin", "staff"]
}
```

#### Teams

`roles` sets the default roles granted to new team members. Omit it to use the provider default (`["owner"]`).

```hcl
resource "appwrite_auth_team" "engineering" {
  name = "engineering"
}

resource "appwrite_auth_team" "marketing" {
  name  = "marketing"
  roles = ["owner", "editor"]
}
```

### Data sources

The **`appwrite_auth_user`** and **`appwrite_auth_team`** data sources read users and teams that already exist (for example created through the Console or via your application) by ID. Use them to wire identities into other resources without managing them with Terraform.

```hcl
data "appwrite_auth_user" "admin" {
  id = "64f2cd7e27bda9f23ab6"
}

data "appwrite_auth_team" "engineers" {
  id = "engineers"
}

output "admin_email" {
  value = data.appwrite_auth_user.admin.email
}
```

See the Terraform Registry for the full attribute lists: [auth_user data source](https://registry.terraform.io/providers/appwrite/appwrite/latest/docs/data-sources/auth_user) and [auth_team data source](https://registry.terraform.io/providers/appwrite/appwrite/latest/docs/data-sources/auth_team).

### Related

- [Configuration](/docs/tooling/terraform/provider): authentication and endpoints
- [Auth product docs](/docs/products/auth)

---

## Backups

URL: https://appwrite.io/docs/tooling/terraform/resources/backups

The `appwrite_backup_policy` resource configures **backup policies** for supported resources. Availability depends on your Appwrite Cloud plan or self-hosted setup.

**Dedicated databases have their own policies**

`appwrite_backup_policy` covers resources on Appwrite's shared infrastructure. A [dedicated database](/docs/tooling/terraform/resources/dedicated-databases#backups) uses its own engine-specific resource instead, `appwrite_postgresql_backup_policy`, `appwrite_mysql_backup_policy`, or `appwrite_mongo_backup_policy`. Those also support incremental backups and shipping to a bucket you own.

See the Terraform Registry: [backup_policy](https://registry.terraform.io/providers/appwrite/appwrite/latest/docs/resources/backup_policy). The [provider repository](https://github.com/appwrite/terraform-provider-appwrite) lists the full argument reference.

### Resource

| Resource | Purpose |
|----------|---------|
| `appwrite_backup_policy` | Configure backup policies for supported resources |

Policies use **`schedule`** (CRON), **`retention`** (days), and **`services`** (for example `["databases"]`). Omit **`resource_id`** to cover all databases in the project, or set **`resource_id`** to a specific database ID (often `appwrite_tablesdb.*.id`) to back up one database.

### Example

```hcl
resource "appwrite_backup_policy" "daily" {
  name      = "daily database backup"
  services  = ["databases"]
  retention = 7
  schedule  = "0 2 * * *"
}

resource "appwrite_tablesdb" "main" {
  name = "main"
}

resource "appwrite_backup_policy" "production" {
  name        = "production database backup"
  services    = ["databases"]
  resource_id = appwrite_tablesdb.main.id
  retention   = 14
  schedule    = "0 */6 * * *"
}
```

### Related

- [TablesDB](/docs/tooling/terraform/resources/databases): databases you can target with `resource_id`
- [Dedicated databases](/docs/tooling/terraform/resources/dedicated-databases): backup policies and backup storage for PostgreSQL, MySQL, and MongoDB
- [Configuration](/docs/tooling/terraform/provider): authentication and endpoints

---

## TablesDB

URL: https://appwrite.io/docs/tooling/terraform/resources/databases

The provider exposes Appwrite **TablesDB** as Terraform resources. Typical order: create a **database** (`appwrite_tablesdb`), then **tables**, then **columns** and **indexes**, and optionally **rows**.

**Other database products**

This page covers TablesDB, Appwrite's relational product on shared infrastructure. The provider also manages [dedicated databases](/docs/tooling/terraform/resources/dedicated-databases) (PostgreSQL, MySQL, and MongoDB on reserved infrastructure), [DocumentsDB](/docs/tooling/terraform/resources/documentsdb) (schemaless JSON collections), and [VectorsDB](/docs/tooling/terraform/resources/vectorsdb) (embeddings searched by similarity).

For full generated schemas, see the Terraform Registry: [tablesdb](https://registry.terraform.io/providers/appwrite/appwrite/latest/docs/resources/tablesdb), [tablesdb_table](https://registry.terraform.io/providers/appwrite/appwrite/latest/docs/resources/tablesdb_table), [tablesdb_column](https://registry.terraform.io/providers/appwrite/appwrite/latest/docs/resources/tablesdb_column), [tablesdb_index](https://registry.terraform.io/providers/appwrite/appwrite/latest/docs/resources/tablesdb_index), and [tablesdb_row](https://registry.terraform.io/providers/appwrite/appwrite/latest/docs/resources/tablesdb_row). The [provider repository](https://github.com/appwrite/terraform-provider-appwrite) contains the source and examples.

**Version compatibility**

TablesDB resources target Appwrite Cloud or Community Edition 1.9.0 and later. Earlier self-hosted versions return `general_route_not_found` for TablesDB routes.

### Resources

| Resource | Purpose |
|----------|---------|
| `appwrite_tablesdb` | Create a database in your project |
| `appwrite_tablesdb_table` | Create a table within a database |
| `appwrite_tablesdb_column` | Define columns (types, constraints, defaults) |
| `appwrite_tablesdb_index` | Add indexes on one or more columns |
| `appwrite_tablesdb_row` | Insert and manage rows in a table |

Relationships use `database_id` and `table_id` references, often wired through Terraform resource attributes (for example `appwrite_tablesdb.main.id`).

### Example

This pattern matches the upstream documentation: a database, a `users` table, several columns, and a unique index on email.

```hcl
resource "appwrite_tablesdb" "main" {
  id   = "main"
  name = "main"
}

resource "appwrite_tablesdb_table" "users" {
  database_id = appwrite_tablesdb.main.id
  id          = "users"
  name        = "users"
}

resource "appwrite_tablesdb_column" "name" {
  database_id = appwrite_tablesdb.main.id
  table_id    = appwrite_tablesdb_table.users.id
  key         = "name"
  type        = "varchar"
  size        = 255
  required    = true
}

resource "appwrite_tablesdb_column" "email" {
  database_id = appwrite_tablesdb.main.id
  table_id    = appwrite_tablesdb_table.users.id
  key         = "email"
  type        = "email"
  required    = true
}

resource "appwrite_tablesdb_column" "age" {
  database_id = appwrite_tablesdb.main.id
  table_id    = appwrite_tablesdb_table.users.id
  key         = "age"
  type        = "integer"
  min         = 0
  max         = 150
}

resource "appwrite_tablesdb_column" "role" {
  database_id = appwrite_tablesdb.main.id
  table_id    = appwrite_tablesdb_table.users.id
  key         = "role"
  type        = "enum"
  elements    = ["admin", "editor", "viewer"]
  default     = "viewer"
}

resource "appwrite_tablesdb_column" "tags" {
  database_id = appwrite_tablesdb.main.id
  table_id    = appwrite_tablesdb_table.users.id
  key         = "tags"
  type        = "varchar"
  size        = 64
  array       = true
}

resource "appwrite_tablesdb_column" "location" {
  database_id = appwrite_tablesdb.main.id
  table_id    = appwrite_tablesdb_table.users.id
  key         = "location"
  type        = "point"
}

resource "appwrite_tablesdb_index" "email_unique" {
  database_id = appwrite_tablesdb.main.id
  table_id    = appwrite_tablesdb_table.users.id
  key         = "email_unique"
  type        = "unique"
  columns     = [appwrite_tablesdb_column.email.key]
}
```

Column `type` supports the types your Appwrite version exposes (for example `varchar`, `email`, `integer`, `enum`, `point`, and more); check the provider docs for the full set.

### Seeding rows

The `appwrite_tablesdb_row` resource inserts rows declaratively. Pass the payload through `jsonencode` and list the columns the row depends on so Terraform does not attempt to insert before the schema is in place.

```hcl
resource "appwrite_tablesdb_row" "alice" {
  database_id = appwrite_tablesdb.main.id
  table_id    = appwrite_tablesdb_table.users.id
  data = jsonencode({
    name  = "alice"
    email = "alice@example.com"
  })

  depends_on = [
    appwrite_tablesdb_column.name,
    appwrite_tablesdb_column.email,
  ]
}
```

Use this for fixtures, default content, or small reference tables. For bulk data loads, prefer the SDKs or the Appwrite CLI.

### Data sources

**Data sources** read resources that already exist instead of creating them. The provider exposes **`appwrite_tablesdb`** so you can reference a database by ID (for example when it was created outside Terraform or in another state).

See the [Terraform Registry](https://registry.terraform.io/providers/appwrite/appwrite/latest/docs/data-sources/tablesdb) for the full argument list.

```hcl
data "appwrite_tablesdb" "existing" {
  id = "main"
}

resource "appwrite_tablesdb_table" "example" {
  database_id = data.appwrite_tablesdb.existing.id
  id          = "example"
  name        = "example"
}
```

### Related

- [Databases product docs](/docs/products/databases): concepts and Console workflows
- [Dedicated databases](/docs/tooling/terraform/resources/dedicated-databases): PostgreSQL, MySQL, and MongoDB
- [DocumentsDB](/docs/tooling/terraform/resources/documentsdb) and [VectorsDB](/docs/tooling/terraform/resources/vectorsdb): document and embedding collections
- [Configuration](/docs/tooling/terraform/provider): authentication and endpoints

---

## Dedicated databases

URL: https://appwrite.io/docs/tooling/terraform/resources/dedicated-databases

A dedicated database runs on infrastructure reserved for a single project, with its own connection string, compute specification, and lifecycle. The provider exposes each engine as its own set of resources, because Appwrite routes them separately and only some engines have a pooler or extensions.

| Engine | Resource prefix |
|--------|-----------------|
| PostgreSQL | `appwrite_postgresql_` |
| MySQL | `appwrite_mysql_` |
| MongoDB | `appwrite_mongo_` |

**Provisioning takes minutes**

Creating, resizing, or upgrading a dedicated database takes several minutes. Terraform waits for the database to leave its transitional state before continuing, so dependent resources are never handed a half-built database. Expect `terraform apply` to sit on these resources for several minutes.

For full generated schemas, see the Terraform Registry: [postgresql_database](https://registry.terraform.io/providers/appwrite/appwrite/latest/docs/resources/postgresql_database), [mysql_database](https://registry.terraform.io/providers/appwrite/appwrite/latest/docs/resources/mysql_database), and [mongo_database](https://registry.terraform.io/providers/appwrite/appwrite/latest/docs/resources/mongo_database). The [provider repository](https://github.com/appwrite/terraform-provider-appwrite) contains the source and examples.

### Resources

Every engine has the same database, backup, and branch resources. Poolers and extensions exist only where the engine supports them.

| Resource | Purpose | PostgreSQL | MySQL | MongoDB |
|----------|---------|------------|-------|---------|
| `*_database` | Provision and size a dedicated database | Yes | Yes | Yes |
| `*_backup_policy` | Schedule backups on a CRON expression | Yes | Yes | Yes |
| `*_backup_storage` | Send backups to a bucket you own | Yes | Yes | Yes |
| `*_branch` | Branch a database for previews and CI | Yes | Yes | Yes |
| `*_pooler` | Configure the connection pooler | Yes | Yes | No |
| `*_extension` | Install an engine extension | Yes | No | No |

Written out, that is `appwrite_postgresql_database`, `appwrite_mysql_database`, and `appwrite_mongo_database`; `appwrite_postgresql_backup_policy`, `appwrite_mysql_backup_policy`, and `appwrite_mongo_backup_policy`; `appwrite_postgresql_backup_storage`, `appwrite_mysql_backup_storage`, and `appwrite_mongo_backup_storage`; `appwrite_postgresql_branch`, `appwrite_mysql_branch`, and `appwrite_mongo_branch`; `appwrite_postgresql_pooler` and `appwrite_mysql_pooler`; and `appwrite_postgresql_extension`.

### Data sources

Data sources read what already exists instead of creating it. Use them to size a database from what your plan allows, to look up a connection string, or to report on live health.

| Data source | Purpose | PostgreSQL | MySQL | MongoDB |
|-------------|---------|------------|-------|---------|
| `*_database` | Look up one database by ID, including connection credentials | Yes | Yes | Yes |
| `*_databases` | List databases, with server-side query filtering | Yes | Yes | Yes |
| `*_specifications` | List the compute specifications your billing plan allows | Yes | Yes | Yes |
| `*_database_status` | Read live health, replication, connections, and volumes | Yes | Yes | Yes |
| `*_backups` | List backups, for example to find an ID to restore from | Yes | Yes | Yes |
| `*_extensions` | List installed and installable extensions | Yes | No | No |

The `*_databases` listing omits connection credentials on purpose, so listing every database does not put every password into state. Read a single `*_database` data source when you need the connection string.

### Provisioning a database

At minimum a database needs a `name`. Set `specification` to pick the compute size, and `version` to pin the engine major version.

```hcl
resource "appwrite_postgresql_database" "main" {
  name          = "main"
  version       = "17"
  specification = "s-1vcpu-1gb"
}
```

Changing `specification` resizes the database in place. Changing `version` performs an in-place major version upgrade, which cannot be rolled back.

### Sizing from the specifications data source

Compute slugs (`s-1vcpu-1gb` through `s-8vcpu-64gb`) are not all enabled on every billing plan, and Appwrite rejects a slug your plan does not allow at apply time. Read the catalog to see what you can pick:

```hcl
data "appwrite_postgresql_specifications" "available" {}

output "available_specifications" {
  value = [
    for s in data.appwrite_postgresql_specifications.available.specifications :
    { slug = s.slug, cpu = s.cpu, memory = s.memory, price = s.price }
    if s.enabled
  ]
}
```

Each specification reports `slug`, `name`, `cpu`, `memory`, `max_connections`, `included_storage`, `included_bandwidth`, `price`, and `enabled`.

Then set the slug you want. How much compute a database gets is worth choosing rather than deriving from whatever the catalog returns first:

```hcl
resource "appwrite_postgresql_database" "production" {
  name          = "production"
  version       = "17"
  specification = "s-2vcpu-4gb"
}
```

To catch an unavailable slug at plan time rather than halfway through an apply, check your choice against the catalog:

```hcl
variable "database_specification" {
  type    = string
  default = "s-2vcpu-4gb"
}

resource "appwrite_postgresql_database" "production" {
  name          = "production"
  version       = "17"
  specification = var.database_specification

  lifecycle {
    precondition {
      condition = contains(
        [for s in data.appwrite_postgresql_specifications.available.specifications : s.slug if s.enabled],
        var.database_specification
      )
      error_message = "Specification ${var.database_specification} is not enabled on this billing plan."
    }
  }
}
```

### High availability and recovery

Setting `replicas` above 0 turns on high availability. `sync_mode` applies only when there are replicas. `async` never blocks a commit, `sync` waits for a standby, and `quorum` waits for a majority.

```hcl
resource "appwrite_postgresql_database" "production" {
  name          = "production"
  specification = "s-2vcpu-4gb"

  # A warm standby with synchronous replication.
  replicas  = 1
  sync_mode = "sync"

  # Point-in-time recovery with a two week window.
  pitr                = true
  pitr_retention_days = 14

  # Grow storage automatically, up to 100 GB.
  storage_autoscaling                   = true
  storage_autoscaling_threshold_percent = 80
  storage_autoscaling_max_gb            = 100
}
```

Set `storage_autoscaling_max_gb` to `0` for no ceiling.

### Networking and maintenance

`network_ip_allowlist` takes IP addresses and CIDR ranges. An empty set allows any address, so the allowlist restricts nothing until it has an entry. `maintenance_window_day` and `maintenance_window_hour_utc` must be set together.

```hcl
resource "appwrite_mysql_database" "production" {
  name          = "production"
  specification = "s-2vcpu-4gb"

  network_ip_allowlist         = ["203.0.113.0/24", "10.0.0.0/16"]
  network_idle_timeout_seconds = 300

  # Patch on Sunday mornings rather than mid-week.
  maintenance_window_day      = "sun"
  maintenance_window_hour_utc = 3
}
```

### Pausing and idling

`idle_timeout_minutes` scales the container to zero after a period of inactivity, and `0` keeps it always on. `status` is the desired state. Set it to `paused` to stop a database without deleting its data, and back to `ready` to resume. When you leave it unset, Terraform only reads `status` back from the server.

```hcl
resource "appwrite_postgresql_database" "development" {
  name                 = "development"
  specification        = "s-1vcpu-1gb"
  idle_timeout_minutes = 15
  status               = "ready"
}
```

The read-only `lifecycle_state` reports how far an idling database has scaled down: `active`, `warm`, `cold`, or `hibernated`.

### SQL API

The SQL API runs statements over the Appwrite API rather than a direct connection. It is off by default. DDL and DCL statements (`CREATE`, `ALTER`, `DROP`, `TRUNCATE`, `GRANT`, `REVOKE`) are rejected unless you list them in `sql_api_allowed_statements`.

```hcl
resource "appwrite_postgresql_database" "analytics" {
  name          = "analytics"
  specification = "s-1vcpu-1gb"

  sql_api_enabled            = true
  sql_api_allowed_statements = ["SELECT"]
  sql_api_max_rows           = 1000
  sql_api_max_bytes          = 1048576
  sql_api_timeout_seconds    = 30
}
```

MongoDB accepts these arguments so the schema is the same across engines, but it does not run SQL, so they have no effect.

### Connection details

Each database exports its connection details as read-only attributes. `connection_string` and `connection_password` are sensitive. They land in Terraform state, so protect the state file and mark any output that carries them.

```hcl
output "database_host" {
  value = appwrite_postgresql_database.main.hostname
}

output "database_url" {
  value     = appwrite_postgresql_database.main.connection_string
  sensitive = true
}
```

The database also exports `connection_user`, `connection_port`, `ssl`, `cpu`, `memory`, `storage`, `engine`, `network_max_connections`, `backup_enabled`, `created_at`, `updated_at`, and `error` when the status is `failed`. Rotate credentials outside Terraform, since the provider only reads them.

### Connection pooling

A pooler sits in front of PostgreSQL and MySQL databases. It exists for the lifetime of the database, so this resource only updates settings. Destroying it leaves the pooler running with its last applied configuration.

```hcl
resource "appwrite_postgresql_pooler" "main" {
  database_id       = appwrite_postgresql_database.production.id
  mode              = "transaction"
  default_pool_size = 25

  # Send SELECTs to the replica and keep writes on the primary.
  read_write_splitting = true
}
```

`mode` is `transaction` (a connection returns to the pool after each transaction, which suits short serverless queries) or `session` (held for the whole client session). `read_write_splitting` is only active when the database has replicas.

`max_connections` is settable on MySQL and read-only on PostgreSQL. The PostgreSQL pooler has no client cap of its own and reports the database's `network_max_connections` instead, so size it through the database specification. You can also tune the sidecar with `pooler_cpu_request`, `pooler_cpu_limit`, `pooler_memory_request`, and `pooler_memory_limit`, each a Kubernetes quantity such as `200m` or `128Mi`.

### Extensions

PostgreSQL databases accept extensions. Read the installable names from the data source rather than guessing:

```hcl
data "appwrite_postgresql_extensions" "main" {
  database_id = appwrite_postgresql_database.main.id
}

output "available_extensions" {
  value = data.appwrite_postgresql_extensions.main.available
}

resource "appwrite_postgresql_extension" "postgis" {
  database_id = appwrite_postgresql_database.main.id
  name        = "postgis"
}
```

The data source also returns `installed` and a `metadata` list describing each available extension.

### Branches

A branch is a copy of a database. It shares the parent's credentials but has its own host and database name, which gives a preview environment or a CI job production-like data.

```hcl
resource "appwrite_postgresql_branch" "preview" {
  database_id = appwrite_postgresql_database.main.id
  branch_id   = "preview"
}

### A branch with a TTL is reclaimed by the server when it expires.
resource "appwrite_postgresql_branch" "ephemeral" {
  database_id = appwrite_postgresql_database.main.id
  branch_id   = "ci-run"
  ttl         = 3600
}

output "preview_connection_string" {
  value     = appwrite_postgresql_branch.preview.connection_string
  sensitive = true
}
```

Branches have no update route, so changing any argument replaces the branch and discards its data. When a `ttl` expires, the server deletes the branch. The next refresh drops it from state and the following plan recreates it, so a short-lived branch comes back instead of staying deleted.

### Backups

Dedicated databases use their own engine-specific backup policy resource. Use [`appwrite_backup_policy`](/docs/tooling/terraform/resources/backups) for databases on Appwrite's shared infrastructure instead.

```hcl
resource "appwrite_postgresql_backup_policy" "nightly" {
  database_id = appwrite_postgresql_database.main.id
  name        = "nightly"
  schedule    = "0 3 * * *"
  retention   = 7
  type        = "full"
}

resource "appwrite_postgresql_backup_policy" "incremental" {
  database_id = appwrite_postgresql_database.main.id
  name        = "six-hourly"
  schedule    = "0 */6 * * *"
  retention   = 3
  type        = "incremental"
}
```

`retention` is in days and `schedule` is a CRON expression in UTC. Changing `type` replaces the policy.

To find a backup to restore from, list them:

```hcl
data "appwrite_postgresql_backups" "main" {
  database_id = appwrite_postgresql_database.main.id
  queries     = ["equal(\"status\", \"completed\")"]
}
```

Each backup reports `id`, `status`, `type`, `requested_type`, `trigger`, `size_bytes`, `policy_id`, `log_position`, and its timestamps. `requested_type` differs from `type` when the server could not run the backup that was asked for and fell back to another, and `fallback_reason` explains why.

#### Backup storage

Send backups to a bucket you control so they outlive the Appwrite project and fall under your own retention rules.

```hcl
resource "appwrite_postgresql_backup_storage" "offsite" {
  database_id      = appwrite_postgresql_database.main.id
  storage_provider = "s3"
  bucket           = "acme-database-backups"
  region           = "eu-west-1"
  prefix           = "postgresql/main"

  access_key = var.backup_access_key
  secret_key = var.backup_secret_key
}
```

`storage_provider` is `s3` (Amazon S3 or S3-compatible), `gcs`, or `azure`. An S3-compatible provider that is not Amazon needs an explicit `endpoint`.

**Backup storage cannot be read back**

Appwrite has no route to read this configuration back, so Terraform cannot detect drift on it, cannot verify what the server holds, and cannot import an existing configuration. Destroying the resource only removes it from state, and backups keep going to the last destination applied. Change the destination by applying a new one.

### Reading live status

The status data source reports what the database is doing right now, which is useful for alerting outputs or for gating a dependent resource.

```hcl
data "appwrite_postgresql_database_status" "main" {
  database_id = appwrite_postgresql_database.main.id
}

output "database_health" {
  value = {
    health        = data.appwrite_postgresql_database_status.main.health
    ready         = data.appwrite_postgresql_database_status.main.ready
    connections   = data.appwrite_postgresql_database_status.main.connections_current
    sync_degraded = data.appwrite_postgresql_database_status.main.sync_degraded
  }
}
```

`health` is `healthy`, `degraded`, `unhealthy`, or `unknown` when nothing could be measured. The `replicas` list reports each member's `role`, `healthy`, `replicating`, and `lag_seconds`, and `volumes` reports mount paths and usage. `sync_state_confirmed` says whether the replication fields come from an engine reading rather than a recorded estimate. `false` means no reading was taken, not that replication is unhealthy.

### Looking up existing databases

```hcl
data "appwrite_postgresql_database" "existing" {
  id = "main"
}

data "appwrite_mysql_databases" "ready" {
  queries = ["equal(\"status\", \"ready\")"]
}

output "ready_database_count" {
  value = data.appwrite_mysql_databases.ready.total
}
```

### Importing

You can import most dedicated database resources. Identifiers start with the database ID:

```bash
terraform import appwrite_postgresql_database.main <database-id>
terraform import appwrite_postgresql_pooler.main <database-id>
terraform import appwrite_postgresql_extension.postgis <database-id>/postgis
terraform import appwrite_postgresql_branch.preview <database-id>/<branch-id>
terraform import appwrite_postgresql_backup_policy.nightly <database-id>/<policy-id>
```

Backup storage is the exception. It has no read route, so it cannot be imported.

### Related

- [DocumentsDB](/docs/tooling/terraform/resources/documentsdb): schemaless JSON collections
- [VectorsDB](/docs/tooling/terraform/resources/vectorsdb): embeddings searched by similarity
- [TablesDB](/docs/tooling/terraform/resources/databases): the relational product on shared infrastructure
- [Backups](/docs/tooling/terraform/resources/backups): backup policies for shared databases
- [Configuration](/docs/tooling/terraform/provider): authentication, endpoints, and timeouts

---

## DocumentsDB

URL: https://appwrite.io/docs/tooling/terraform/resources/documentsdb

DocumentsDB stores schemaless JSON documents in collections. The provider exposes it as a database (`appwrite_documentsdb`), then collections, then indexes, and optionally documents for seed data.

For full generated schemas, see the Terraform Registry: [documentsdb](https://registry.terraform.io/providers/appwrite/appwrite/latest/docs/resources/documentsdb), [documentsdb_collection](https://registry.terraform.io/providers/appwrite/appwrite/latest/docs/resources/documentsdb_collection), [documentsdb_index](https://registry.terraform.io/providers/appwrite/appwrite/latest/docs/resources/documentsdb_index), and [documentsdb_document](https://registry.terraform.io/providers/appwrite/appwrite/latest/docs/resources/documentsdb_document).

### Resources

| Resource | Purpose |
|----------|---------|
| `appwrite_documentsdb` | Create a DocumentsDB database in your project |
| `appwrite_documentsdb_collection` | Create a collection within a database |
| `appwrite_documentsdb_index` | Index one or more document attributes |
| `appwrite_documentsdb_document` | Manage seed and reference documents |

### Data sources

| Data source | Purpose |
|-------------|---------|
| `appwrite_documentsdb` | Look up a database by ID |
| `appwrite_documentsdb_specifications` | List the compute specifications your billing plan allows |

**DocumentsDB has its own API key scopes**

DocumentsDB does not use the TablesDB scopes (`tables.*`, `rows.*`) or the deprecated `collections.*` and `documents.*` ones. Give the key `documentsdb.read` and `documentsdb.write` for databases, `documentsdb.collections.read` and `documentsdb.collections.write` for collections and indexes, and `documentsdb.documents.read` and `documentsdb.documents.write` for documents.

### Creating a database

A database needs only a `name`:

```hcl
resource "appwrite_documentsdb" "main" {
  name = "main"
}
```

Setting `specification` instead places the database on dedicated infrastructure reserved for your project, which is billed separately. Each product publishes its own catalog of compute sizes, so size a DocumentsDB database from the DocumentsDB specifications rather than a dedicated engine's.

```hcl
data "appwrite_documentsdb_specifications" "available" {}

output "available_specifications" {
  value = [
    for s in data.appwrite_documentsdb_specifications.available.specifications :
    { slug = s.slug, cpu = s.cpu, memory = s.memory, price = s.price }
    if s.enabled
  ]
}

resource "appwrite_documentsdb" "production" {
  name          = "production"
  specification = "s-2vcpu-4gb"
  replicas      = 1
  sync_mode     = "sync"
}
```

Set the slug you want rather than deriving one from the catalog output. For a `precondition` that fails the plan when a slug is not enabled on your billing plan, see [asserting a specification at plan time](/docs/tooling/terraform/resources/dedicated-databases#sizing-from-the-specifications-data-source).

**Some deployments require a specification**

Omitting `specification` runs the database on the deployment's shared pool. Not every deployment has one configured. Where none is, the API rejects creation with `dedicated_database_required`, and `specification` becomes required.

`replicas` counts high availability replicas on the dedicated backing, not counting the primary. `sync_mode` (`async`, `sync`, or `quorum`) applies only when `replicas` is greater than 0. Creating a database with a dedicated backing waits for that backing to finish provisioning, so a collection is never created against a database that is still starting.

Set `enabled = false` to make a database unreachable for your users while an API key can still reach it. Read-only attributes report `type`, `engine`, `status`, `created_at`, and `updated_at`. `engine` and `status` are empty when the database has no dedicated backing.

### Collections

A collection holds the documents. You set permissions at the collection level, and `document_security` enforces per-document permissions on top of them.

```hcl
resource "appwrite_documentsdb_collection" "articles" {
  database_id = appwrite_documentsdb.main.id
  id          = "articles"
  name        = "Articles"

  permissions       = ["read(\"any\")", "create(\"users\")"]
  document_security = true

  attributes = jsonencode([
    {
      key      = "slug"
      type     = "string"
      size     = 255
      required = true
    },
    {
      key      = "published_at"
      type     = "datetime"
      required = false
    },
  ])
}
```

**Attributes are create-only**

Appwrite applies `attributes` only when the collection is created. There is no route to add, change, or remove one afterwards, so changing this argument replaces the collection and discards its documents. Terraform does not refresh it from the server either, so drift on it goes undetected. An index can only be built on a declared attribute, so declare anything you intend to index here.

### Indexes

```hcl
resource "appwrite_documentsdb_index" "by_slug" {
  database_id   = appwrite_documentsdb.main.id
  collection_id = appwrite_documentsdb_collection.articles.id
  key           = "by_slug"
  type          = "unique"
  attributes    = ["slug"]
}

resource "appwrite_documentsdb_index" "by_published" {
  database_id   = appwrite_documentsdb.main.id
  collection_id = appwrite_documentsdb_collection.articles.id
  key           = "by_published"
  type          = "key"
  attributes    = ["published_at"]
  orders        = ["DESC"]
}
```

`type` is `key`, `unique`, or `fulltext`, depending on the attribute being indexed. `orders` (`ASC` or `DESC`) and `lengths` are positional, matching `attributes` entry for entry.

Indexes have no update route, so changing any argument replaces the index. Terraform waits for a new index to become available, so a dependent resource is never handed one that is still building. The read-only `status` reports `available`, `processing`, `deleting`, `stuck`, or `failed`, and `error` explains a failed build.

### Documents

The document resource manages data rather than infrastructure, which fits seed and reference records:

```hcl
resource "appwrite_documentsdb_collection" "settings" {
  database_id = appwrite_documentsdb.main.id
  name        = "Settings"
}

resource "appwrite_documentsdb_document" "defaults" {
  database_id   = appwrite_documentsdb.main.id
  collection_id = appwrite_documentsdb_collection.settings.id
  id            = "defaults"

  data = jsonencode({
    theme       = "dark"
    locale      = "en-GB"
    max_uploads = 25
  })
}
```

Terraform tracks only the keys present in `data`, so fields written by other clients do not show up as drift. Do not manage documents your application writes at runtime here. Every apply would fight the application. `permissions` on a document is only enforced when the collection has `document_security` enabled.

### Looking up a database

```hcl
data "appwrite_documentsdb" "existing" {
  id = "main"
}

resource "appwrite_documentsdb_collection" "example" {
  database_id = data.appwrite_documentsdb.existing.id
  name        = "Example"
}
```

### Importing

```bash
terraform import appwrite_documentsdb.main <database-id>
terraform import appwrite_documentsdb_collection.articles <database-id>/<collection-id>
terraform import appwrite_documentsdb_index.by_slug <database-id>/<collection-id>/<key>
terraform import appwrite_documentsdb_document.defaults <database-id>/<collection-id>/<document-id>
```

### Related

- [VectorsDB](/docs/tooling/terraform/resources/vectorsdb): the same shape, for embeddings
- [Dedicated databases](/docs/tooling/terraform/resources/dedicated-databases): PostgreSQL, MySQL, and MongoDB
- [TablesDB](/docs/tooling/terraform/resources/databases): the relational product on shared infrastructure
- [Configuration](/docs/tooling/terraform/provider): authentication and endpoints

---

## Custom domains

URL: https://appwrite.io/docs/tooling/terraform/resources/domains

The `appwrite_proxy_rule` resource attaches a custom domain to an Appwrite site or function. Appwrite verifies the domain and i