diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..fd3b249 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,12 @@ +.git +.github +.ruff_cache +.venv +__pycache__ +*.pyc +.env +.env.* +!.env.example +deploy +images +tests diff --git a/.env.example b/.env.example index a67996a..3eb6e7b 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,27 @@ -APPWRITE_API_KEY= -APPWRITE_PROJECT_ID= -APPWRITE_ENDPOINT= \ No newline at end of file +# Appwrite Cloud base endpoint. The served project's OAuth authorization server +# lives under it at /oauth2/. +APPWRITE_ENDPOINT=https://cloud.appwrite.io/v1 + +# External URL clients use to reach THIS MCP server. Used to build the canonical +# resource URI (/mcp) and RFC 9728 metadata. For hosted +# deployment, set this to your public HTTPS origin, for example +# https://mcp.appwrite.io. +MCP_PUBLIC_URL=http://localhost:8000 + +# The single Appwrite project this MCP serves. Defaults to the Cloud console +# project; override only for non-console deployments. Tokens issued by any other +# project's OAuth server are rejected. +# APPWRITE_PROJECT_ID=console + +# HTTP bind address. +HOST=0.0.0.0 +PORT=8000 + +# Optional. Enables appwrite_search_docs by embedding incoming docs queries. +# OPENAI_API_KEY=sk-... + +# ── Integration tests only (not used by the running server) ───────────────── +# The live integration suite authenticates to the Appwrite API with an API key +# and runs against APPWRITE_PROJECT_ID (above). +# APPWRITE_PROJECT_ID=your-project-id +# APPWRITE_API_KEY=your-api-key diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9882672..999ffba 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,24 +5,29 @@ on: push: jobs: - format: - name: Format + lint: + name: Lint & format runs-on: ubuntu-latest steps: - name: Check out code - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 with: python-version: "3.12" - name: Set up uv - uses: astral-sh/setup-uv@v7 + uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0 + with: + version: "0.11.22" - name: Install dev dependencies run: uv sync --group dev + - name: Lint + run: uv run --group dev ruff check src tests + - name: Check formatting run: uv run --group dev black --check src tests @@ -31,15 +36,17 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out code - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 with: python-version: "3.12" - name: Set up uv - uses: astral-sh/setup-uv@v7 + uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0 + with: + version: "0.11.22" - name: Install dependencies run: uv sync @@ -47,6 +54,16 @@ jobs: - name: Run unit tests run: uv run python -m unittest discover -s tests/unit -v + docker: + name: Docker build + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + + - name: Build hosted HTTP image + run: docker build -t appwrite-mcp:ci . + integration: name: Integration runs-on: ubuntu-latest @@ -57,15 +74,17 @@ jobs: APPWRITE_ENDPOINT: ${{ secrets.APPWRITE_ENDPOINT }} steps: - name: Check out code - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 with: python-version: "3.12" - name: Set up uv - uses: astral-sh/setup-uv@v7 + uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0 + with: + version: "0.11.22" - name: Install dependencies run: uv sync --extra integration diff --git a/.github/workflows/production.yml b/.github/workflows/production.yml new file mode 100644 index 0000000..a4a9660 --- /dev/null +++ b/.github/workflows/production.yml @@ -0,0 +1,74 @@ +name: Production deployment + +on: + release: + types: [published] + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }} + cancel-in-progress: false + +env: + ENVIRONMENT: production + PROJECT: mcp + DECLARATIVE_OWNER: appwrite-labs + DECLARATIVE_REPOSITORY: assets-applications + REGISTRY_GITHUB: ghcr.io + IMAGE_NAME: appwrite/mcp + TAG: ${{ github.event.release.tag_name || github.sha }} + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout the repo + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 + + - name: Login to GitHub Container Registry + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 + with: + registry: ${{ env.REGISTRY_GITHUB }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push + uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6 + with: + context: . + push: true + tags: ${{ env.REGISTRY_GITHUB }}/${{ env.IMAGE_NAME }}:${{ env.TAG }} + + deploy: + needs: build + runs-on: ubuntu-latest + steps: + - name: Get token for ${{ env.DECLARATIVE_REPOSITORY }} + id: app-token + uses: actions/create-github-app-token@fee1f7d63c2ff003460e3d139729b119787bc349 # v2 + with: + app-id: ${{ vars.DECLARATIVE_DEPLOYMENT_GITHUB_APP_ID }} + private-key: ${{ secrets.DECLARATIVE_DEPLOYMENT_GITHUB_APP_PRIVATE_KEY }} + owner: ${{ env.DECLARATIVE_OWNER }} + repositories: ${{ env.DECLARATIVE_REPOSITORY }} + + - name: Checkout ${{ env.DECLARATIVE_REPOSITORY }} + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 + with: + repository: ${{ env.DECLARATIVE_OWNER }}/${{ env.DECLARATIVE_REPOSITORY }} + token: ${{ steps.app-token.outputs.token }} + + - name: Update image tag + run: yq -i '.["mcp"].image.tag = strenv(TAG)' ${{ env.ENVIRONMENT }}/${{ env.PROJECT }}/fra1.yaml + + - name: Commit and push + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add ${{ env.ENVIRONMENT }}/${{ env.PROJECT }}/fra1.yaml + if git diff --cached --quiet; then + echo "No changes to commit" + else + git commit -m "chore(${{ env.ENVIRONMENT }}): ${{ env.PROJECT }} image tag to ${{ env.TAG }}" + git push + fi diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 67a7e10..1970122 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -10,17 +10,17 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out code - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 with: python-version: "3.12" - name: Install build dependencies run: | python -m pip install --upgrade pip - pip install build twine + pip install build==1.5.0 twine==6.2.0 - name: Build package run: python -m build diff --git a/.github/workflows/staging.yml b/.github/workflows/staging.yml new file mode 100644 index 0000000..5a1de6f --- /dev/null +++ b/.github/workflows/staging.yml @@ -0,0 +1,75 @@ +name: Staging deployment + +on: + workflow_dispatch: + push: + branches: + - main + +concurrency: + group: ${{ github.workflow }} + cancel-in-progress: false + +env: + ENVIRONMENT: staging + PROJECT: mcp + DECLARATIVE_OWNER: appwrite-labs + DECLARATIVE_REPOSITORY: assets-applications + REGISTRY_GITHUB: ghcr.io + IMAGE_NAME: appwrite/mcp + TAG: ${{ github.sha }} + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout the repo + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 + + - name: Login to GitHub Container Registry + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 + with: + registry: ${{ env.REGISTRY_GITHUB }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push + uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6 + with: + context: . + push: true + tags: ${{ env.REGISTRY_GITHUB }}/${{ env.IMAGE_NAME }}:${{ env.TAG }} + + deploy: + needs: build + runs-on: ubuntu-latest + steps: + - name: Get token for ${{ env.DECLARATIVE_REPOSITORY }} + id: app-token + uses: actions/create-github-app-token@fee1f7d63c2ff003460e3d139729b119787bc349 # v2 + with: + app-id: ${{ vars.DECLARATIVE_DEPLOYMENT_GITHUB_APP_ID }} + private-key: ${{ secrets.DECLARATIVE_DEPLOYMENT_GITHUB_APP_PRIVATE_KEY }} + owner: ${{ env.DECLARATIVE_OWNER }} + repositories: ${{ env.DECLARATIVE_REPOSITORY }} + + - name: Checkout ${{ env.DECLARATIVE_REPOSITORY }} + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 + with: + repository: ${{ env.DECLARATIVE_OWNER }}/${{ env.DECLARATIVE_REPOSITORY }} + token: ${{ steps.app-token.outputs.token }} + + - name: Update image tag + run: yq -i '.["mcp"].image.tag = strenv(TAG)' ${{ env.ENVIRONMENT }}/${{ env.PROJECT }}/fra1.yaml + + - name: Commit and push + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add ${{ env.ENVIRONMENT }}/${{ env.PROJECT }}/fra1.yaml + if git diff --cached --quiet; then + echo "No changes to commit" + else + git commit -m "chore(${{ env.ENVIRONMENT }}): ${{ env.PROJECT }} image tag to ${{ env.TAG }}" + git push + fi diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..090dcd6 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,22 @@ +FROM python:3.12-slim AS base + +ENV PYTHONUNBUFFERED=1 \ + UV_COMPILE_BYTECODE=1 \ + UV_LINK_MODE=copy + +WORKDIR /app + +COPY --from=ghcr.io/astral-sh/uv:0.11.22 /uv /usr/local/bin/uv + +COPY pyproject.toml uv.lock README.md ./ +COPY src ./src + +RUN uv sync --frozen --no-dev + +ENV HOST=0.0.0.0 \ + PORT=8000 \ + APPWRITE_ENDPOINT=https://cloud.appwrite.io/v1 + +EXPOSE 8000 + +CMD ["uv", "run", "mcp-server-appwrite"] diff --git a/README.md b/README.md index 85a4f7a..aa56469 100644 --- a/README.md +++ b/README.md @@ -1,248 +1,122 @@ # Appwrite MCP server -mcp-name: io.github.appwrite/mcp-for-api - -[![Install MCP Server](https://cursor.com/deeplink/mcp-install-light.svg)](https://cursor.com/install-mcp?name=appwrite&config=%7B%22command%22%3A%22uvx%20mcp-server-appwrite%22%2C%22env%22%3A%7B%22APPWRITE_API_KEY%22%3A%22%3Cyour-api-key%3E%22%2C%22APPWRITE_PROJECT_ID%22%3A%22%3Cyour-project-id%3E%22%2C%22APPWRITE_ENDPOINT%22%3A%22https%3A//%3CREGION%3E.cloud.appwrite.io/v1%22%7D%7D) +mcp-name: io.github.appwrite/mcp ## Overview -A Model Context Protocol server for interacting with Appwrite's API. This server provides tools to manage databases, users, functions, teams, and more within your Appwrite project. +A Model Context Protocol server for interacting with Appwrite's API. It provides tools to manage databases, users, functions, teams, and more within your Appwrite project. + +The server is a hosted [OAuth 2.1 Resource Server](https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization) served over the MCP [Streamable HTTP](https://modelcontextprotocol.io/specification/2025-06-18/basic/transports) transport. It targets the Appwrite Cloud console project; users authenticate with that project's OAuth 2.1 authorization server, and no API keys are distributed to clients. ## Quick Links -- [Configuration](#configuration) -- [Installation](#installation) -- IDE Integration: - - [Claude Desktop](#usage-with-claude-desktop) - - [Cursor](#usage-with-cursor) - - [Windsurf Editor](#usage-with-windsurf-editor) - - [VS Code](#usage-with-vs-code) -- [Local Development](#local-development) +- [Connecting a client](#connecting-a-client) +- [How authentication works](#how-authentication-works) +- [Tool surface](#tool-surface) +- [Local development](#local-development) - [Debugging](#debugging) -## Configuration - -> Before launching the MCP server, you must setup an [Appwrite project](https://cloud.appwrite.io/) and create an API key with the necessary scopes enabled. - -The server validates the credentials and scopes required for its built-in Appwrite service set during startup. If the endpoint, project ID, API key, or scopes are wrong, the MCP server will fail to start instead of waiting for the first tool call to fail. - -Create a `.env` file in your working directory and add the following: +## Connecting a client -```env -APPWRITE_PROJECT_ID=your-project-id -APPWRITE_API_KEY=your-api-key -APPWRITE_ENDPOINT=https://.cloud.appwrite.io/v1 -``` - -Then, open your terminal and run the following command - -### Linux and MacOS +Add the server to any MCP client that supports remote (Streamable HTTP) servers by its URL: -```sh -source .env ``` - -### Windows - -#### Command Prompt - -```cmd -for /f "tokens=1,2 delims==" %A in (.env) do set %A=%B +https://mcp.appwrite.io/mcp ``` -#### PowerShell +For example, in a client that accepts a JSON server config: -```powershell -Get-Content .\.env | ForEach-Object { - if ($_ -match '^(.*?)=(.*)$') { - [System.Environment]::SetEnvironmentVariable($matches[1], $matches[2], "Process") +```json +{ + "mcpServers": { + "appwrite": { + "type": "http", + "url": "https://mcp.appwrite.io/mcp" + } } } ``` -## Installation - -### Using uv (recommended) -When using [`uv`](https://docs.astral.sh/uv/) no specific installation is needed. We will -use [`uvx`](https://docs.astral.sh/uv/guides/tools/) to directly run *mcp-server-appwrite*. - -```bash -uvx mcp-server-appwrite -``` +The first time you connect, the client opens an Appwrite consent screen in your browser. Approve the requested scopes and the client is connected — there are no keys to copy. -### Using pip +## How authentication works -```bash -pip install mcp-server-appwrite -``` -Then run the server using +The MCP server validates the bearer access token on every request and forwards it to the Appwrite REST API, which accepts the OAuth2 access token directly. The flow (handled automatically by MCP-aware clients): -```bash -python -m mcp_server_appwrite -``` +1. The client requests `/mcp` without a token and receives `401` with a `WWW-Authenticate` header pointing to the protected-resource metadata. +2. The client fetches `GET /.well-known/oauth-protected-resource/mcp` (RFC 9728), which lists the authorization server (`/oauth2/console`) and supported scopes. +3. The client discovers the authorization server (RFC 8414 / OIDC) and **self-registers** via RFC 7591 Dynamic Client Registration — the OAuth server exposes an open `registration_endpoint`, so there is no client ID or secret to pre-provision. MCP clients register as public (PKCE) clients automatically. +4. The client runs the OAuth 2.1 + PKCE authorization-code flow, including the RFC 8707 `resource` parameter that binds the token's audience to this server. +5. The client calls `/mcp` with `Authorization: Bearer `. -### Tool surface +## Tool surface -The server no longer accepts service-selection or mode flags. It always starts in a compact workflow so the MCP client only sees a small operator-style surface while the full Appwrite catalog stays internal. +The server starts in a compact workflow so the MCP client only sees a small operator-style surface while the full Appwrite catalog stays internal. -- Only 2 MCP tools are exposed to the model: +- Up to 3 MCP tools are exposed to the model: - `appwrite_search_tools` - `appwrite_call_tool` + - `appwrite_search_docs` — semantic search over the Appwrite documentation (only registered when the docs index and `OPENAI_API_KEY` are present; see [Documentation search](#documentation-search)). - The full Appwrite tool catalog stays internal and is searched at runtime. - Large tool outputs are stored as MCP resources and returned as preview text plus a resource URI. - Mutating hidden tools require `confirm_write=true`. -- The server automatically registers all supported Appwrite services except the legacy Databases API. - -If you still have older MCP configs that pass flags such as `--mode` or `--users`, remove them. - -## Usage with Claude Desktop - -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. Clicking on the **Edit Config** button will take you to the `claude_desktop_config.json` file, where you must add the following: - -```json -{ - "mcpServers": { - "appwrite": { - "command": "uvx", - "args": [ - "mcp-server-appwrite" - ], - "env": { - "APPWRITE_PROJECT_ID": "", - "APPWRITE_API_KEY": "", - "APPWRITE_ENDPOINT": "https://.cloud.appwrite.io/v1" // Optional - } - } - } -} +- Every Appwrite service the installed SDK ships is registered automatically — 25 in total, each becoming a tool-name prefix: `account`, `activities`, `advisor`, `apps`, `avatars`, `backups`, `databases`, `functions`, `graphql`, `health`, `locale`, `messaging`, `oauth2`, `organization`, `presences`, `project`, `proxy`, `sites`, `storage`, `tables_db`, `teams`, `tokens`, `usage`, `users`, and `webhooks`. Which ones a given user can actually call is gated by the scopes their OAuth token was granted (enforced per-route by the Appwrite API), not by the catalog. -``` +## Documentation search -> Note: In case you see a `uvx ENOENT` error, ensure that you either add `uvx` to the `PATH` environment variable on your system or use the full path to your `uvx` installation in the config file. +`appwrite_search_docs` runs semantic search over the Appwrite documentation entirely in-process, replacing the standalone docs MCP server. It embeds the query with OpenAI `text-embedding-3-small` and ranks a prebuilt index of doc pages by cosine similarity, returning the most relevant pages with their full content. It needs no `project_id`. -Upon successful configuration, you should be able to see the server in the list of available servers in Claude Desktop. +The index is a small artifact committed under `src/mcp_server_appwrite/data/` (`docs_index.npz` + `docs_index_meta.json`) and shipped in the image. The tool is registered only when both the artifact and `OPENAI_API_KEY` are available; otherwise the server boots without it. -![Claude Desktop Config](images/claude-desktop-integration.png) +### Runtime configuration -## Usage with [Cursor](https://www.cursor.com/) +- `OPENAI_API_KEY` — required to embed incoming queries (one OpenAI call per search). +- `DOCS_SEARCH_MIN_SCORE` — minimum cosine score for a match (default `0.25`). +- `DOCS_SEARCH_LIMIT` — default maximum pages returned (default `5`, max `10`). -Head to Cursor `Settings > MCP` and click on **Add new MCP server**. Choose the type as `Command` and add the command below to the **Command** field. +### Rebuilding the index -- **MacOS** +Re-run the build script when the docs change and commit the refreshed artifact: ```bash -env APPWRITE_API_KEY=your-api-key env APPWRITE_PROJECT_ID=your-project-id uvx mcp-server-appwrite -``` - -- **Windows** - -```cmd -cmd /c SET APPWRITE_PROJECT_ID=your-project-id && SET APPWRITE_API_KEY=your-api-key && uvx mcp-server-appwrite -``` - -![Cursor Settings](./images/cursor-integration.png) - -## Usage with [Windsurf Editor](https://codeium.com/windsurf) - -Head to Windsurf `Settings > Cascade > Model Context Protocol (MCP) Servers` and click on **View raw config**. Update the `mcp_config.json` file to include the following: - -```json -{ - "mcpServers": { - "appwrite": { - "command": "uvx", - "args": [ - "mcp-server-appwrite" - ], - "env": { - "APPWRITE_PROJECT_ID": "", - "APPWRITE_API_KEY": "", - "APPWRITE_ENDPOINT": "https://.cloud.appwrite.io/v1" // Optional - } - } - } -} -``` - -![Windsurf Settings](./images/windsurf-integration.png) - -## Usage with [VS Code](https://code.visualstudio.com/) - -### Configuration - -1. **Update the MCP configuration file**: Open the Command Palette (`Ctrl+Shift+P` or `Cmd+Shift+P`) and run `MCP: Open User Configuration`. It should open the `mcp.json` file in your user settings. - -2. **Add the Appwrite MCP server configuration**: Add the following to the `mcp.json` file: - -```json -{ - "servers": { - "appwrite": { - "command": "uvx", - "args": ["mcp-server-appwrite"], - "env": { - "APPWRITE_PROJECT_ID": "", - "APPWRITE_API_KEY": "", - "APPWRITE_ENDPOINT": "https://.cloud.appwrite.io/v1" - } - } - } -} +OPENAI_API_KEY=sk-... uv run python scripts/build_docs_index.py ``` -3. **Start the MCP server**: Open the Command Palette (`Ctrl+Shift+P` or `Cmd+Shift+P`) and run `MCP: List Servers`. In the dropdown, select `appwrite` and click on the **Start Server** button. - -4. **Use in Copilot Chat**: Open Copilot Chat and switch to **Agent Mode** to access the Appwrite tools. - -![VS Code Settings](./images/vs-code-integration.png) - -## Local Development - -### Clone the repository - -```bash -git clone https://github.com/appwrite/mcp-for-api.git -``` +It downloads `appwrite/website` docs from GitHub, chunks each page, embeds the chunks, and writes the artifact. Optional env vars: `DOCS_WEBSITE_REF` (git ref, default `main`), `DOCS_EMBED_BATCH` (default `100`). -### Install `uv` +## Local development -- Linux or MacOS +### Clone and install `uv` ```bash +git clone https://github.com/appwrite/mcp.git +cd mcp +# Linux or MacOS curl -LsSf https://astral.sh/uv/install.sh | sh +# Windows (PowerShell) +# powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex" ``` -- Windows (PowerShell) - -```powershell -powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex" -``` - -### Prepare virtual environment +### Run the server -First, create a virtual environment. +With Docker Compose: ```bash -uv venv +docker compose up --build ``` -Next, activate the virtual environment. +Compose defaults to `MCP_PUBLIC_URL=http://localhost:8000` and exposes the MCP endpoint at: -- Linux or MacOS - -```bash -source .venv/bin/activate +```text +http://localhost:8000/mcp ``` -- Windows +To enable documentation search locally, provide `OPENAI_API_KEY` in your shell or a local `.env` file before running Compose. -```powershell -.venv\Scripts\activate -``` - -### Run the server +With `uv` directly: ```bash -uv run -v --directory ./ mcp-server-appwrite +MCP_PUBLIC_URL=http://localhost:8000 APPWRITE_ENDPOINT=https://cloud.appwrite.io/v1 \ + uv run mcp-server-appwrite ``` ## Testing @@ -255,7 +129,7 @@ uv run python -m unittest discover -s tests/unit -v ### Live integration tests -These tests create and delete real Appwrite resources against a real Appwrite project. They run automatically when valid Appwrite credentials are available in the environment or `.env`. +These create and delete real Appwrite resources against a real project. They authenticate to the Appwrite API with an API key supplied via the environment or `.env` (`APPWRITE_PROJECT_ID`, `APPWRITE_API_KEY`, `APPWRITE_ENDPOINT`) and are skipped when no credentials are present. ```bash uv run --extra integration python -m unittest discover -s tests/integration -v @@ -263,16 +137,13 @@ uv run --extra integration python -m unittest discover -s tests/integration -v ## Debugging -You can use the MCP inspector to debug the server. +Use the MCP Inspector against a running server URL: ```bash -npx @modelcontextprotocol/inspector \ - uv \ - --directory . \ - run mcp-server-appwrite +npx @modelcontextprotocol/inspector ``` -Make sure your `.env` file is properly configured before running the inspector. You can then access the inspector at `http://localhost:5173`. +Point it at `https://mcp.appwrite.io/mcp` and complete the OAuth flow when prompted. ## License diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 0000000..e5de1a8 --- /dev/null +++ b/compose.yaml @@ -0,0 +1,28 @@ +services: + appwrite-mcp: + build: + context: . + image: appwrite-mcp:local + ports: + - "${PORT:-8000}:8000" + environment: + HOST: 0.0.0.0 + PORT: 8000 + APPWRITE_ENDPOINT: ${APPWRITE_ENDPOINT:-https://cloud.appwrite.io/v1} + APPWRITE_PROJECT_ID: ${APPWRITE_PROJECT_ID:-console} + MCP_PUBLIC_URL: ${MCP_PUBLIC_URL:-http://localhost:8000} + OPENAI_API_KEY: ${OPENAI_API_KEY:-} + DOCS_SEARCH_MIN_SCORE: ${DOCS_SEARCH_MIN_SCORE:-0.25} + DOCS_SEARCH_LIMIT: ${DOCS_SEARCH_LIMIT:-5} + healthcheck: + test: + [ + "CMD", + "python", + "-c", + "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/healthz', timeout=2).read()", + ] + interval: 10s + timeout: 3s + retries: 3 + start_period: 10s diff --git a/pyproject.toml b/pyproject.toml index ad852d5..b24a433 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,14 +1,20 @@ [project] name = "mcp-server-appwrite" -version = "0.4.1" +version = "0.7.0" description = "MCP (Model Context Protocol) server for Appwrite" readme = "README.md" requires-python = ">=3.12" dependencies = [ - "appwrite>=13.4.1,<16", + "appwrite>=21.0.0,<22", "docstring-parser>=0.16", - "mcp[cli]>=1.3.0", + "mcp[cli]>=1.12.0", "python-dotenv>=1.0.1", + "starlette>=0.40.0", + "uvicorn[standard]>=0.30.0", + "httpx>=0.27.0", + "pyjwt[crypto]>=2.9.0", + "numpy>=2.0", + "openai>=1.40", ] [project.optional-dependencies] @@ -22,14 +28,31 @@ integration = [ [dependency-groups] dev = [ "black>=25.1.0", + "ruff>=0.10.0", + # Only needed by scripts/build_docs_index.py to (re)build the docs index. + "pyyaml>=6.0", ] [project.scripts] mcp-server-appwrite = "mcp_server_appwrite.__main__:main" +[tool.hatch.build.targets.wheel] +# Ship the prebuilt docs search index (non-.py package data) in the wheel. +artifacts = ["src/mcp_server_appwrite/data/*"] + [tool.black] target-version = ["py312"] +[tool.ruff] +target-version = "py312" +line-length = 88 + +[tool.ruff.lint] +# E/W: pycodestyle, F: pyflakes (real bugs), I: import sorting. +# Line length (E501) is owned by black, so it is disabled here. +select = ["E", "F", "W", "I"] +ignore = ["E501"] + [build-system] requires = ["hatchling"] build-backend = "hatchling.build" diff --git a/scripts/build_docs_index.py b/scripts/build_docs_index.py new file mode 100644 index 0000000..cc603e7 --- /dev/null +++ b/scripts/build_docs_index.py @@ -0,0 +1,219 @@ +"""Build the committed semantic-search index for the Appwrite documentation. + +This downloads the Appwrite docs from GitHub, chunks each page's markdown, embeds +the chunks with OpenAI ``text-embedding-3-small``, and writes a small artifact +that the running server loads at startup (see ``mcp_server_appwrite/docs_search.py``). + +Run this when the docs change and commit the refreshed artifact: + + OPENAI_API_KEY=sk-... uv run python scripts/build_docs_index.py + +Outputs (committed into the repo, shipped in the image / wheel): + src/mcp_server_appwrite/data/docs_index.npz float32 vectors + chunk->page map + src/mcp_server_appwrite/data/docs_index_meta.json page metadata (path/title/desc/content) + +Env vars: + OPENAI_API_KEY required. + DOCS_WEBSITE_REF git ref of appwrite/website to index (default "main"). + DOCS_EMBED_BATCH embedding batch size (default 100). +""" + +from __future__ import annotations + +import io +import json +import os +import re +import sys +import tarfile +from pathlib import Path + +import httpx +import numpy as np +import yaml +from openai import OpenAI + +EMBED_MODEL = "text-embedding-3-small" +EMBED_DIMENSION = 1536 +GITHUB_OWNER = "appwrite" +GITHUB_REPO = "website" +DOCS_SUBDIR = "src/routes/docs" + +# Approximate Mastra's markdown chunking: header-aware sections packed to ~1500 +# chars with ~200 chars of overlap. Exact sizing is not load-bearing — retrieval +# quality is dominated by the (identical) embedding model. +CHUNK_SIZE = 1500 +CHUNK_OVERLAP = 200 + +DATA_DIR = ( + Path(__file__).resolve().parent.parent / "src" / "mcp_server_appwrite" / "data" +) + + +def download_docs(ref: str) -> dict[str, str]: + """Download appwrite/website and return {webPath: raw .markdoc text}.""" + url = f"https://codeload.github.com/{GITHUB_OWNER}/{GITHUB_REPO}/tar.gz/{ref}" + print(f"Downloading {GITHUB_OWNER}/{GITHUB_REPO}@{ref} ...") + response = httpx.get(url, follow_redirects=True, timeout=120.0) + response.raise_for_status() + + pages: dict[str, str] = {} + with tarfile.open(fileobj=io.BytesIO(response.content), mode="r:gz") as tar: + for member in tar.getmembers(): + if not member.isfile() or not member.name.endswith(".markdoc"): + continue + # member.name == "website-/src/routes/docs/.../+page.markdoc" + parts = member.name.split("/", 1) + if len(parts) != 2: + continue + repo_relative = parts[1] + if not repo_relative.startswith(DOCS_SUBDIR + "/"): + continue + fileobj = tar.extractfile(member) + if fileobj is None: + continue + text = fileobj.read().decode("utf-8", errors="replace") + inner = repo_relative[len(DOCS_SUBDIR) + 1 :] # strip "src/routes/docs/" + web_path = ("docs/" + inner).replace("/+page.markdoc", "") + pages[web_path] = text + + print(f"Found {len(pages)} .markdoc pages") + return pages + + +def parse_front_matter(text: str) -> tuple[dict[str, str], str]: + """Split YAML front-matter from the markdown body.""" + if text.startswith("---"): + match = re.match(r"^---\n(.*?)\n---\n?(.*)$", text, re.DOTALL) + if match: + try: + attributes = yaml.safe_load(match.group(1)) or {} + except yaml.YAMLError: + attributes = {} + if not isinstance(attributes, dict): + attributes = {} + return attributes, match.group(2) + return {}, text + + +def chunk_markdown(text: str) -> list[str]: + """Header-aware markdown chunking approximating Mastra's markdown strategy.""" + text = text.strip() + if not text: + return [] + + # Split into header-delimited sections, keeping the header with its body. + sections: list[str] = [] + current: list[str] = [] + for line in text.splitlines(): + if re.match(r"^#{1,6}\s", line) and current: + sections.append("\n".join(current).strip()) + current = [line] + else: + current.append(line) + if current: + sections.append("\n".join(current).strip()) + + chunks: list[str] = [] + for section in sections: + if not section: + continue + if len(section) <= CHUNK_SIZE: + chunks.append(section) + continue + # Hard-split oversized sections with overlap. + start = 0 + while start < len(section): + end = start + CHUNK_SIZE + chunks.append(section[start:end].strip()) + if end >= len(section): + break + start = end - CHUNK_OVERLAP + return [chunk for chunk in chunks if chunk] + + +def embed_texts(client: OpenAI, texts: list[str], batch_size: int) -> np.ndarray: + vectors: list[list[float]] = [] + for start in range(0, len(texts), batch_size): + batch = texts[start : start + batch_size] + print(f"Embedding {start + 1}-{start + len(batch)} of {len(texts)} ...") + response = client.embeddings.create(model=EMBED_MODEL, input=batch) + vectors.extend(item.embedding for item in response.data) + matrix = np.asarray(vectors, dtype=np.float32) + # L2-normalize so cosine similarity is a dot product at query time. + norms = np.linalg.norm(matrix, axis=1, keepdims=True) + norms[norms == 0] = 1.0 + return matrix / norms + + +def main() -> int: + # Load OPENAI_API_KEY (and friends) from a local .env, like the server does. + try: + from dotenv import load_dotenv + + load_dotenv() + except ImportError: + pass + + if not os.getenv("OPENAI_API_KEY"): + print("OPENAI_API_KEY is not set", file=sys.stderr) + return 1 + + ref = os.getenv("DOCS_WEBSITE_REF", "main") + batch_size = int(os.getenv("DOCS_EMBED_BATCH", "100")) + client = OpenAI() + + raw_pages = download_docs(ref) + + pages: list[dict[str, str]] = [] + chunk_texts: list[str] = [] + chunk_page: list[int] = [] + + for web_path, raw in sorted(raw_pages.items()): + attributes, body = parse_front_matter(raw) + chunks = chunk_markdown(body) + if not chunks: + continue + page_index = len(pages) + pages.append( + { + "path": web_path, + "title": str(attributes.get("title", "")), + "description": str(attributes.get("description", "")), + "content": body.strip(), + } + ) + for chunk in chunks: + chunk_texts.append(chunk) + chunk_page.append(page_index) + + if not chunk_texts: + print("No chunks produced; aborting", file=sys.stderr) + return 1 + + print(f"Indexing {len(chunk_texts)} chunks across {len(pages)} pages") + vectors = embed_texts(client, chunk_texts, batch_size) + if vectors.shape[1] != EMBED_DIMENSION: + print(f"Unexpected embedding dimension {vectors.shape[1]}", file=sys.stderr) + return 1 + + DATA_DIR.mkdir(parents=True, exist_ok=True) + np.savez_compressed( + DATA_DIR / "docs_index.npz", + vectors=vectors, + chunk_page=np.asarray(chunk_page, dtype=np.int32), + ) + (DATA_DIR / "docs_index_meta.json").write_text( + json.dumps( + {"model": EMBED_MODEL, "dimension": EMBED_DIMENSION, "pages": pages}, + ensure_ascii=False, + ), + encoding="utf-8", + ) + + print(f"Wrote {vectors.shape[0]} vectors and {len(pages)} pages to {DATA_DIR}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/server.json b/server.json index 8ed4f55..9e7b727 100644 --- a/server.json +++ b/server.json @@ -1,20 +1,16 @@ { "$schema": "https://static.modelcontextprotocol.io/schemas/2025-09-29/server.schema.json", - "name": "io.github.appwrite/mcp-for-api", + "name": "io.github.appwrite/mcp", "description": "MCP (Model Context Protocol) server for Appwrite", - "version": "0.4.1", + "version": "0.6.0", "repository": { - "url": "https://github.com/appwrite/mcp-for-api", + "url": "https://github.com/appwrite/mcp", "source": "github" }, - "packages": [ + "remotes": [ { - "version": "0.4.1", - "registryType": "pypi", - "identifier": "mcp-server-appwrite", - "transport": { - "type": "stdio" - } + "type": "streamable-http", + "url": "https://mcp.appwrite.io/mcp" } ] } diff --git a/src/mcp_server_appwrite/__init__.py b/src/mcp_server_appwrite/__init__.py index a78579b..2ea680b 100644 --- a/src/mcp_server_appwrite/__init__.py +++ b/src/mcp_server_appwrite/__init__.py @@ -1,16 +1,8 @@ -import asyncio -import sys - - def main(): """Main entry point for the package.""" - from .server import _run + from .server import main as _main - try: - asyncio.run(_run()) - except KeyboardInterrupt: - print("[appwrite-mcp] Shutdown requested", file=sys.stderr, flush=True) - return 0 + return _main() __all__ = ["main"] diff --git a/src/mcp_server_appwrite/auth.py b/src/mcp_server_appwrite/auth.py new file mode 100644 index 0000000..5392cf7 --- /dev/null +++ b/src/mcp_server_appwrite/auth.py @@ -0,0 +1,284 @@ +"""OAuth 2.1 resource-server layer for the hosted Appwrite MCP. + +The MCP server acts as an OAuth 2.1 Resource Server (per the MCP authorization +spec): it validates the bearer access token issued by Appwrite Cloud's OAuth +authorization server and then lets the request proceed. The same token is later +forwarded to the Appwrite REST API, which natively accepts it. + +This deployment is single-tenant: it serves one Appwrite project — the Cloud +console project by default, overridable via ``APPWRITE_PROJECT_ID`` — so the MCP +endpoint is simply ``/mcp`` with no project in the path. Tokens must be issued by +that project's authorization server (``/oauth2/``); a token +whose issuer names any other project is rejected. +""" + +from __future__ import annotations + +import os +import sys +import time +from urllib.parse import urlsplit, urlunsplit + +import anyio +import httpx +import jwt +from jwt import PyJWKClient +from mcp.server.auth.provider import AccessToken, TokenVerifier + +DEFAULT_ENDPOINT = "https://cloud.appwrite.io/v1" +DEFAULT_PROJECT_ID = "console" + + +def _log(message: str) -> None: + print(f"[appwrite-mcp][auth] {message}", file=sys.stderr, flush=True) + + +def appwrite_endpoint() -> str: + return os.getenv("APPWRITE_ENDPOINT", DEFAULT_ENDPOINT).rstrip("/") + + +def configured_project_id() -> str: + """The single Appwrite project this MCP serves. Defaults to the Cloud console + project; override with ``APPWRITE_PROJECT_ID`` for other deployments/tests.""" + return os.getenv("APPWRITE_PROJECT_ID", DEFAULT_PROJECT_ID) + + +def public_base_url() -> str: + """External base URL of this MCP server, used to build canonical resource URIs. + Falls back to APPWRITE-independent ``MCP_PUBLIC_URL``.""" + base = os.getenv("MCP_PUBLIC_URL", "http://localhost:8000") + return base.rstrip("/") + + +def issuer_url() -> str: + """The Appwrite OAuth authorization server (issuer) for the served project.""" + return f"{appwrite_endpoint()}/oauth2/{configured_project_id()}" + + +def canonical_resource() -> str: + """RFC 8707 canonical resource URI for this MCP server.""" + return f"{public_base_url()}/mcp" + + +def resource_metadata_url() -> str: + """RFC 9728 protected-resource metadata URL (well-known path + resource path).""" + parts = urlsplit(public_base_url()) + path = "/.well-known/oauth-protected-resource/mcp" + return urlunsplit((parts.scheme, parts.netloc, path, "", "")) + + +# Cache of scopes_supported, keyed by served project id (process lifetime; the +# project OAuth config is effectively static). Failed lookups raise and are not +# cached, so they retry. +_discovery_cache: dict[str, dict] = {} + + +def discovery_url() -> str: + return f"{issuer_url()}/.well-known/openid-configuration" + + +def _validate_discovery(doc: dict, url: str) -> dict: + issuer = doc.get("issuer") + jwks_uri = doc.get("jwks_uri") + if not isinstance(issuer, str) or not issuer: + raise ValueError(f"authorization server discovery missing issuer: {url}") + if not isinstance(jwks_uri, str) or not jwks_uri: + raise ValueError(f"authorization server discovery missing jwks_uri: {url}") + return doc + + +async def authorization_server_metadata() -> dict: + project_id = configured_project_id() + cached = _discovery_cache.get(project_id) + if cached is not None: + return cached + + url = discovery_url() + async with httpx.AsyncClient(timeout=10.0, follow_redirects=True) as client: + resp = await client.get(url) + resp.raise_for_status() + metadata = _validate_discovery(resp.json(), url) + + _discovery_cache[project_id] = metadata + return metadata + + +def authorization_server_metadata_sync() -> dict: + project_id = configured_project_id() + cached = _discovery_cache.get(project_id) + if cached is not None: + return cached + + url = discovery_url() + resp = httpx.get(url, timeout=10.0, follow_redirects=True) + resp.raise_for_status() + metadata = _validate_discovery(resp.json(), url) + _discovery_cache[project_id] = metadata + return metadata + + +async def supported_scopes() -> list[str]: + """Scopes advertised in the protected-resource metadata, sourced live from the + served project's authorization-server discovery (`scopes_supported`). This is + exactly the set the project's OAuth server will grant, so it never drifts from + the tool surface. Raises if discovery is unreachable or malformed (the + authorization server is the same Appwrite deployment this MCP depends on).""" + metadata = await authorization_server_metadata() + scopes = metadata.get("scopes_supported") + if not isinstance(scopes, list): + raise ValueError( + f"authorization server discovery missing scopes_supported: {discovery_url()}" + ) + return scopes + + +def build_resource_metadata(scopes: list[str], authorization_servers=None) -> dict: + """RFC 9728 Protected Resource Metadata document.""" + return { + "resource": canonical_resource(), + "authorization_servers": authorization_servers or [issuer_url()], + "scopes_supported": scopes, + "bearer_methods_supported": ["header"], + } + + +async def protected_resource_metadata() -> dict: + """RFC 9728 Protected Resource Metadata, with scopes sourced from AS discovery.""" + metadata = await authorization_server_metadata() + scopes = metadata.get("scopes_supported") + if not isinstance(scopes, list): + raise ValueError( + f"authorization server discovery missing scopes_supported: {discovery_url()}" + ) + return build_resource_metadata(scopes, [metadata["issuer"]]) + + +def project_id_from_issuer(iss: str | None) -> str | None: + """Extract the project ID from an Appwrite OAuth issuer.""" + if not isinstance(iss, str) or not iss: + return None + issuer = urlsplit(iss) + endpoint = urlsplit(appwrite_endpoint()) + if issuer.scheme != endpoint.scheme: + return None + endpoint_path = endpoint.path.rstrip("/") + prefix = f"{endpoint_path}/oauth2/" if endpoint_path else "/oauth2/" + issuer_path = issuer.path.rstrip("/") + if not issuer_path.startswith(prefix): + return None + project_id = issuer_path[len(prefix) :].strip("/") + # Project IDs are a single path segment. + if not project_id or "/" in project_id: + return None + return project_id + + +class AppwriteTokenVerifier(TokenVerifier): + """Validates RS256 access tokens against the served project's JWKS. + + Revocation/rotation is not checked here: the Appwrite REST API re-validates the + token (signature + rotation + expiry + identity) on every downstream call, so a + lightweight verification at the MCP gate is sufficient and avoids an introspection + round-trip. Short-lived public-client tokens keep the exposure window small. + """ + + def __init__(self) -> None: + # One JWKS client per project, cached for the process lifetime. In practice + # only the served project's client is ever created. + self._jwks_clients: dict[str, PyJWKClient] = {} + + def _jwks_client(self, issuer: str, jwks_uri: str) -> PyJWKClient: + client = self._jwks_clients.get(issuer) + if client is None: + client = PyJWKClient(jwks_uri) + self._jwks_clients[issuer] = client + return client + + def _verify_sync(self, token: str) -> AccessToken | None: + try: + unverified = jwt.decode(token, options={"verify_signature": False}) + except jwt.PyJWTError: + return None + + issuer = unverified.get("iss") + try: + metadata = authorization_server_metadata_sync() + except Exception as exc: + _log(f"Rejecting token: authorization server discovery failed ({exc}).") + return None + + expected_issuer = metadata["issuer"] + if issuer != expected_issuer: + _log( + f"Rejecting token: issuer {issuer!r} does not match discovered " + f"issuer {expected_issuer!r}." + ) + return None + + project_id = project_id_from_issuer(issuer) + if not project_id: + _log("Rejecting token: issuer is not an Appwrite OAuth issuer.") + return None + if project_id != configured_project_id(): + _log( + f"Rejecting token: issuer project {project_id!r} is not the served " + f"project {configured_project_id()!r}." + ) + return None + + try: + signing_key = self._jwks_client( + expected_issuer, metadata["jwks_uri"] + ).get_signing_key_from_jwt(token) + claims = jwt.decode( + token, + signing_key.key, + algorithms=["RS256"], + options={"verify_aud": False, "require": ["exp"]}, + ) + except jwt.PyJWTError as exc: + _log(f"Rejecting token: verification failed ({exc}).") + return None + + expected_resource = canonical_resource() + if not self._audience_ok(claims.get("aud"), expected_resource): + return None + + scope_claim = claims.get("scope") or claims.get("scp") or "" + scopes = ( + scope_claim.split() if isinstance(scope_claim, str) else list(scope_claim) + ) + + return AccessToken( + token=token, + client_id=str( + claims.get("client_id") or claims.get("azp") or claims.get("aud") or "" + ), + scopes=scopes, + expires_at=int(claims["exp"]) if "exp" in claims else None, + resource=expected_resource, + subject=claims.get("sub"), + claims={**claims, "project_id": project_id}, + ) + + def _audience_ok(self, aud, expected_resource: str) -> bool: + # Tokens must be audience-bound to this MCP server (RFC 8707). The Appwrite + # OAuth server always issues Resource Indicators, so a missing or mismatched + # audience is a hard rejection. + audiences = ( + [aud] if isinstance(aud, str) else list(aud) if aud is not None else [] + ) + if expected_resource in audiences: + return True + _log( + f"Rejecting token: audience {audiences!r} not bound to {expected_resource!r}." + ) + return False + + async def verify_token(self, token: str) -> AccessToken | None: + access_token = await anyio.to_thread.run_sync(self._verify_sync, token) + if access_token is None: + return None + if access_token.expires_at and access_token.expires_at < int(time.time()): + return None + return access_token diff --git a/src/mcp_server_appwrite/data/docs_index.npz b/src/mcp_server_appwrite/data/docs_index.npz new file mode 100644 index 0000000..555bade Binary files /dev/null and b/src/mcp_server_appwrite/data/docs_index.npz differ diff --git a/src/mcp_server_appwrite/data/docs_index_meta.json b/src/mcp_server_appwrite/data/docs_index_meta.json new file mode 100644 index 0000000..9a8020e --- /dev/null +++ b/src/mcp_server_appwrite/data/docs_index_meta.json @@ -0,0 +1 @@ +{"model": "text-embedding-3-small", "dimension": 1536, "pages": [{"path": "docs/advanced/billing", "title": "Billing", "description": "Understand Appwrite's plans, add-ons, service level agreements, and billing policies.", "content": "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.\n\n# Manage billing {% #manage-billing %}\n\nConfigure your organization's plan, payment methods, and spending controls.\n\n{% cards %}\n{% cards_item href=\"/docs/advanced/billing/payments\" title=\"Manage billing\" %}\nManage your plan, billing periods, payment methods, budget caps, and invoices.\n{% /cards_item %}\n{% /cards %}\n\n# Plans {% #plans %}\n\nLearn which plan best suits your organization.\n\n{% cards %}\n{% cards_item href=\"/docs/advanced/billing/free\" title=\"Free\" %}\nLearn about Appwrite Free plan. Free plan for hobby projects and learners.\n{% /cards_item %}\n\n{% cards_item href=\"/docs/advanced/billing/pro\" title=\"Pro\" %}\nLearn about Appwrite Pro, for growing organizations that need to scale.\n{% /cards_item %}\n\n{% cards_item href=\"/docs/advanced/billing/enterprise\" title=\"Enterprise\" %}\nLearn about Appwrite Enterprise, for large organizations with advanced needs.\n{% /cards_item %}\n\n{% cards_item href=\"/docs/advanced/billing/oss\" title=\"Open source\" %}\nAppwrite provides special plans for open source projects.\n{% /cards_item %}\n{% /cards %}\n\n# Add ons {% #add-ons %}\n\nLearn about additional features and functionalities that Appwrite offers.\n\n{% cards %}\n{% cards_item href=\"/docs/advanced/billing/compute\" title=\"Compute\" %}\nCPU and memory for Functions and Sites, including build and runtime specs.\n{% /cards_item %}\n\n{% cards_item href=\"/docs/advanced/billing/phone-otp\" title=\"Phone OTP\" %}\nLearn how Appwrite handles SMS-based OTP authentication.\n{% /cards_item %}\n\n{% cards_item href=\"/docs/advanced/billing/image-transformations\" title=\"Image Transformations\" %}\nLearn how to transform images dynamically with Appwrite.\n{% /cards_item %}\n\n{% cards_item href=\"/docs/advanced/billing/database-reads-and-writes\" title=\"Database Reads and Writes\" %}\nLearn how Appwrite handles database reads and writes.\n{% /cards_item %}\n{% /cards %}\n\n# Service level agreements {% #slas %}\n\nUnderstand the service level agreements available on Appwrite's paid plans.\n\n{% cards %}\n{% cards_item href=\"/docs/advanced/billing/support-sla\" title=\"Support SLA\" %}\nLearn about Appwrite's support response time commitments.\n{% /cards_item %}\n\n{% cards_item href=\"/docs/advanced/billing/uptime-sla\" title=\"Uptime SLA\" %}\nLearn about Appwrite's uptime commitments.\n{% /cards_item %}\n{% /cards %}\n\n# Policies {% #policies %}\n\nUnderstand the policies that govern usage and billing on Appwrite.\n\n{% cards %}\n{% cards_item href=\"/docs/advanced/billing/fair-use-policy\" title=\"Fair use\" %}\nUnderstand Appwrite's usage limits, prohibited activities, and enforcement actions.\n{% /cards_item %}\n\n{% cards_item href=\"/docs/advanced/billing/abuse\" title=\"Abuse\" %}\nGuidelines on abusive behavior, prohibited activities, and reporting mechanisms.\n{% /cards_item %}\n\n{% cards_item href=\"/docs/advanced/billing/refund-policy\" title=\"Refund\" %}\nLearn about Appwrite's refund policy, eligibility criteria, and request process.\n{% /cards_item %}\n{% /cards %}"}, {"path": "docs/advanced/billing/abuse", "title": "Abuse policy", "description": "Guidelines on abusive behavior, prohibited activities, and reporting mechanisms under our Fair Use Policy.", "content": "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.\n\n{% partial file=\"prohibited-activities.md\" /%}\n\n# Reporting Abuse {% #reporting-abuse %}\nIf you observe or suspect any prohibited activity, please report it as soon as possible to [abuse@appwrite.io](mailto:abuse@appwrite.io).\n\nPlease 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."}, {"path": "docs/advanced/billing/compute", "title": "Compute", "description": "Learn about CPU and memory options for Appwrite Functions and Sites on Cloud, including separate build and runtime specifications and plan build timeouts.", "content": "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.\n\nThese 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.\n\n# Specifications {% #specifications %}\n\nAppwrite Cloud offers the following specification tiers. The same tiers are available for **build** and **runtime** independently on each function or site:\n\n| Memory | CPU cores | Hourly usage |\n|--------|-----------|--------------|\n| 512MB | 0.5 | 0.25 |\n| 512MB | 1 | 0.5 |\n| 1GB | 1 | 1 |\n| 2GB | 2 | 4 |\n| 4GB | 2 | 8 |\n| 4GB | 4 | 16 |\n\n{% info title=\"Note\" %}\nOn 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).\n{% /info %}\n\n# Build timeouts {% #build-timeouts %}\n\nOn **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:\n\n| Plan | Maximum build duration |\n| --- | --- |\n| Free | 15 minutes |\n| Pro / Scale | 45 minutes |\n| Enterprise | Custom |\n\nThese 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).\n\nOn **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.\n\nConfigure 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.\n\n# GB-Hours {% #gb-hours %}\n\nGB-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.\n\nHow it works:\n\n- Memory allocation: The GB value comes from the spec in use for that phase (build or runtime).\n- Duration: Time that phase is active (for example, build duration or execution time).\n- Calculation: Memory (GB) multiplied by hours gives GB-hours for that usage.\n\n**Example:**\n\nIf a function runs with 4 GB of memory for 2 hours of execution time:\n\n**4GB * 2 hours = 8 GB-hours**\n\nThat counts toward your execution GB-hours for the billing period.\n\n## Pricing {% #pricing %}\n\n- The Free plan includes up to 100 GB-hours of execution and build time per month.\n\n- 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.\n\nOnce the monthly GB-hours limit is reached, additional usage is billed automatically. Set budget alerts and a budget cap to avoid unexpected charges."}, {"path": "docs/advanced/billing/database-reads-and-writes", "title": "Database Reads and Writes", "description": "Learn how Appwrite handles database reads and writes and their associated costs.", "content": "Appwrite provides powerful database capabilities through its [Database API](/docs/products/databases), allowing you to perform read and write operations across your application data. Understanding how these operations are counted and billed is essential for planning your application's scalability.\n\n## Database Operations\n\nDatabase operations in Appwrite are categorized into two types:\n\n**Read Operations**: Any action that retrieves data from your database, including:\n- Fetching rows with `getRow` or `listRows`.\n\n**Write Operations**: Any action that modifies data in your database, including:\n- Creating rows with `createRow`.\n- Updating rows with `updateRow`.\n- Deleting rows with `deleteRow`.\n\nHow it works:\n1. Perform database operations through the Appwrite SDK or API.\n2. Appwrite automatically tracks and logs these operations.\n3. Operations are counted based on the number of rows affected, not API calls.\n4. Quotas are refreshed monthly based on your subscription plan.\n\nFor 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.\n\n## Pricing\n\n### Free Plan\n- **Included**: 500,000 read operations and 250,000 write operations per month.\n- **Overage**: Not available (operations are throttled when limits are reached).\n\n### Pro and Scale Plans\n- **Included**: 1,750,000 read operations and 750,000 write operations per month.\n- **Overage**: $0.060 per 100,000 additional read operations and $0.10 per 100,000 additional write operations.\n\n### Enterprise Plan\n- **Included**: Unlimited read and write operations.\n- **Overage**: Not applicable.\n\nFor detailed information about the different pricing options and features, please visit the [pricing page](/pricing).\n\n## Best Practices\n\nTo optimize your database operations and control costs:\n1. **Use efficient queries**: Filter data on the server side rather than retrieving large datasets and filtering client-side.\n2. **Implement pagination**: Use the `limit` and `offset` parameters to retrieve only the data you need.\n3. **Monitor usage**: Keep track of your database operations through the Appwrite Console.\n4. **Consider caching**: Cache frequently accessed data to reduce repeated read operations."}, {"path": "docs/advanced/billing/enterprise", "title": "Enterprise", "description": "How Appwrite can accelerate enterprise development teams and provide custom support and hosting options.", "content": "Enterprise development teams face unique challenges and have unique needs. \nAppwrite can provide tailored solutions for enterprise customers with custom hosting, training, and support needs.\n\nIf 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."}, {"path": "docs/advanced/billing/fair-use-policy", "title": "Fair use policy", "description": "Understand Appwrite's usage limits, prohibited activities, and enforcement actions.", "content": "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.\n\n# Definitions and scope {% #definitions-and-scope %}\n\n- **Normal usage:** Resource usage that falls within expected thresholds for a user's selected plan.\n- **Excessive usage:** Usage that exceeds defined thresholds and may affect the platform's performance for other users.\n- **Prohibited activities:** Actions or behaviors that violate this policy, including, but not limited to, abuse of resources and security breaches.\n\nThis policy applies to all resources provided by Appwrite Cloud, including but not limited to API requests, data storage, compute resources, and database operations.\n\n# Usage limits and thresholds {% #usage-limits-and-thresholds %}\n\nEach user plan includes specific resource limits, which are available to view on our [Pricing page](/pricing).\n\nExceeding these limits may lead to throttling or suspension of services, as described under [Enforcement actions](#enforcement-actions).\n\n{% partial file=\"prohibited-activities.md\" /%}\n\n# Enforcement actions {% #enforcement-actions %}\n\nIn cases of excessive or prohibited usage, Appwrite reserves the right to enforce the following actions:\n\n- **Temporary suspension** of access to the project or organization.\n- **Permanent suspension** of the account for repeated or serious policy violations.\n\nIn all cases, users will be notified of the action taken and provided with guidance on how to correct the issue.\n\n# Monitoring and compliance {% #monitoring-and-compliance %}\n\nWe 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.\n\n# Options for users exceeding limits {% #options-for-users-exceeding-limits %}\n\nUsers who consistently exceed usage limits have the following options:\n\n- **Upgrade to a higher plan:** Users may select a plan that better fits their needs.\n- **Resource upgrades:** Pro and Scale plan users may upgrade the usage limits for specific resources. Pricing details are available [here](/pricing).\n\n# Policy updates {% #policy-updates %}\n\nAppwrite reserves the right to update this policy to reflect evolving platform capabilities or changing usage patterns at any time.\n\n# Contact us {% #contact-us %}\n\nFor questions or further clarification, please [contact our support team](/contact-us)."}, {"path": "docs/advanced/billing/free", "title": "Free", "description": "Appwrite's Free plan provides a generous free tier. Perfect for budding projects, hobbiests, and side-projects.", "content": "Appwrite Cloud provides a **Free** plan to all developers to start building with Appwrite.\nAppwrite Free plan is perfect for personal hobby projects for students and professional developers alike.\nLearn more about the Free plan's generous resource limits on the [pricing page](https://appwrite.io/pricing).\n\n# Create a Free plan organization {% #create-a-free-plan-organizations %}\n\nAppwrite 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.\nWhen you create your Appwrite Cloud account, a **Personal Projects** organization using the Free plan is automatically created.\n\nEach Appwrite Cloud account can only have one organization and 2 projects on the **Free** plan.\n\n# Resource limits {% #resource-limits %}\n\nEach plan in Appwrite Cloud has resource limits. The details of these resource limits are on the [pricing page](https://appwrite.io/pricing).\n\nEach 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.\n\n{% partial file=\"resource-limits.md\" /%}"}, {"path": "docs/advanced/billing/image-transformations", "title": "Image Transformations", "description": "Learn how to transform images using Appwrite's storage API.", "content": "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.\n\n### Origin Image\n\nAn \"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.\n\nHow it works:\n\n1. Upload an image to Appwrite Storage\n2. Use the `getFilePreview` method to apply transformations to the image\n3. Retrieve transformed images through the preview endpoint\n4. Pay only for unique origin images, regardless of transformation count\n\nFor 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**.\n\n## Pricing\n\n- Currently, this feature is **unavailable on the free plan**.\n- Pro and Scale plans include 100 origin images per month. Additional usage is available at $5 per 1000 origin images.\n\nFor detailed information about the different pricing options and features, please visit the [pricing page](/pricing)."}, {"path": "docs/advanced/billing/oss", "title": "Open source", "description": "Learn how Appwrite supports open-source projects by providing free credits and other benefits.", "content": "Appwrite remains open source and continues to support open-source maintainers that build fundamental software that modern developers depend upon with the OSS Program.\n\nThe 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.\n\nThe program has no fixed end date but will be reviewed annually to ensure optimal mutual support.\n\n# Criteria {% #criteria %}\n\nTo 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:\n\n- Your project needs to meet the criteria of the [Open Source Initiative definition](https://opensource.org/osd/)\n- Have obtained an approved [OSS license](https://opensource.org/licenses/)\n- Have an open-source GitHub repository\n- Have an active project that has at least 15 contributions and 100 stars\n- Have a nonprofit or pre-revenue project\n\n# Application {% #application %}\n\nIf 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).\n\nWe 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."}, {"path": "docs/advanced/billing/payments", "title": "Manage billing", "description": "Understand Appwrite's billing features, like budget caps, billing periods, taxes, and more.", "content": "Appwrite allows you to configure billing per organization.\nYou can access your organizations billing information under the **Billing** tab of your organization.\n\n# Plans {% #plans %}\nYou can view or change your organization's plan under the **Billing** section.\nYou'll also find the expected cost, as well as the start and end date of the current billing period.\n\n## Billing period {% #billing-period %}\nBilling periods begin the day you change your plan, and lasts 30 days.\nYour resource limits are reset at the beginning of each billing period.\n\nCharges are applied at different times:\n- **Projects and add-ons** are billed at the start of the billing period\n- **Additional usage** (like bandwidth, execution time, and GB-hours) is billed at the start of the following billing period based on your actual usage\n\n# Payment history {% #payment-history %}\nYou can view and download you past invoices under **Payment history**.\nYou can click the three-dots menu to view and download your invoices.\n\n# Payment methods {% #payment-methods %}\nAppwrite Cloud accepts credit and debit cards as payment methods and will bill the card at the end of each billing cycle.\nAppwrite accepts Visa, Mastercard, American Express, Discover & Diners Club, China UnionPay, Japan Credit Bureau (JCB), Cartes Bancaires, and eftpos Australia.\n\n# Billing address {% #billing-address %}\nYour billing address will be displayed on your invoices and used when Appwrite Cloud bills your payment method at the end of a billing cycle.\n\n# Tax ID {% #tax-id %}\nIf you'd like you or your company's tax ID displayed on your generated invoice, you can provide it under **Tax ID**.\n\n# Budget cap {% #budget-cap %}\nAppwrite allows you to set budget caps for your organization.\nAppwrite will automatically scale your projects by purchasing add-ons as they require more resources.\nBudget caps limit the amount of automatic scaling and prevent unexpected bills.\nThis budget cap does **not include the plan's recurring cost**, only add-ons.\n\nYou can enable budget caps under **Budget cap**, toggle the option **Enable budget cap**.\nYou will be able to set a budget cap in USD.\n\n## Budget alerts {% #budget-alerts %}\nWhen 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.\nBy 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.\n\n# Redeem credit {% #redeem-credit %}\nIf you received a redeemable code for Appwrite Cloud credits, you can redeem them in the **Available credit** section.\nWhen you redeem credit, the credit balance will be automatically applied to the next billing cycle.\n\n# Recurring payments for Indian developers {% #recurring-payments-for-indian-developers %}\n\nThe Reserve Bank of India (RBI) mandates additional security measures for recurring payments on **Indian cards**.\nAppwrite is obligated to ask for verification before billing your card.\nAppwrite asks for verification for up to $150 in case you use add-ons.\nConfirming the verification will not charge your card.\nWhen you are charged at the end of the billing period,\nyou will not be charged more than your monthly plan plus add-ons used.\n\nIf you set a budget cap, the verification will remain at $150, but the budget cap will still be applied for your add-ons.\nAgain, you will not be charged when confirming the verification.\nWhen you are charged at the end of the billing period,\nyou will not be charged more than your monthly plan plus add-ons used.\n\nIf you need higher limits, [contact us](mailto:billing@appwrite.io)."}, {"path": "docs/advanced/billing/phone-otp", "title": "Phone OTP", "description": "Learn how Appwrite handles SMS-based OTP authentication for secure user verification.", "content": "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.\n\n# Free testing {% #free-testing %}\n\nYou can use the [Mock phone numbers](/docs/products/auth/security#mock-phone-numbers) feature to test your integrations without incurring any costs.\n\n# SMS messages {% #sms-messages %}\n\nYou'll be charged per SMS sent.\n\nThe cost for additional messages is calculated based on two factors:\n1. The number of messages sent\n2. The destination country of each message\n\nAs 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.\n\n## Rates {% #rates %}\n\nSMS rates vary by country due to differences in telecom infrastructure and regulations. Here is a breakdown of the rates:\n\n| Country code | Country name | Price / SMS (USD) |\n|--------------|--------------------------|-------------------|\n| +213 | Algeria | $ 0.38 |\n| +376 | Andorra | $ 0.14 |\n| +244 | Angola | $ 0.15 |\n| +54 | Argentina | $ 0.14 |\n| +374 | Armenia | $ 0.29 |\n| +297 | Aruba | $ 0.35 |\n| +61 | Australia | $ 0.05 |\n| +43 | Austria | $ 0.04 |\n| +994 | Azerbaijan | $ 0.54 |\n| +973 | Bahrain | $ 0.05 |\n| +880 | Bangladesh | $ 0.62 |\n| +375 | Belarus | $ 0.32 |\n| +32 | Belgium | $ 0.14 |\n| +501 | Belize | $ 0.43 |\n| +229 | Benin | $ 0.39 |\n| +975 | Bhutan | $ 0.47 |\n| +591 | Bolivia | $ 0.31 |\n| +387 | Bosnia and Herzegovina | $ 0.08 |\n| +267 | Botswana | $ 0.13 |\n| +55 | Brazil | $ 0.05 |\n| +673 | Brunei | $ 0.09 |\n| +359 | Bulgaria | $ 0.23 |\n| +226 | Burkina Faso | $ 0.34 |\n| +257 | Burundi | $ 0.55 |\n| +855 | Cambodia | $ 0.57 |\n| +237 | Cameroon | $ 0.38 |\n| +238 | Cape Verde Islands | $ 0.28 |\n| +56 | Chile | $ 0.04 |\n| +86 | China | $ 0.03 |\n| +57 | Colombia | $ 0.05 |\n| +269 | Comoros and Mayotte | $ 0.54 |\n| +242 | Congo | $ 0.39 |\n| +682 | Cook Islands | $ 0.17 |\n| +506 | Costa Rica | $ 0.22 |\n| +385 | Croatia | $ 0.19 |\n| +53 | Cuba | $ 0.11 |\n| +357 | Cyprus | $ 0.02 |\n| +420 | Czech Republic | $ 0.09 |\n| +45 | Denmark | $ 0.08 |\n| +253 | Djibouti | $ 0.19 |\n| +593 | Ecuador | $ 0.32 |\n| +20 | Egypt | $ 0.54 |\n| +503 | El Salvador | $ 0.11 |\n| +240 | Equatorial Guinea | $ 0.28 |\n| +291 | Eritrea | $ 0.17 |\n| +372 | Estonia | $ 0.07 |\n| +251 | Ethiopia | $ 0.51 |\n| +500 | Falkland Islands | $ 0.13 |\n| +298 | Faroe Islands | $ 0.09 |\n| +679 | Fiji | $ 0.29 |\n| +358 | Finland | $ 0.12 |\n| +33 | France | $ 0.10 |\n| +594 | French Guiana | $ 0.19 |\n| +689 | French Polynesia | $ 0.14 |\n| +241 | Gabon | $ 0.42 |\n| +220 | Gambia | $ 0.13 |\n| +995 | Georgia | $ 0.20 |\n| +49 | Germany | $ 0.14 |\n| +233 | Ghana | $ 0.45 |\n| +350 | Gibraltar | $ 0.12 |\n| +30 | Greece | $ 0.07 |\n| +299 | Greenland | $ 0.04 |\n| +590 | Guadeloupe | $ 0.21 |\n| +1671 | Guam | $ 0.04 |\n| +502 | Guatemala | $ 0.31 |\n| +224 | Guinea | $ 0.37 |\n| +245 | Guinea-Bissau | $ 0.37 |\n| +592 | Guyana | $ 0.31 |\n| +509 | Haiti | $ 0.47 |\n| +504 | Honduras | $ 0.33 |\n| +852 | Hong Kong | $ 0.08 |\n| +36 | Hungary | $ 0.10 |\n| +354 | Iceland | $ 0.10 |\n| +91 | India | $ 0.003 |\n| +62 | Indonesia | $ 0.58 |\n| +98 | Iran | $ 0.39 |\n| +964 | Iraq | $ 0.56 |\n| +353 | Ireland | $ 0.11 |\n| +972 | Israel | $ 0.01 |\n| +39 | Italy | $ 0.07 |\n| +81 | Japan | $ 0.09 |\n| +962 | Jordan | $ 0.57 |\n| +254 | Kenya | $ 0.38 |\n| +686 | Kiribati | $ 0.08 |\n| +850 | North Korea | $ 0.03 |\n| +82 | South Korea | $ 0.03 |\n| +965 | Kuwait | $ 0.30 |\n| +996 | Kyrgyzstan | $ 0.49 |\n| +856 | Laos | $ 0.19 |\n| +371 | Latvia | $ 0.09 |\n| +961 | Lebanon | $ 0.42 |\n| +266 | Lesotho | $ 0.16 |\n| +231 | Liberia | $ 0.38 |\n| +218 | Libya | $ 0.60 |\n| +423 | Liechtenstein | $ 0.05 |\n| +370 | Lithuania | $ 0.07 |\n| +352 | Luxembourg | $ 0.13 |\n| +853 | Macao | $ 0.04 |\n| +389 | Macedonia | $ 0.07 |\n| +261 | Madagascar | $ 0.62 |\n| +265 | Malawi | $ 0.40 |\n| +60 | Malaysia | $ 0.40 |\n| +960 | Maldives | $ 0.44 |\n| +223 | Mali | $ 0.36 |\n| +356 | Malta | $ 0.08 |\n| +692 | Marshall Islands | $ 0.03 |\n| +596 | Martinique | $ 0.21 |\n| +222 | Mauritania | $ 0.30 |\n| +52 | Mexico | $ 0.35 |\n| +691 | Micronesia | $ 0.03 |\n| +373 | Moldova | $ 0.11 |\n| +377 | Monaco | $ 0.13 |\n| +976 | Mongolia | $ 0.46 |\n| +212 | Morocco | $ 0.36 |\n| +258 | Mozambique | $ 0.29 |\n| +95 | Myanmar | $ 0.59 |\n| +264 | Namibia | $ 0.07 |\n| +674 | Nauru | $ 0.29 |\n| +977 | Nepal | $ 0.47 |\n| +31 | Netherlands | $ 0.15 |\n| +687 | New Caledonia | $ 0.12 |\n| +64 | New Zealand | $ 0.11 |\n| +505 | Nicaragua | $ 0.21 |\n| +227 | Niger | $ 0.42 |\n| +234 | Nigeria | $ 0.61 |\n| +683 | Niue | $ 0.05 |\n| +672 | Norfolk Islands | $ 0.07 |\n| +1 | North America | $ 0.02 |\n| +1670 | Northern Mariana Islands | $ 0.11 |\n| +47 | Norway | $ 0.11 |\n| +968 | Oman | $ 0.25 |\n| +680 | Palau | $ 0.12 |\n| +92 | Pakistan | $ 0.58 |\n| +507 | Panama | $ 0.22 |\n| +675 | Papua New Guinea | $ 0.26 |\n| +595 | Paraguay | $ 0.12 |\n| +51 | Peru | $ 0.04 |\n| +63 | Philippines | $ 0.34 |\n| +48 | Poland | $ 0.04 |\n| +351 | Portugal | $ 0.04 |\n| +974 | Qatar | $ 0.34 |\n| +262 | Reunion | $ 0.09 |\n| +40 | Romania | $ 0.08 |\n| +7 | Russia and Kazakhstan | $ 0.52 |\n| +250 | Rwanda | $ 0.49 |\n| +378 | San Marino | $ 0.06 |\n| +239 | Sao Tome and Principe | $ 0.11 |\n| +966 | Saudi Arabia | $ 0.33 |\n| +221 | Senegal | $ 0.46 |\n| +381 | Serbia | $ 0.46 |\n| +248 | Seychelles | $ 0.43 |\n| +232 | Sierra Leone | $ 0.35 |\n| +65 | Singapore | $ 0.09 |\n| +421 | Slovak Republic | $ 0.09 |\n| +386 | Slovenia | $ 0.18 |\n| +677 | Solomon Islands | $ 0.12 |\n| +252 | Somalia | $ 0.24 |\n| +27 | South Africa | $ 0.16 |\n| +34 | Spain | $ 0.07 |\n| +94 | Sri Lanka | $ 0.60 |\n| +290 | St. Helena | $ 0.06 |\n| +249 | Sudan | $ 0.48 |\n| +597 | Suriname | $ 0.27 |\n| +268 | Swaziland | $ 0.13 |\n| +46 | Sweden | $ 0.09 |\n| +41 | Switzerland | $ 0.06 |\n| +963 | Syria | $ 0.60 |\n| +886 | Taiwan | $ 0.08 |\n| +992 | Tajikistan | $ 0.57 |\n| +255 | Tanzania | $ 0.49 |\n| +66 | Thailand | $ 0.03 |\n| +228 | Togo | $ 0.53 |\n| +676 | Tonga | $ 0.25 |\n| +216 | Tunisia | $ 0.54 |\n| +90 | Turkey | $ 0.01 |\n| +993 | Turkmenistan | $ 0.39 |\n| +688 | Tuvalu | $ 0.13 |\n| +256 | Uganda | $ 0.41 |\n| +380 | Ukraine | $ 0.25 |\n| +971 | United Arab Emirates | $ 0.16 |\n| +44 | United Kingdom | $ 0.08 |\n| +598 | Uruguay | $ 0.11 |\n| +998 | Uzbekistan | $ 0.64 |\n| +678 | Vanuatu | $ 0.28 |\n| +58 | Venezuela | $ 0.44 |\n| +84 | Vietnam | $ 0.28 |\n| +967 | Yemen | $ 0.34 |\n| +260 | Zambia | $ 0.46 |\n| +263 | Zimbabwe | $ 0.32 |"}, {"path": "docs/advanced/billing/pro", "title": "Pro", "description": "Understand Appwrite's pricing plans, behaviors, billing cycles, and limitations.", "content": "Appwrite Cloud's Pro plan is designed for professional developers or development teams that need to build applications at scale.\nWhen applications outgrow Appwrite's Free plan, organizations can switch to a Pro plan to continue growing their apps.\nYou can learn more about the Pro plan on the [pricing page](https://appwrite.io/pricing).\n\n# Create a Pro plan organization {% #create-a-pro-plan-organizations %}\n\nAppwrite's plans are applied to an entire organization, but resources are allocated per project.\nGet started with a Pro plan organization by visiting the [pricing page](https://appwrite.io/pricing) and click **Start building**\nor create a new organization from the Appwrite Console and select **Pro plan**.\n\n## Switch to Pro plan {% #switch-to-pro-plan %}\n\nYou can access your organization's overview through the profile menu at the top right of your Appwrite Console.\nUnder the **Billing** tab, you can click **Change plan** to update your organization's plan.\n\n# Resource limits {% #resource-limits %}\n\nEach 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).\n\nAdditional resources are automatically purchased when your organization exceeds the resource limits to continue scaling until the budget cap is reached.\nEach resource limit is applied per billing period and resets at the beginning of each billing period.\n\n## Budget cap {% #budget-cap %}\n\nAppwrite allows organizations to set budget caps when using a Pro plan.\nAppwrite will automatically scale Pro plan projects as they require more resources.\nBudget caps limit the amount of automatic scaling and prevent unexpected bills.\n\nOrganization budget caps can be set by navigating to the organization's **Billing** tab, under **Budget cap**, toggling, and setting a budget cap.\nAppwrite will send emails to warn organization members when they are near the budget cap.\n\n{% partial file=\"resource-limits.md\" /%}"}, {"path": "docs/advanced/billing/refund-policy", "title": "Refund policy", "description": "Learn about Appwrite's refund policy for services, including eligibility criteria and the request process.", "content": "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.\n\n# General policy {% #general-policy %}\n\nAppwrite 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.\n\nHowever, 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.\n\n# Eligible scenarios {% #eligible-scenarios %}\n\nRefunds may be considered in the following circumstances:\n\n## Service delivery failure {% #service-delivery-failure %}\n\nCritical and unresolved service outages affecting paid plans, or confirmed infrastructure issues that significantly impact service availability and performance.\n\n## Billing errors {% #billing-errors %}\n\nCustomers who are incorrectly charged due to:\n\n- Duplicate payments\n- Plan mismatches\n- Proven invoicing errors\n\n## Unauthorized transactions {% #unauthorized-transactions %}\n\nPayments made without proper authorization or the account owner's permission, reported within 30 days of the transaction.\n\n## First-time customer dissatisfaction {% #first-time-customer-dissatisfaction %}\n\nFirst-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).\n\n# Ineligible scenarios {% #ineligible-scenarios %}\n\nThe following scenarios do **not** qualify for refunds:\n\n- **Change in business needs or direction:** Decisions to pivot, discontinue use, or change platforms\n- **Incorrect setup or inactivity:** Customer misconfiguration, failure to complete onboarding, or lack of usage\n- **Expired refund window:** Requests submitted more than 30 days after the transaction date\n- **Third-party limitations:** Issues caused by third-party tools, plugins, or unsupported integrations\n- **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\n\n# Request process {% #request-process %}\n\nTo request a refund, please follow these steps:\n\n1. **Submit a ticket** via the Appwrite Console\n1. **Provide detailed information** about the issue, including relevant evidence and documentation\n1. **Allow for review time** - we aim to respond within 5 business days, with final resolution within 10 business days\n\n## Alternative solutions {% #alternative-solutions %}\n\nWhenever possible, we will first consider non-monetary solutions such as:\n\n- Service credits\n- Extended trial periods\n- Plan adjustments\n\n{% partial file=\"policy-modifications.md\" /%}\n\n{% partial file=\"policy-contact-us.md\" /%}"}, {"path": "docs/advanced/billing/support-sla", "title": "Support SLA", "description": "Learn about Appwrite's support service level agreement (SLA) including response times, severity levels, and support commitments for different subscription tiers.", "content": "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.\n\n# Scope\n\nThis 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**.\n\n# Severity levels\n\nSupport issues are categorized into the following severity levels:\n\n- **Critical**: System is down or a critical component is non-functional, causing a complete stoppage of work or significant business impact.\n- **High**: Major functionality is impaired, but a workaround is available, or a critical component is significantly degraded.\n- **Medium**: Minor functionality is impaired without significant business impact.\n- **Low**: Issue has minor impact on business operations; workaround is not necessary.\n- **Question**: Requests for information, general guidance, or feature requests.\n\n# Response time targets\n\n| Severity | Silver | Gold | Platinum |\n| --- | --- | --- | --- |\n| Critical | Unsupported | 1 hour (24/7/365) | 15 minutes (24/7/365) |\n| High | Unsupported | 4 hours (24/7/365) | 1 hour (24/7/365) |\n| Medium | 2 business days | 1 business day | 12 hours (24/7/365) |\n| Low | 3 business days | 2 business days | 24 hours (24/7/365) |\n| Question | 4 business days | 3 business days | 1 business day |\n\n# Business hours and days\n\nOur 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.\n\n# User responsibilities\n\nTo ensure effective support, users are expected to:\n\n- Provide detailed information about each issue, including screenshots, error messages, logs, and steps to reproduce the problem.\n- Ensure relevant personnel are available to assist in diagnosing and resolving issues.\n- Implement reasonable recommendations provided by our support team.\n\n# Limitations and exclusions\n\n- This SLA applies only to support requests submitted via the Appwrite Console.\n- 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.\n\n{% partial file=\"policy-modifications.md\" /%}\n\n{% partial file=\"policy-contact-us.md\" /%}"}, {"path": "docs/advanced/billing/uptime-sla", "title": "Uptime SLA", "description": "Learn about Appwrite's uptime service level agreement and commitments for different subscription plans.", "content": "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.\n\n# Uptime commitments {% #uptime-commitments %}\n\nWe commit to maintaining the following monthly uptime percentages based on your subscription plan:\n\n| Plan | Monthly Uptime Commitment |\n| --- | --- |\n| **Free** | N/A |\n| **Pro** | 99.5% |\n| **Scale** | 99.9% |\n| **Enterprise** | 99.95% |\n\nUptime 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.\n\n# Allowed downtime {% #allowed-downtime %}\n\nBased on the above uptime commitments, permissible downtime durations are:\n\n| Plan | Annual Downtime | Monthly Downtime |\n| --- | --- | --- |\n| **Free** | N/A | N/A |\n| **Pro** | ~1.83 days | ~3.6 hours |\n| **Scale** | ~8.76 hours | ~43.2 minutes |\n| **Enterprise** | ~4.38 hours | ~21.6 minutes |\n\n# Service credit terms {% #service-credit-terms %}\n\nIf we fail to meet our uptime commitment, affected users may be eligible for service credits according to the following schedule:\n\n| Plan | Uptime < Commitment but ≥ 99% | Uptime < 99% but ≥ 95% | Uptime < 95% |\n| --- | --- | --- | --- |\n| **Pro** | 10% of monthly fee | 25% of monthly fee | 50% of monthly fee |\n| **Scale** | 10% of monthly fee | 25% of monthly fee | 50% of monthly fee |\n| **Enterprise** | 10% of monthly fee | 25% of monthly fee | 50% of monthly fee |\n\nCredits 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.\n\n# Credit request procedure {% #credit-request-procedure %}\n\nTo 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.\n\n# SLA exclusions {% #sla-exclusions %}\n\nDowntime specifically excludes periods caused by:\n\n- Scheduled maintenance activities communicated in advance.\n- Events beyond our reasonable control (e.g., natural disasters, third-party outages, governmental actions).\n- Customer or third-party actions, including incorrect configurations or misuse of our services.\n- Minor performance degradations that do not materially impact core functionality.\n- External network issues beyond our infrastructure.\n\n# Commitment to continuous improvement {% #commitment-to-continuous-improvement %}\n\nWe 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.\n\n{% partial file=\"policy-modifications.md\" /%}\n\n{% partial file=\"policy-contact-us.md\" /%}"}, {"path": "docs/advanced/migrations", "title": "Migrations", "description": "Learn how to use Appwrite Migrations service to move projects from other vendors to Appwrite Cloud or from self-hosting to Cloud and the other way around.", "content": "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.\n\n# Sources {% #sources %}\n\nAppwrite 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.\n\n| Source | Users | Databases | Rows | Files | Functions | Sites |\n|--------|-------|-----------|-----------|-------|-----------|-------|\n| [Firebase](/docs/advanced/migrations/firebase) | enabled | enabled | partial | enabled | manual | - |\n| [Supabase](/docs/advanced/migrations/supabase) | enabled | enabled | partial | enabled | manual | - |\n| [Nhost](/docs/advanced/migrations/nhost) | enabled | enabled | partial | enabled | manual | - |\n| [Cloud](/docs/advanced/migrations/cloud) | enabled | enabled | enabled | enabled | enabled | enabled |\n| [Self hosted](/docs/advanced/migrations/self-hosted) | enabled | enabled | enabled | enabled | enabled | enabled |\n\n# Limitations {% #limitations %}\n\nMigrations cannot transfer all data perfectly, so certain fields, such as `$createdAt` and `$updatedAt`, may not be transferred. \nMore information can be found on the migration page for each source.\n\nMigrations 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. \nWe also recommend you carefully **validate permissions and data integrity** when moving between platforms.\n\n# Charges {% #charges %}\n\nWhen 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."}, {"path": "docs/advanced/migrations/cloud", "title": "Migrate from Cloud", "description": "Self-hosted application migration made easy with Appwrite. Discover the steps and strategies for migrating your self-hosted apps to Appwrite's managed platform.", "content": "Migrations make it as easy as a couple of clicks to move all your Appwrite Cloud data into a self-hosted instance.\n\n{% section #notices step=1 title=\"Things to keep in mind\" %}\n\n1. Data transferred by migrations will reset `$createdAt` and `$updatedAt` timestamps to the date of the migration.\n\n2. Your self-hosted Appwrite project must be accessible from the internet for the migration to work.\n\n3. Migrations are non-destructive. No data will be deleted or lost in the source project.\n\n{% /section %}\n\n{% section #create-migration step=2 title=\"Create migration\" %}\n\nTo begin migrating to self-hosted, make sure to read the [migration overview](/docs/advanced/migrations) and [things to keep in mind](#notices) sections above.\n\n1. Navigate to your Cloud project's console, navigate to **Settings** and click on the **Migrations** tab.\n\n1. Under **Export to self-hosted instance**, click **Export data**.\n\n1. You will complete the migration on your self-hosted instance.\n\n{% /section %}\n\n{% section #continue-on-self-hosted step=3 title=\"Continue on self-hosted\" %}\n\n1. 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.\n\n1. Select the data you wish to migrate. You can choose among accounts, databases, rows, files, functions, and sites.\n\n1. Click **Start Migration** to start the migration process. You do not need to keep the Appwrite Console open through the process.\n\n{% info title=\"Keep in mind\" %}\nYour 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.\n{% /info %}\n{% /section %}"}, {"path": "docs/advanced/migrations/firebase", "title": "Migrate from Firebase", "description": "Migrate seamlessly from Firebase to Appwrite. Learn how to transfer data, authentication, and services from Firebase to leverage Appwrite's capabilities.", "content": "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.\n\n{% info title=\"Charges\" %}\nWhen 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.\n{% /info %}\n\n{% section #create-service-account step=1 title=\"Create service account\" %}\n\nTo begin migrating to Appwrite, follow these steps.\n\n1. Open your Firebase console.\n\n1. Access your **Project Settings** by clicking the gear icon.\n\n1. Click on **Service Accounts**, then click on the **Manage service account permissions** link, which will redirect you to the Google Cloud Console.\n\n1. Click on **Create Service Account**, provide a name, ID, and description, and click **Continue**.\n\n1. Grant the service account the following roles\n\n| Role | Description |\n|-------------------------|------------------------------------------------------------------------------|\n| Firebase Viewer | Read access to your entire Firebase project, including Database and Storage. |\n| Identity Toolkit Viewer | Read access to your users, including their hash config. |\n\n{% /section %}\n\n{% section #create-api-key step=2 title=\"Create API key\" %}\n\n1. Find the service account you just created.\n\n1. Click the triple-dot icon to the right to see more options and click the **Manage keys** button.\n\n1. 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.\n\n{% /section %}\n\n{% section #start-migration step=3 title=\"Start Migration\" %}\n\n1. Create a new project and navigate to the **Migrations** tab in **Project Settings**.\n\n1. Click on the **Create Migration** button and select **Firebase** as your source.\n\n1. 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.\n\n{% /section %}\n{% section #next-steps step=4 title=\"Next steps\" %}\n\n1. 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.\n\n1. Remember to [add appropriate permissions](/docs/advanced/security/permissions) to the migrated resources to protect user data and privacy.\n\n1. Migrate functions manually, by [pick a runtime](/docs/products/functions/runtimes) and [learn to develop Appwrite Functions](/docs/products/functions/develop).\n\n1. Explore Appwrite's unique features by exploring the rest of the [Appwrite Documentation](/docs).\n\n{% /section %}\n\n# Limitations {% #limitations %}\n\nNot 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.\n\n- Appwrite Migrations only supports Firestore as a database source; Realtime Database is currently not supported.\n- At the moment, only top-level row migration is supported. Nested rows will not be migrated automatically.\n- 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.\n- Functions are not automatically migrated because of syntax and runtime differences."}, {"path": "docs/advanced/migrations/nhost", "title": "Migrate from Nhost", "description": "Transition to Appwrite from NHost with confidence. Explore migration steps, considerations, and tools to ensure a successful migration process.", "content": "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.\n\n{% info title=\"Charges\" %}\nWhen 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.\n{% /info %}\n\n{% section #obtain-credentials step=1 title=\"Obtain credentials\" %}\nFind all of the following credentials from your Nhost project.\n\n| Field | Description |\n| --------------- | -------------------------------------------------------------------------------------------------------------- |\n| **Region** | The region your Nhost project is hosted in. This can be found in your Nhost project environment variables as `NHOST_REGION`. |\n| **Subdomain** | The subdomain of your Nhost project. This can be found in your Nhost project environment variables as `NHOST_SUBDOMAIN`. |\n| **Database** | The name of your Nhost database. This can be found in your Nhost project Database settings. |\n| **Username** | The username of your Nhost database. This can be found in your Nhost project Database settings. |\n| **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. |\n| **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. |\n{% /section %}\n\n{% section #migration-process step=2 title=\"Migrating to Appwrite from Nhost\" %}\n\nBefore migrating to Appwrite make sure you've read the [migration overview](/docs/advanced/migrations) page.\n\n1. Create a new project and click on the **Migrations** tab in **Project Settings**.\n\n1. Click on the **Create Migration** button and select **Nhost** as your source.\n\n1. Enter the credentials from the [Obtain credentials](#obtain-credentials) step and click **Next**.\n\n1. Select the resources you want to migrate and finally click **Start migration** to begin the migration process.\n\n{% /section %}\n\n{% section #next-steps step=3 title=\"Next steps\" %}\n\n1. 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.\n\n1. Remember to [add appropriate permissions](/docs/advanced/security/permissions) to the migrated resources to protect user data and privacy.\n\n1. Migrate functions manually, by [pick a runtime](/docs/products/functions/runtimes) and [learn to develop Appwrite Functions](/docs/products/functions/develop).\n\n1. Explore Appwrite's unique features by exploring the rest of the [Appwrite Documentation](/docs).\n\n{% /section %}\n\n# Limitations {% #limitations %}\n\nNot 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.\n\n- 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.\n- 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.\n- Functions are not automatically migrated because of syntax and runtime differences."}, {"path": "docs/advanced/migrations/self-hosted", "title": "Migrate from self-hosted", "description": "Migrate to Appwrite from self-hosted platforms seamlessly. Learn how to move your applications and data to Appwrite for enhanced flexibility and control.", "content": "Migrations makes it as easy as a couple clicks to move all of your self-hosted project data to a Cloud instance.\n\n{% section #notices step=1 title=\"Things to keep in mind\" %}\n\n1. Data transferred by migrations will reset `$createdAt` and `$updatedAt` timestamps to the date of the migration.\n\n2. Your self-hosted Appwrite project must be accessible from the internet for the migration to work.\n\n3. Migrations are non-destructive. No data will be deleted or lost in the source project.\n\n{% /section %}\n\n\n{% section #create-migration step=2 title=\"Create migration\" %}\n\nTo begin migrating to Cloud, make sure to read the [migration overview](/docs/advanced/migrations) \nand [things to keep in mind](#notices) sections above.\n\n1. Navigate to your self-hosted project's Console and click on the **Migrations** tab.\n\n2. Click **Deploy to Cloud**, you will be redirected to Appwrite Cloud.\n\n3. You will complete the migration on Appwrite Cloud.\n\n{% /section %}\n\n{% section #continue-on-cloud step=3 title=\"Continue on Appwrite Cloud\" %}\n\n1. 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.\n\n2. Select the data you wish to migrate. You can choose among accounts, databases, rows, files, functions, and sites.\n\n3. Click **Start migration** to start the migration process. You do not need to keep the Appwrite Console open through the process.\n\n{% /section %}"}, {"path": "docs/advanced/migrations/supabase", "title": "Migrate from Supabase", "description": "Effortlessly migrate from Supabase to Appwrite. Discover migration strategies, data transfer methods, and tips for a smooth transition to Appwrite.", "content": "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.\n\n{% info title=\"Charges\" %}\nWhen 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.\n{% /info %}\n\n{% section #obtain-credentials step=1 title=\"Obtain credentials\" %}\n\nFind all of the following credentials from your Supabase project.\n\n1. Go to your Supabase project dashboard and click the **Connect** button in the top right corner.\n\n1. A modal will appear with several connection options. Select **Transaction pooler** (recommended for migrations).\n\n1. Copy the connection details from the modal to retrieve the **Host**, **Port** and **Username** fields below.\n\n| Field | Description |\n| ---------------| -------------------------------------------------------------------------------------------------------------- |\n| **Host** | The host of your Supabase Database, found in the **Transaction pooler** connection string from the **Connect** modal. |\n| **Port** | The port of your Supabase Database, found in the **Transaction pooler** connection string. |\n| **Username** | The username of your Supabase Database, found in the **Transaction pooler** connection string from the **Connect** modal. |\n| **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**. |\n| **Endpoint** | This is the endpoint of your Supabase instance under **Project Settings > API > Project URL**. This is used to migrate your files. |\n| **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. |\n\n{% /section %}\n\n{% section #create-migration step=2 title=\"Create migration\" %}\n\nBefore migrating to Appwrite make sure you've read the [migration overview](/docs/advanced/migrations) page.\n\n1. Create a new project and click on the **Migrations** tab in **Project Settings**.\n\n1. Click on the **Create Migration** button and select **Supabase** as your source.\n\n1. Enter the credentials from the [Obtain credentials](#obtain-credentials) step and click **Next**.\n\n1. Select the resources you want to migrate and finally click **Start migration** to begin the migration process.\n\n{% /section %}\n\n{% section #next-steps step=3 title=\"Next steps\" %}\n\n1. 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.\n\n1. Remember to [add appropriate permissions](/docs/advanced/security/permissions) to the migrated resources to protect user data and privacy.\n\n1. Migrate functions manually, by [pick a runtime](/docs/products/functions/runtimes) and [learn to develop Appwrite Functions](/docs/products/functions/develop).\n\n1. Explore Appwrite's unique features by exploring the rest of the [Appwrite Documentation](/docs).\n\n{% /section %}\n\n# Limitations {% #limitations %}\n\nNot 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.\n\n- 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.\n- 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.\n- Functions are not automatically migrated because of syntax and runtime differences."}, {"path": "docs/advanced/release-policy", "title": "Release policy", "description": "Understand how Appwrite releases and versions its platforms and APIs.", "content": "We value the trust of developers in Appwrite as the backbone of their applications.\nOur release policy is designed to provide developers with a reliable and consistent experience when using Appwrite.\nWe are committed to providing support for our API, SDKs, and product versions for a reasonable length of time,\nand we follow industry-standard versioning protocols.\nAppwrite will prioritize security updates and will release new versions as soon as possible to fix any security vulnerabilities.\n\n# Schedule {% #schedule %}\n\nWe work to release a new minor version of the product every quarter, which will include new features and enhancements.\n\nWe prioritize the timely release of patch versions with bug fixes and security updates on top of these feature releases,\nand we make every effort to ensure that our releases are thoroughly tested and stable before they are made available to developers.\n\nIn 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),\nnewsletter, and other communication channels.\n\n# Scope {% #scope %}\nAppwrite will provide two phases of continued support for older versions of Appwrite.\n\n{% table %}\n* Phase {% width=100 %}\n* Scope\n---\n* Support\n* Receive continued bug fixes and security updates.\n---\n* Extended security support\n* Receives only security updates.\n{% /table %}\n\n# Support {% #support %}\nAppwrite commits to the continued support of our software with extended support policies for security related fixes for older versions of Appwrite.\nSupported versions will continue to receive bug fixes and security updates. Extended security support versions will only receive security updates.\n{% table %}\n* Releases {% width=100 %}\n* Current Version\n* Support\n* Extended Security Support\n---\n* API\n* `v1`\n* 3 latest versions\n* 10 years\n---\n* SDK\n* [See list](/docs/sdks)\n* 5 latest versions\n* 10 years\n---\n* Self-hosted\n* `1.5.x`\n* Latest major version `>= 1.x.x`\n* 10 years\n---\n* Runtimes\n* [See list](/docs/products/functions/runtimes)\n* 24 months\n* Per vendor\n{% /table %}\n# API versions {% #api-versions %}\n\nWe are committed to providing developers with a stable and reliable API.\nAppwrite API versions don’t change very often but are reserved for breaking changes.\n\nCurrently, the latest stable version of the Appwrite API is `/v1`.\nWe use this prefix in all our API endpoint paths to allow API versioning.\nAny new Appwrite version will retain **backward compatibility** for any supported API version\nas long as this API version is still under maintenance support.\n\nWe will provide standard maintenance support for the last 3 API versions.\nOnce a version is no longer in this maintenance period, we will continue to provide support for security fixes for an additional ten years.\nBased on usage, we may decide to extend support for a specific version.\nSelf-hosted versions of Appwrite will receive continued support for the latest major version of Appwrite.\n\nIf you need **extended support** for older versions of Appwrite, [contact us](/contact-us) for more information.\n\n# SDK versioning {% #sdk-versioning %}\n\nFor 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.\n\nAll 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.\nOnce 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.\n\nWe provide early notice to developers and slowly introduce breaking changes to let developers adjust their application at a reasonable pace.\nWe 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.\n\n# Self-hosted versioning {% #self-hosted-versioning %}\n\nWhen 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.\n\nAppwrite 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.\n\nAll 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.\nIn 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.\n\nOnce 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.\nThis is usually required when we make adjustments to the under the hood data structure for supporting new features and improving maintainability.\nIf 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.\n\n{% arrow_link href=\"/docs/advanced/self-hosting/update\" %}\nLearn more about updating self-hosted Appwrite\n{% /arrow_link %}\n\n# Runtime versioning {% #runtime-versioning %}\n\n[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.\nAppwrite will support and maintain runtimes as a package, which covers the specific combination of operating system, programming language, and software libraries.\n\nAppwrite 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.\nYou can review the [list of supported runtimes](/docs/products/functions/runtimes) on Appwrite.\n\nIn most cases, the end-of-life date of a language version or operating system is known well in advance.\nThe links below give end-of-life schedules for each language that Appwrite supports as a managed runtime.\n\n## Runtimes language {% #runtime-languages %}\n\n{% table %}\n* Language {% width=150 %}\n* LTS policy\n---\n* Node.js\n* [https://nodejs.org](https://nodejs.org)\n---\n* Python\n* [devguide.python.org](https://devguide.python.org)\n---\n* Ruby\n* [www.ruby-lang.org](https://www.ruby-lang.org)\n---\n* Java\n* [www.oracle.com](https://www.oracle.com)\n---\n* .NET Core\n* [dotnet.microsoft.com](https://dotnet.microsoft.com)\n---\n* PHP\n* [https://www.php.net](https://www.php.net)\n---\n* Dart\n* [https://dart.dev/](https://dart.dev/)\n---\n* Deno\n* [https://deno.com/runtime](https://deno.com/runtime)\n---\n* Go\n* [https://go.dev/](https://go.dev/)\n---\n* Swift\n* [https://developer.apple.com/swift](https://developer.apple.com/swift)\n---\n* Kotlin\n* [https://kotlinlang.org](https://kotlinlang.org)\n---\n* C++\n* [https://en.cppreference.com/w/](https://en.cppreference.com/w/)\n{% /table %}\n\n## Runtimes OS {% #runtime-os %}\n{% table %}\n* OS {% width=150 %}\n* LTS policy\n---\n* Alpine\n* [https://www.alpinelinux.org](https://www.alpinelinux.org)\n---\n* Debian\n* [https://www.debian.org/](https://www.debian.org/)\n---\n* Ubuntu\n* [https://releases.ubuntu.com/](https://releases.ubuntu.com/)\n{% /table %}"}, {"path": "docs/advanced/security", "title": "Security", "description": "Learn how Appwrite keeps your project, users, and data secure through security measures and compliance.", "content": "Appwrite helps you build secure apps by applying various security and compliance measures.\nAppwrite is compliant with [GDPR](/docs/advanced/security/gdpr), [CCPA](/docs/advanced/security/ccpa),\n[HIPAA](/docs/advanced/security/hipaa), and [SOC 2](/docs/advanced/security/soc2).\n\nAppwrite also employs [enhanced password protection and encryption](/docs/products/auth/security), [rate limits](/docs/advanced/security/abuse-protection),\n[robust permission systems](/docs/advanced/security/permissions), and [HTTPS/TLS](/docs/advanced/security/tls) to protect you and your users' data.\n\n# Compliance {% #compliance %}\n\nThe safeguarding of your and your users' data is taken seriously at Appwrite.\nAppwrite works to achieve compliance with a variety of standards to protect sensitive data, as well as maintain trust and credibility.\n\n{% cards %}\n{% cards_item href=\"/docs/advanced/security/gdpr\" title=\"GDPR\" %}\nAppwrite is GDPR compliant. Learn about our measures, privacy policy, and find our data processing agreement.\n{% /cards_item %}\n\n{% cards_item href=\"/docs/advanced/security/pci\" title=\"PCI\" %}\nAppwrite uses Stripe to handle payment and payment information securely. Learn about Appwrite's PCI compliance.\n{% /cards_item %}\n\n{% cards_item href=\"/docs/advanced/security/soc2\" title=\"SOC 2\" %}\nAppwrite is SOC2 Type I compliant. Learn about Appwrite's measures to meet SOC 2 standards.\n{% /cards_item %}\n\n{% cards_item href=\"/docs/advanced/security/hipaa\" title=\"HIPAA\" %}\nAppwrite is HIPAA compliant. Learn about Appwrite's measures to protect personal health information.\n{% /cards_item %}\n\n{% cards_item href=\"/docs/advanced/security/ccpa\" title=\"CCPA\" %}\nAppwrite is CCPA compliant. Learn about our measures to protect consumer privacy under the California Consumer Privacy Act.\n{% /cards_item %}\n{% /cards %}\n\n# Measures {% #measures %}\n\nAppwrite employs a variety of measures to help you build secure applications, faster.\nLearn about the different ways Appwrite protects you and your users' data and privacy.\n\n{% cards %}\n{% cards_item href=\"/docs/products/auth/security\" title=\"Authentication\" %}\nSecure authentication methods to\nprotect your users and promote better passwords.\n{% /cards_item %}\n\n{% cards_item href=\"/docs/advanced/security/encryption\" title=\"Encryption\" %}\nAppwrite encrypts sensitive data and files\nin Appwrite Databases and Storage.\n{% /cards_item %}\n\n{% cards_item href=\"/docs/advanced/security/mfa\" title=\"Multi-factor authentication\" %}\nAdd a second layer of protection by requiring\nusers to verify their identity with multiple factors.\n{% /cards_item %}\n\n{% cards_item href=\"/docs/advanced/security/https\" title=\"HTTPS\" %}\nAppwrite Cloud enforces HTTPS on all endpoints to prevent on-path\nattacks like packet sniffing.\n{% /cards_item %}\n\n{% cards_item href=\"/docs/advanced/security/tls\" title=\"TLS\" %}\nAppwrite assigns TLS certificates on all\nAppwrite and user provided domains connected to Appwrite.\n{% /cards_item %}\n\n{% cards_item href=\"/docs/advanced/security/backups\" title=\"Backups\" %}\nAppwrite Cloud uses regular backups to prevent\ndata loss and improve resiliency.\n{% /cards_item %}\n\n{% cards_item href=\"/docs/advanced/security/penetration-tests\" title=\"Penetration tests\" %}\nAppwrite employs regular third-party penetration tests\nto find vulnerabilities.\n{% /cards_item %}\n\n{% cards_item href=\"/docs/advanced/security/audit-logs\" title=\"Audit logs\" %}\nAppwrite provides detailed audit logs for each\nproduct to track and discover suspicious activity.\n{% /cards_item %}\n\n{% cards_item href=\"/docs/advanced/security/abuse-protection\" title=\"Abuse protection\" %}\nAppwrite protects against common abuse methods\nlike DoS and brute-force attacks.\n{% /cards_item %}\n\n{% /cards %}\n\n# Access control {% #access-control %}\n\nAppwrite is secure by default and provides tools for you to manage\naccess control and prevent abuse.\n\n{% cards %}\n{% cards_item href=\"/docs/advanced/security/permissions\" title=\"Permissions\" %}\nControl which users can access which resources.\n{% /cards_item %}\n\n{% cards_item href=\"/docs/advanced/security/roles\" title=\"Roles\" %}\nLearn about Console organization member roles and the access each one grants.\n{% /cards_item %}\n\n{% cards_item href=\"/docs/advanced/security/rate-limits\" title=\"Rate limits\" %}\nAppwrite has rate limits on some endpoints to prevent abuse.\n{% /cards_item %}\n\n{% cards_item href=\"/docs/advanced/security/api-keys\" title=\"API keys\" %}\nCreate and manage API keys used by Server SDKs.\n{% /cards_item %}\n\n{% cards_item href=\"/docs/advanced/security/dev-keys\" title=\"Dev keys\" %}\nCreate and manage dev keys used by Client SDKs in dev environments.\n{% /cards_item %}\n{% /cards %}\n\n# Configuration {% #configuration %}\n\nPass constants and secrets to your Appwrite resources.\n\n{% cards %}\n{% cards_item href=\"/docs/advanced/security/environment-variables\" title=\"Environment variables\" %}\nUse project, function, and site environment variables to pass constants and secrets to your Functions and Sites.\n{% /cards_item %}\n{% /cards %}\n\n# Reporting vulnerabilities {% #reporting-vulnerabilities %}\nIf you discover security vulnerabilities, please contact us at security@appwrite.io.\nPlease avoid **posting a public issue** on GitHub or elsewhere online to prevent malicious actors\nfrom abusing the vulnerabilities before the Appwrite team has chance to patch the issue."}, {"path": "docs/advanced/security/abuse-protection", "title": "Abuse protection", "description": "Learn how Appwrite protects your apps from abuse through rate limiting and cross-site scripting protection.", "content": "Appwrite comes packaged with tools to protect against various forms of abuse, like brute force attacks, data scraping, and many\nother common forms of abuse.\n\n# Rate limiting {% #rate-limiting %}\nAppwrite uses rate limits on some endpoints to avoid abuse or brute-force attacks against Appwrite's REST API.\nEach Appwrite route documentation has information about any rate limits that might apply to them.\n\nRate limits limit the number of requests a user or IP can make against an API within a period of time.\nRate limits help protect against brute force attacks against authentication endpoints and other forms of API\nabuse like [denial of service attacks](https://en.wikipedia.org/wiki/Denial-of-service_attack).\n\n{% arrow_link href=\"/docs/advanced/security/rate-limits\" %}\nLearn more about rate limits\n{% /arrow_link %}\n\n# Email policies {% #email-policies %}\nEmail 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.\n\n{% arrow_link href=\"/docs/products/auth/email-policies\" %}\nLearn more about email policies\n{% /arrow_link %}\n\n# Cross-origin resource sharing (CORS) {% #CORS %}\nAppwrite limits who can make requests to Appwrite's APIs by default.\nThis means that unless your app's domain is added to Appwrite as a platform, requests are rejected.\nBy being explicit with the domains that are allowed\nto make requests to your Appwrite project, requests from JavaScript hosted on unknown domains\nwill not be accepted.\n\nYou can add new platforms by navigating to **Overview** > **Platforms** > **Add platform**.\n\n{% only_dark %}\n![Add a platform](/images/docs/quick-starts/dark/add-platform.avif)\n{% /only_dark %}\n{% only_light %}\n![Add a platform](/images/docs/quick-starts/add-platform.avif)\n{% /only_light %}\n\n{% arrow_link href=\"/blog/post/cors-error\" %}\nLearn more about debugging CORS errors\n{% /arrow_link %}\n\n# DDoS protection {% #ddos-protection %}\nAppwrite Cloud's infrastructure is protected with always-on DDoS protection.\nAppwrite's DDoS protection operates across multiple layers, including the Network (layer 3), Transport (layer 4), and Application (layer 7) layers.\nThis 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.\n\nAdditionally, 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."}, {"path": "docs/advanced/security/api-keys", "title": "API keys", "description": "Secure your application with Appwrite API Keys. Discover how to create and manage API keys to control access and enhance your application's security.", "content": "API keys are secrets used by Appwrite [Server SDKs](/docs/sdks#server) and the Appwrite CLI to prove their identity.\nWhat can be accessed each API key is restricted by [scopes](#scopes) instead of permissions.\n\n{% info title=\"Best practice\" %}\nIt is a best practice to grant only the scopes you need to meet your project's goals to an API key. \nAPI keys should be treated as a secret. Never share the API key and keep API keys out of client applications.\n{% /info %}\n\n# API keys vs Dev keys {% #api-keys-vs-dev-keys %}\n\nAPI keys and [Dev keys](/docs/advanced/security/dev-keys) are not the same and cannot be used interchangeably.\n\nAPI 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.\n\nAPI keys are for server SDKs and the CLI in production environments, while Dev keys are for client SDKs in development environments.\n\n# Create API key {% #create-api-key %}\n\n{% only_dark %}\n![Project settings screen](/images/docs/platform/dark/create-api-key.avif)\n{% /only_dark %}\n{% only_light %}\n![Project settings screen](/images/docs/platform/create-api-key.avif)\n{% /only_light %}\n\nTo create a new API key, navigate to **Overview** > **Integration** > **API keys** and click **Create API key**.\n\nYou can then use the API key to initialize the Appwrite client in your server-side apps.\n\n{% multicode %}\n```server-nodejs\nconst sdk = require('node-appwrite');\n\nconst client = new sdk.Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey(''); // Your API key\n```\n\n```server-python\nfrom appwrite.client import Client\n\nclient = Client()\nclient.set_endpoint('https://.cloud.appwrite.io/v1')\nclient.set_project('')\nclient.set_key('') # Your API key\n```\n\n```server-php\nuse Appwrite\\Client;\n\n$client = new Client();\n$client\n ->setEndpoint('https://.cloud.appwrite.io/v1')\n ->setProject('')\n ->setKey(''); // Your API key\n```\n\n```server-go\npackage main\n\nimport \"github.com/appwrite/sdk-for-go/client\"\n\nfunc main() {\n c := client.NewClient()\n c.SetEndpoint(\"https://.cloud.appwrite.io/v1\")\n c.SetProject(\"\")\n c.SetKey(\"\") // Your API key\n}\n```\n\n```server-ruby\nrequire 'appwrite'\n\nclient = Appwrite::Client.new\nclient.set_endpoint('https://.cloud.appwrite.io/v1')\nclient.set_project('')\nclient.set_key('') # Your API key\n```\n\n```server-deno\nimport { Client } from 'npm:node-appwrite';\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey(''); // Your API key\n```\n\n```server-dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\n\nClient client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey(''); // Your API key\n```\n\n```server-kotlin\nimport io.appwrite.Client\n\nval client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\") // Your API key\n```\n\n```server-dotnet\nusing Appwrite;\n\nvar client = new Client()\n .SetEndpoint(\"https://.cloud.appwrite.io/v1\")\n .SetProject(\"\")\n .SetKey(\"\"); // Your API key\n```\n\n```server-swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\") // Your API key\n```\n\n```server-java\nimport io.appwrite.Client;\n\nClient client = new Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\"); // Your API key\n```\n\n```server-rust\nuse appwrite::Client;\n\nlet client = Client::new()\n .set_endpoint(\"https://.cloud.appwrite.io/v1\")\n .set_project(\"\")\n .set_key(\"\"); // Your API key\n```\n{% /multicode %}\n\nWhen adding a new API Key, you can choose which [scopes](#scopes) to grant your application.\nIf you need to replace your API Key, create a new key, update your app credentials and, once ready, delete your old key.\n\n# Manage API keys with a Server SDK {% #manage-api-keys-with-a-server-sdk %}\n\nYou can also manage API keys programmatically using a Server SDK. This requires an API key with the `keys.read` and `keys.write` [scopes](#scopes).\n\n{% info title=\"Sensitive scopes\" %}\nThe `keys.read` and `keys.write` scopes are very sensitive. An API key with `keys.write` can create new keys with any scope, effectively granting full access to your project. Only assign these scopes to keys used in trusted, secure environments, and never expose them in client-side applications.\n{% /info %}\n\n## List API keys {% #list-api-keys %}\n\n{% multicode %}\n```server-nodejs\nimport { Client, Project } from 'node-appwrite';\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nconst project = new Project(client);\n\nconst result = await project.listKeys();\n```\n```server-deno\nimport { Client, Project } from \"npm:node-appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nconst project = new Project(client);\n\nconst result = await project.listKeys();\n```\n```server-php\nsetEndpoint('https://.cloud.appwrite.io/v1')\n ->setProject('')\n ->setKey('');\n\n$project = new Project($client);\n\n$result = $project->listKeys();\n```\n```server-python\nfrom appwrite.client import Client\nfrom appwrite.services.project import Project\n\nclient = Client()\nclient.set_endpoint('https://.cloud.appwrite.io/v1')\nclient.set_project('')\nclient.set_key('')\n\nproject = Project(client)\n\nresult = project.list_keys()\n```\n```server-ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://.cloud.appwrite.io/v1')\n .set_project('')\n .set_key('')\n\nproject = Project.new(client)\n\nresponse = project.list_keys()\n```\n```server-dotnet\nusing Appwrite;\nusing Appwrite.Services;\n\nClient client = new Client()\n .SetEndPoint(\"https://.cloud.appwrite.io/v1\")\n .SetProject(\"\")\n .SetKey(\"\");\n\nProject project = new Project(client);\n\nvar result = await project.ListKeys();\n```\n```server-dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\n\nClient client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nProject project = Project(client);\n\nfinal result = await project.listKeys();\n```\n```server-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Project\n\nval client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\")\n\nval project = Project(client)\n\nval result = project.listKeys()\n```\n```server-java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Project;\n\nClient client = new Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\");\n\nProject project = new Project(client);\n\nproject.listKeys(\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n System.out.println(result);\n })\n);\n```\n```server-swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\")\n\nlet project = Project(client)\n\nlet result = try await project.listKeys()\n```\n```server-go\npackage main\n\nimport (\n \"fmt\"\n \"github.com/appwrite/sdk-for-go/appwrite\"\n)\n\nfunc main() {\n client := appwrite.NewClient(\n appwrite.WithEndpoint(\"https://.cloud.appwrite.io/v1\"),\n appwrite.WithProject(\"\"),\n appwrite.WithKey(\"\"),\n )\n\n project := appwrite.NewProject(client)\n result, err := project.ListKeys()\n\n if err != nil {\n panic(err)\n }\n\n fmt.Println(result)\n}\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::project::Project;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box> {\n let client = Client::new()\n .set_endpoint(\"https://.cloud.appwrite.io/v1\")\n .set_project(\"\")\n .set_key(\"\");\n\n let project = Project::new(&client);\n\n let result = project.list_keys(\n None, // queries\n None, // total\n ).await?;\n\n println!(\"{:?}\", result);\n Ok(())\n}\n```\n{% /multicode %}\n\n## Get an API key {% #get-an-api-key %}\n\n{% multicode %}\n```server-nodejs\nimport { Client, Project } from 'node-appwrite';\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nconst project = new Project(client);\n\nconst result = await project.getKey({\n keyId: ''\n});\n```\n```server-deno\nimport { Client, Project } from \"npm:node-appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nconst project = new Project(client);\n\nconst result = await project.getKey({\n keyId: ''\n});\n```\n```server-php\nsetEndpoint('https://.cloud.appwrite.io/v1')\n ->setProject('')\n ->setKey('');\n\n$project = new Project($client);\n\n$result = $project->getKey(\n keyId: ''\n);\n```\n```server-python\nfrom appwrite.client import Client\nfrom appwrite.services.project import Project\n\nclient = Client()\nclient.set_endpoint('https://.cloud.appwrite.io/v1')\nclient.set_project('')\nclient.set_key('')\n\nproject = Project(client)\n\nresult = project.get_key(\n key_id=''\n)\n```\n```server-ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://.cloud.appwrite.io/v1')\n .set_project('')\n .set_key('')\n\nproject = Project.new(client)\n\nresponse = project.get_key(\n key_id: ''\n)\n```\n```server-dotnet\nusing Appwrite;\nusing Appwrite.Services;\n\nClient client = new Client()\n .SetEndPoint(\"https://.cloud.appwrite.io/v1\")\n .SetProject(\"\")\n .SetKey(\"\");\n\nProject project = new Project(client);\n\nvar result = await project.GetKey(\n keyId: \"\"\n);\n```\n```server-dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\n\nClient client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nProject project = Project(client);\n\nfinal result = await project.getKey(\n keyId: '',\n);\n```\n```server-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Project\n\nval client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\")\n\nval project = Project(client)\n\nval result = project.getKey(\n keyId = \"\"\n)\n```\n```server-java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Project;\n\nClient client = new Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\");\n\nProject project = new Project(client);\n\nproject.getKey(\n \"\", // keyId\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n System.out.println(result);\n })\n);\n```\n```server-swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\")\n\nlet project = Project(client)\n\nlet result = try await project.getKey(\n keyId: \"\"\n)\n```\n```server-go\npackage main\n\nimport (\n \"fmt\"\n \"github.com/appwrite/sdk-for-go/appwrite\"\n)\n\nfunc main() {\n client := appwrite.NewClient(\n appwrite.WithEndpoint(\"https://.cloud.appwrite.io/v1\"),\n appwrite.WithProject(\"\"),\n appwrite.WithKey(\"\"),\n )\n\n project := appwrite.NewProject(client)\n result, err := project.GetKey(\n \"\",\n )\n\n if err != nil {\n panic(err)\n }\n\n fmt.Println(result)\n}\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::project::Project;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box> {\n let client = Client::new()\n .set_endpoint(\"https://.cloud.appwrite.io/v1\")\n .set_project(\"\")\n .set_key(\"\");\n\n let project = Project::new(&client);\n\n let result = project.get_key(\n \"\",\n ).await?;\n\n println!(\"{:?}\", result);\n Ok(())\n}\n```\n{% /multicode %}\n\n## Create an API key {% #create-an-api-key %}\n\n{% multicode %}\n```server-nodejs\nimport { Client, Project, ID, Scopes } from 'node-appwrite';\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nconst project = new Project(client);\n\nconst result = await project.createKey({\n keyId: ID.unique(),\n name: 'My API Key',\n scopes: [Scopes.DatabasesRead, Scopes.DatabasesWrite],\n expire: '2026-12-31T23:59:59.000+00:00'\n});\n```\n```server-deno\nimport { Client, Project, ID, Scopes } from \"npm:node-appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nconst project = new Project(client);\n\nconst result = await project.createKey({\n keyId: ID.unique(),\n name: 'My API Key',\n scopes: [Scopes.DatabasesRead, Scopes.DatabasesWrite],\n expire: '2026-12-31T23:59:59.000+00:00'\n});\n```\n```server-php\nsetEndpoint('https://.cloud.appwrite.io/v1')\n ->setProject('')\n ->setKey('');\n\n$project = new Project($client);\n\n$result = $project->createKey(\n keyId: ID::unique(),\n name: 'My API Key',\n scopes: ['databases.read', 'databases.write'],\n expire: '2026-12-31T23:59:59.000+00:00'\n);\n```\n```server-python\nfrom appwrite.client import Client\nfrom appwrite.id import ID\nfrom appwrite.services.project import Project\nfrom appwrite.enums.scopes import Scopes\n\nclient = Client()\nclient.set_endpoint('https://.cloud.appwrite.io/v1')\nclient.set_project('')\nclient.set_key('')\n\nproject = Project(client)\n\nresult = project.create_key(\n key_id=ID.unique(),\n name='My API Key',\n scopes=[Scopes.DATABASES_READ, Scopes.DATABASES_WRITE],\n expire='2026-12-31T23:59:59.000+00:00'\n)\n```\n```server-ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://.cloud.appwrite.io/v1')\n .set_project('')\n .set_key('')\n\nproject = Project.new(client)\n\nresponse = project.create_key(\n key_id: ID.unique(),\n name: 'My API Key',\n scopes: ['databases.read', 'databases.write'],\n expire: '2026-12-31T23:59:59.000+00:00'\n)\n```\n```server-dotnet\nusing Appwrite;\nusing Appwrite.Enums;\nusing Appwrite.Services;\nusing Appwrite.Models;\n\nClient client = new Client()\n .SetEndPoint(\"https://.cloud.appwrite.io/v1\")\n .SetProject(\"\")\n .SetKey(\"\");\n\nProject project = new Project(client);\n\nvar result = await project.CreateKey(\n keyId: ID.Unique(),\n name: \"My API Key\",\n scopes: new List {Scopes.DatabasesRead, Scopes.DatabasesWrite},\n expire: \"2026-12-31T23:59:59.000+00:00\"\n);\n```\n```server-dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\nimport 'package:dart_appwrite/enums.dart' as enums;\n\nClient client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nProject project = Project(client);\n\nfinal result = await project.createKey(\n keyId: ID.unique(),\n name: 'My API Key',\n scopes: [enums.Scopes.databasesRead, enums.Scopes.databasesWrite],\n expire: '2026-12-31T23:59:59.000+00:00',\n);\n```\n```server-kotlin\nimport io.appwrite.Client\nimport io.appwrite.ID\nimport io.appwrite.enums.Scopes\nimport io.appwrite.services.Project\n\nval client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\")\n\nval project = Project(client)\n\nval result = project.createKey(\n keyId = ID.unique(),\n name = \"My API Key\",\n scopes = listOf(Scopes.DATABASES_READ, Scopes.DATABASES_WRITE),\n expire = \"2026-12-31T23:59:59.000+00:00\"\n)\n```\n```server-java\nimport io.appwrite.Client;\nimport io.appwrite.ID;\nimport io.appwrite.enums.Scopes;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Project;\n\nClient client = new Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\");\n\nProject project = new Project(client);\n\nproject.createKey(\n ID.unique(), // keyId\n \"My API Key\", // name\n List.of(Scopes.DATABASES_READ, Scopes.DATABASES_WRITE), // scopes\n \"2026-12-31T23:59:59.000+00:00\", // expire\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n System.out.println(result);\n })\n);\n```\n```server-swift\nimport Appwrite\nimport AppwriteEnums\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\")\n\nlet project = Project(client)\n\nlet result = try await project.createKey(\n keyId: ID.unique(),\n name: \"My API Key\",\n scopes: [Scopes.databasesRead, Scopes.databasesWrite],\n expire: \"2026-12-31T23:59:59.000+00:00\"\n)\n```\n```server-go\npackage main\n\nimport (\n \"fmt\"\n \"github.com/appwrite/sdk-for-go/appwrite\"\n \"github.com/appwrite/sdk-for-go/id\"\n)\n\nfunc main() {\n client := appwrite.NewClient(\n appwrite.WithEndpoint(\"https://.cloud.appwrite.io/v1\"),\n appwrite.WithProject(\"\"),\n appwrite.WithKey(\"\"),\n )\n\n project := appwrite.NewProject(client)\n result, err := project.CreateKey(\n id.Unique(),\n \"My API Key\",\n []string{\"databases.read\", \"databases.write\"},\n project.WithCreateKeyExpire(\"2026-12-31T23:59:59.000+00:00\"),\n )\n\n if err != nil {\n panic(err)\n }\n\n fmt.Println(result)\n}\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::id::ID;\nuse appwrite::enums::Scopes;\nuse appwrite::services::project::Project;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box> {\n let client = Client::new()\n .set_endpoint(\"https://.cloud.appwrite.io/v1\")\n .set_project(\"\")\n .set_key(\"\");\n\n let project = Project::new(&client);\n\n let result = project.create_key(\n ID::unique(), // key_id\n \"My API Key\", // name\n vec![Scopes::DatabasesRead, Scopes::DatabasesWrite], // scopes\n Some(\"2026-12-31T23:59:59.000+00:00\"), // expire\n ).await?;\n\n println!(\"{:?}\", result);\n Ok(())\n}\n```\n{% /multicode %}\n\n## Update an API key {% #update-an-api-key %}\n\n{% multicode %}\n```server-nodejs\nimport { Client, Project, Scopes } from 'node-appwrite';\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nconst project = new Project(client);\n\nconst result = await project.updateKey({\n keyId: '',\n name: 'Updated Key',\n scopes: [Scopes.DatabasesRead, Scopes.DatabasesWrite, Scopes.UsersRead],\n expire: '2027-06-30T23:59:59.000+00:00'\n});\n```\n```server-deno\nimport { Client, Project, Scopes } from \"npm:node-appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nconst project = new Project(client);\n\nconst result = await project.updateKey({\n keyId: '',\n name: 'Updated Key',\n scopes: [Scopes.DatabasesRead, Scopes.DatabasesWrite, Scopes.UsersRead],\n expire: '2027-06-30T23:59:59.000+00:00'\n});\n```\n```server-php\nsetEndpoint('https://.cloud.appwrite.io/v1')\n ->setProject('')\n ->setKey('');\n\n$project = new Project($client);\n\n$result = $project->updateKey(\n keyId: '',\n name: 'Updated Key',\n scopes: ['databases.read', 'databases.write', 'users.read'],\n expire: '2027-06-30T23:59:59.000+00:00'\n);\n```\n```server-python\nfrom appwrite.client import Client\nfrom appwrite.services.project import Project\nfrom appwrite.enums.scopes import Scopes\n\nclient = Client()\nclient.set_endpoint('https://.cloud.appwrite.io/v1')\nclient.set_project('')\nclient.set_key('')\n\nproject = Project(client)\n\nresult = project.update_key(\n key_id='',\n name='Updated Key',\n scopes=[Scopes.DATABASES_READ, Scopes.DATABASES_WRITE, Scopes.USERS_READ],\n expire='2027-06-30T23:59:59.000+00:00'\n)\n```\n```server-ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://.cloud.appwrite.io/v1')\n .set_project('')\n .set_key('')\n\nproject = Project.new(client)\n\nresponse = project.update_key(\n key_id: '',\n name: 'Updated Key',\n scopes: ['databases.read', 'databases.write', 'users.read'],\n expire: '2027-06-30T23:59:59.000+00:00'\n)\n```\n```server-dotnet\nusing Appwrite;\nusing Appwrite.Enums;\nusing Appwrite.Services;\nusing Appwrite.Models;\n\nClient client = new Client()\n .SetEndPoint(\"https://.cloud.appwrite.io/v1\")\n .SetProject(\"\")\n .SetKey(\"\");\n\nProject project = new Project(client);\n\nvar result = await project.UpdateKey(\n keyId: \"\",\n name: \"Updated Key\",\n scopes: new List {Scopes.DatabasesRead, Scopes.DatabasesWrite, Scopes.UsersRead},\n expire: \"2027-06-30T23:59:59.000+00:00\"\n);\n```\n```server-dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\nimport 'package:dart_appwrite/enums.dart' as enums;\n\nClient client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nProject project = Project(client);\n\nfinal result = await project.updateKey(\n keyId: '',\n name: 'Updated Key',\n scopes: [enums.Scopes.databasesRead, enums.Scopes.databasesWrite, enums.Scopes.usersRead],\n expire: '2027-06-30T23:59:59.000+00:00',\n);\n```\n```server-kotlin\nimport io.appwrite.Client\nimport io.appwrite.enums.Scopes\nimport io.appwrite.services.Project\n\nval client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\")\n\nval project = Project(client)\n\nval result = project.updateKey(\n keyId = \"\",\n name = \"Updated Key\",\n scopes = listOf(Scopes.DATABASES_READ, Scopes.DATABASES_WRITE, Scopes.USERS_READ),\n expire = \"2027-06-30T23:59:59.000+00:00\"\n)\n```\n```server-java\nimport io.appwrite.Client;\nimport io.appwrite.enums.Scopes;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Project;\n\nClient client = new Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\");\n\nProject project = new Project(client);\n\nproject.updateKey(\n \"\", // keyId\n \"Updated Key\", // name\n List.of(Scopes.DATABASES_READ, Scopes.DATABASES_WRITE, Scopes.USERS_READ), // scopes\n \"2027-06-30T23:59:59.000+00:00\", // expire\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n System.out.println(result);\n })\n);\n```\n```server-swift\nimport Appwrite\nimport AppwriteEnums\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\")\n\nlet project = Project(client)\n\nlet result = try await project.updateKey(\n keyId: \"\",\n name: \"Updated Key\",\n scopes: [Scopes.databasesRead, Scopes.databasesWrite, Scopes.usersRead],\n expire: \"2027-06-30T23:59:59.000+00:00\"\n)\n```\n```server-go\npackage main\n\nimport (\n \"fmt\"\n \"github.com/appwrite/sdk-for-go/appwrite\"\n)\n\nfunc main() {\n client := appwrite.NewClient(\n appwrite.WithEndpoint(\"https://.cloud.appwrite.io/v1\"),\n appwrite.WithProject(\"\"),\n appwrite.WithKey(\"\"),\n )\n\n project := appwrite.NewProject(client)\n result, err := project.UpdateKey(\n \"\",\n \"Updated Key\",\n []string{\"databases.read\", \"databases.write\", \"users.read\"},\n project.WithUpdateKeyExpire(\"2027-06-30T23:59:59.000+00:00\"),\n )\n\n if err != nil {\n panic(err)\n }\n\n fmt.Println(result)\n}\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::enums::Scopes;\nuse appwrite::services::project::Project;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box> {\n let client = Client::new()\n .set_endpoint(\"https://.cloud.appwrite.io/v1\")\n .set_project(\"\")\n .set_key(\"\");\n\n let project = Project::new(&client);\n\n let result = project.update_key(\n \"\", // key_id\n \"Updated Key\", // name\n vec![Scopes::DatabasesRead, Scopes::DatabasesWrite, Scopes::UsersRead], // scopes\n Some(\"2027-06-30T23:59:59.000+00:00\"), // expire\n ).await?;\n\n println!(\"{:?}\", result);\n Ok(())\n}\n```\n{% /multicode %}\n\n## Delete an API key {% #delete-an-api-key %}\n\n{% multicode %}\n```server-nodejs\nimport { Client, Project } from 'node-appwrite';\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nconst project = new Project(client);\n\nawait project.deleteKey({\n keyId: ''\n});\n```\n```server-deno\nimport { Client, Project } from \"npm:node-appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nconst project = new Project(client);\n\nawait project.deleteKey({\n keyId: ''\n});\n```\n```server-php\nsetEndpoint('https://.cloud.appwrite.io/v1')\n ->setProject('')\n ->setKey('');\n\n$project = new Project($client);\n\n$project->deleteKey(\n keyId: ''\n);\n```\n```server-python\nfrom appwrite.client import Client\nfrom appwrite.services.project import Project\n\nclient = Client()\nclient.set_endpoint('https://.cloud.appwrite.io/v1')\nclient.set_project('')\nclient.set_key('')\n\nproject = Project(client)\n\nproject.delete_key(\n key_id=''\n)\n```\n```server-ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://.cloud.appwrite.io/v1')\n .set_project('')\n .set_key('')\n\nproject = Project.new(client)\n\nproject.delete_key(\n key_id: ''\n)\n```\n```server-dotnet\nusing Appwrite;\nusing Appwrite.Services;\n\nClient client = new Client()\n .SetEndPoint(\"https://.cloud.appwrite.io/v1\")\n .SetProject(\"\")\n .SetKey(\"\");\n\nProject project = new Project(client);\n\nawait project.DeleteKey(\n keyId: \"\"\n);\n```\n```server-dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\n\nClient client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nProject project = Project(client);\n\nawait project.deleteKey(\n keyId: '',\n);\n```\n```server-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Project\n\nval client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\")\n\nval project = Project(client)\n\nproject.deleteKey(\n keyId = \"\"\n)\n```\n```server-java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Project;\n\nClient client = new Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\");\n\nProject project = new Project(client);\n\nproject.deleteKey(\n \"\", // keyId\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n System.out.println(result);\n })\n);\n```\n```server-swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\")\n\nlet project = Project(client)\n\ntry await project.deleteKey(\n keyId: \"\"\n)\n```\n```server-go\npackage main\n\nimport (\n \"fmt\"\n \"github.com/appwrite/sdk-for-go/appwrite\"\n)\n\nfunc main() {\n client := appwrite.NewClient(\n appwrite.WithEndpoint(\"https://.cloud.appwrite.io/v1\"),\n appwrite.WithProject(\"\"),\n appwrite.WithKey(\"\"),\n )\n\n project := appwrite.NewProject(client)\n _, err := project.DeleteKey(\n \"\",\n )\n\n if err != nil {\n panic(err)\n }\n\n fmt.Println(\"API key deleted\")\n}\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::project::Project;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box> {\n let client = Client::new()\n .set_endpoint(\"https://.cloud.appwrite.io/v1\")\n .set_project(\"\")\n .set_key(\"\");\n\n let project = Project::new(&client);\n\n project.delete_key(\n \"\",\n ).await?;\n\n Ok(())\n}\n```\n{% /multicode %}\n\n# Scopes {% #scopes %}\n\nWhen adding a new API key, you choose which scopes to grant. Scopes are grouped by service, matching the categories shown in the Appwrite Console.\n\n{% accordion %}\n{% accordion_item title=\"Auth\" %}\n\n| Name | Description |\n|-----------------------|---------------------------------------------------------------------------------|\n| `sessions.write` | Access to create, update, and delete user sessions |\n| `users.read` | Access to read your project's users |\n| `users.write` | Access to create, update, and delete your project's users |\n| `teams.read` | Access to read your project's teams |\n| `teams.write` | Access to create, update, and delete your project's teams |\n\n{% /accordion_item %}\n{% accordion_item title=\"Databases\" %}\n\n| Name | Description |\n|-----------------------|---------------------------------------------------------------------------------|\n| `databases.read` | Access to read your project's databases |\n| `databases.write` | Access to create, update, and delete your project's databases |\n| `tables.read` | Access to read your project's database tables |\n| `tables.write` | Access to create, update, and delete your project's database tables |\n| `columns.read` | Access to read your project's database table columns |\n| `columns.write` | Access to create, update, and delete your project's database table columns |\n| `indexes.read` | Access to read your project's database table indexes |\n| `indexes.write` | Access to create, update, and delete your project's database table indexes |\n| `rows.read` | Access to read your project's database rows |\n| `rows.write` | Access to create, update, and delete your project's database rows |\n\n{% /accordion_item %}\n{% accordion_item title=\"Functions\" %}\n\n| Name | Description |\n|-----------------------|---------------------------------------------------------------------------------|\n| `functions.read` | Access to read your project's functions and code deployments |\n| `functions.write` | Access to create, update, and delete your project's functions and code deployments|\n| `execution.read` | Access to read your project's execution logs |\n| `execution.write` | Access to execute your project's functions |\n\n{% /accordion_item %}\n{% accordion_item title=\"Storage\" %}\n\n| Name | Description |\n|-----------------------|---------------------------------------------------------------------------------|\n| `files.read` | Access to read your project's storage files and preview images |\n| `files.write` | Access to create, update, and delete your project's storage files |\n| `buckets.read` | Access to read your project's storage buckets |\n| `buckets.write` | Access to create, update, and delete your project's storage buckets |\n\n{% /accordion_item %}\n{% accordion_item title=\"Messaging\" %}\n\n| Name | Description |\n|-----------------------|---------------------------------------------------------------------------------|\n| `providers.read` | Access to read your project's providers |\n| `providers.write` | Access to create, update, and delete your project's providers |\n| `messages.read` | Access to read your project's messages |\n| `messages.write` | Access to create, update, and delete your project's messages |\n| `topics.read` | Access to read your project's topics |\n| `topics.write` | Access to create, update, and delete your project's topics |\n| `subscribers.read` | Access to read your project's subscribers |\n| `subscribers.write` | Access to create, update, and delete your project's subscribers |\n| `targets.read` | Access to read your project's targets |\n| `targets.write` | Access to create, update, and delete your project's targets |\n\n{% /accordion_item %}\n{% accordion_item title=\"Sites\" %}\n\n| Name | Description |\n|-----------------------|---------------------------------------------------------------------------------|\n| `sites.read` | Access to read your project's sites and deployments |\n| `sites.write` | Access to create, update, and delete your project's sites and deployments |\n| `log.read` | Access to read your site's logs |\n| `log.write` | Access to update and delete your site's logs |\n\n{% /accordion_item %}\n{% accordion_item title=\"Other\" %}\n\n| Name | Description |\n|-----------------------|---------------------------------------------------------------------------------|\n| `locale.read` | Access to your project's Locale service |\n| `avatars.read` | Access to your project's Avatars service |\n| `health.read` | Access to read your project's health status |\n| `migrations.read` | Access to read your project's migrations |\n| `migrations.write` | Access to create, update, and delete your project's migrations |\n| `tokens.read` | Access to read your project's tokens |\n| `tokens.write` | Access to create, update, and delete your project's tokens |\n| `webhooks.read` | Access to read your project's webhooks |\n| `webhooks.write` | Access to create, update, and delete your project's webhooks |\n| `keys.read` | Access to read your project's API keys |\n| `keys.write` | Access to create, update, and delete your project's API keys |\n| `rules.read` | Access to read your project's proxy rules |\n| `rules.write` | Access to create, update, and delete your project's proxy rules |\n| `vcs.read` | Access to read your project's VCS repositories |\n| `vcs.write` | Access to create, update, and delete your project's VCS repositories |\n| `assistant.read` | Access to read the Assistant service |\n\n{% /accordion_item %}\n{% /accordion %}"}, {"path": "docs/advanced/security/audit-logs", "title": "Audit logs", "description": "Appwrite provides audit logs to help detect anomalies and investigate security incidents.", "content": "All Appwrite products, like Authentication, Databases, Storage, Functions, and Messaging, provide detailed audit logs.\nAudit logs are important in detecting and responding to security incidents.\nThrough audit logs, you can detect incidents through anomalous activities,\ntrace the source of security incidents, and understand the scope of users affected so you can respond more quickly\nand effectively.\n\n# Access audit logs {% #access-audit-logs %}\n\nYou can access audit logs for different products under the **Activity** tab where applicable.\nLogs are available for tables, rows, and individual users.\n\n{% only_dark %}\n![Project settings screen](/images/docs/advanced/security/dark/activity.avif)\n{% /only_dark %}\n\n{% only_light %}\n![Project settings screen](/images/docs/advanced/security/activity.avif)\n{% /only_light %}\n\n# Format {% #format %}\n\nAudit log entries under the **Activity** tab has the following structure.\nEach entry describes an event.\n\n{% table %}\n- Column\n- Description\n---\n- User\n- Name of the user that performed the event.\n---\n- Event\n- The name of the [event](/docs/apis/events).\n---\n- Location\n- The physical of the user when they performed the action.\n---\n- IP\n- The IP of the user when they performed an action.\n---\n- Date\n- The date and time of the event.\n{% /table %}\n\n# Retention {% #retention %}\n\nGDPR data retention rules require any personal data that is collected or processed to be kept\nonly for as long as data are required to achieve the purpose for which the information was collected.\nFor this reason, audit logs are retained for only 7 days for Pro organizations and 1 hour\nfor Free organizations."}, {"path": "docs/advanced/security/authentication", "title": "Authentication", "description": "Learn how Appwrite protects your passwords and helps users pick better passwords.", "content": "Appwrite helps you implement secure authentication in your applications by using password hashing to protect passwords in storage.\nAppwrite also provides tools to help users pick better passwords, making them harder to break.\n\n{% partial file=\"auth-security.md\" /%}"}, {"path": "docs/advanced/security/backups", "title": "Backups", "description": "Appwrite provides both self-managed project backups and automated disaster recovery backups to ensure data security and availability.", "content": "Preventing downtime and maintaining data availability is crucial for digital security. Appwrite provides both self-managed backups and automated disaster recovery backups.\n\nSelf-managed backups are available for Pro plans and above. These backups allow you to:\n- Configure automatic backup policies\n- Initiate manual backups through the Console\n- Recover from accidental data deletion\n- Restore data to a previous point in time\n\nFor detailed information about self-managed backup features, configuration options, and restoration procedures, visit our [Backup Documentation](/docs/products/databases/backups).\n\nFor 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."}, {"path": "docs/advanced/security/ccpa", "title": "CCPA", "description": "Protecting your and your users' data privacy is a priority at Appwrite. Learn about Appwrite's compliance with the California Consumer Privacy Act (CCPA).", "content": "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.\n\nTo confirm Appwrite's compliance with the CCPA, we have ensured the following rights for users:\n\n- **Right to know:** Appwrite users can request information about the personal data that is collected, shared, or sold.\n- **Right to delete:** Users can request that Appwrite delete their personal data, with certain exceptions when it comes to security.\n- **Right to opt-out:** Users can opt out of the sale or sharing of their personal data with third parties.\n- **Right to non-discrimination:** Appwrite does not discriminate against users who exercise their CCPA rights.\n- **Right to correct:** Users can correct inaccurate personal information held by Appwrite.\n- **Right to limit:** Users can limit how Appwrite uses and shares their sensitive personal information.\n\nSome of the measures that Appwrite has taken for compliance include:\n\n- CCPA references in the DPA for both customers and vendors.\n- A detailed summary of data subject rights under CCPA, with a commitment to assist customers in compliance.\n- Employee training on handling privacy-related inquiries and Appwrite's adherence to CCPA requirements.\n- Updates to Appwrite's data retention and deletion policies.\n- Revisions to the data breach and incident response policies.\n\nPlease 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.\nYou can reach us at `privacy@appwrite.io` for more questions."}, {"path": "docs/advanced/security/dev-keys", "title": "Dev keys", "description": "Bypass Appwrite rate limits and CORS errors in your development environment with Appwrite Dev keys.", "content": "{% info title=\"Deprecation notice\" %}\nDev keys are going to be deprecated on September 1, 2026. We recommend planning your migration away from dev keys ahead of time.\n{% /info %}\n\nDev 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:\n\n- Appwrite rate limits and CORS errors are bypassed\n- Configurable expiration date with 1 day, 7 days, and 30 day options\n\nThis 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.\n\n{% info title=\"Important note\" %}\nDev 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.\n{% /info %}\n\n# Dev keys vs API keys {% #dev-keys-vs-api-keys %}\n\nDev keys and [API keys](/docs/advanced/security/api-keys) are not the same and cannot be used interchangeably.\n\nDev 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.\n\nDev keys are for client SDKs in development environments, while API keys are for server SDKs and the CLI in production environments.\n\n# Create dev key {% #create-dev-key %}\n\nTo create a new dev key, navigate to **Overview** > **Integrations** > **Dev keys** and click **Create Dev key**.\n\n{% only_dark %}\n![Create dev key](/images/docs/dev-keys/dark.avif)\n{% /only_dark %}\n{% only_light %}\n![Create dev key](/images/docs/dev-keys/light.avif)\n{% /only_light %}\n\nYou can then implement the dev key while initializing the Appwrite client in your app.\n\n{% multicode %}\n```client-web\nimport { Client } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setDevKey(''); // Your dev key\n```\n\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nClient client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setDevKey(''); // Your dev key\n```\n\n```client-react-native\nimport { Client } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setDevKey(''); // Your dev key\n```\n\n```client-apple\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setDevKey(\"\") // Your dev key\n```\n\n```client-android-kotlin\nimport io.appwrite.Client\n\nval client = Client(context)\n .setEndpoint(\"https://cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setDevKey(\"\") // Your dev key\n```\n\n```client-android-java\nimport io.appwrite.Client;\n\nClient client = new Client(context)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setDevKey(\"\"); // Your dev key\n```\n{% /multicode %}\n\nIf you need to replace your dev key, create a new key, update your app credentials and, once ready, delete your old key."}, {"path": "docs/advanced/security/encryption", "title": "Encryption", "description": "Learn about Appwrite's use of encryption across Appwrite's databases and storage buckets to protect user data.", "content": "Other than applying encryption in [authentication](/docs/products/auth/security), [enforcing HTTPS](/docs/advanced/security/https),\nand [generating TLS certificate for domains](/docs/advanced/security/tls), Appwrite also uses encryption for Storage, and Databases to come.\nEncryption helps secure your files and data in storage. In the event that an attack happens and a malicious actor gains access to files\nor data, encrypted files and data cannot be deciphered, adding a further layer of protection.\n\n## Storage {% #storage %}\n\nFor 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.\nYou can enable encryption by going to your bucket's **Settings** > **Security settings** > toggle **Encryption**.\nFiles are encrypted with AES-128 in Galois/Counter Mode (GCM).\n\n## Databases {% #databases %}\nDatabase 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.\nColumns are encrypted with AES-128 in Galois/Counter Mode (GCM).\n\n{% info title=\"Querying encrypted columns\" %}\nNote that encrypted columns cannot be queried.\n{% /info %}"}, {"path": "docs/advanced/security/environment-variables", "title": "Environment variables", "description": "Use project, function, and site environment variables to pass constants and secrets to your Appwrite Functions and Appwrite Sites at build and runtime.", "content": "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.\n\nAppwrite supports three scopes of environment variables:\n\n- **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.\n- **Function variables** are scoped to a single function. Use them for values only that function needs.\n- **Site variables** are scoped to a single site. Use them for values only that site needs.\n\nWhen 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.\n\n{% info title=\"Redeployment required\" %}\nVariable changes only take effect on the next deployment. Redeploy your functions or sites after creating, updating, or deleting variables.\n{% /info %}\n\n# Project variables {% #project-variables %}\n\nProject 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.\n\nThis page covers how to manage project variables. To manage variables on a single function or site, see the dedicated pages:\n\n{% cards %}\n{% cards_item href=\"/docs/products/functions/environment-variables\" title=\"Function variables\" %}\nManage variables for a single function.\n{% /cards_item %}\n{% cards_item href=\"/docs/products/sites/environment-variables\" title=\"Site variables\" %}\nManage variables for a single site.\n{% /cards_item %}\n{% /cards %}\n\n# Manage in the Console {% #console %}\n\nYou can create and manage project variables from the Appwrite Console. The Console refers to them as **Global variables**:\n\n1. Navigate to your project.\n2. Open **Settings** and scroll to the **Global variables** section.\n3. Click **Create a global variable** and enter a key and value.\n4. Optionally select the **Secret** checkbox to prevent any team member from reading the value after creation.\n5. Click **Create**, then redeploy your functions and sites for the change to take effect.\n\n{% only_dark %}\n![Project Global variables in the Appwrite Console](/images/docs/platform/dark/env-variables.avif)\n{% /only_dark %}\n{% only_light %}\n![Project Global variables in the Appwrite Console](/images/docs/platform/env-variables.avif)\n{% /only_light %}\n\n# Manage with a Server SDK {% #server-sdks %}\n\nYou can also manage project variables programmatically using a [Server SDK](/docs/sdks#server). Each call requires an [API key](/docs/advanced/security/api-keys) with the `project.write` scope to create, update, or delete variables, or the `project.read` scope to list and read them.\n\n## Create a variable {% #create-variable %}\n\n{% multicode %}\n```server-nodejs\nimport { Client, Project, ID } from 'node-appwrite';\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n .setKey(''); // Your API key\n\nconst project = new Project(client);\n\nconst result = await project.createVariable({\n variableId: ID.unique(),\n key: '',\n value: '',\n secret: false // optional\n});\n```\n```server-deno\nimport { Client, Project, ID } from \"npm:node-appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n .setKey(''); // Your API key\n\nconst project = new Project(client);\n\nconst result = await project.createVariable({\n variableId: ID.unique(),\n key: '',\n value: '',\n secret: false // optional\n});\n```\n```server-php\nsetEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n ->setProject('') // Your project ID\n ->setKey(''); // Your API key\n\n$project = new Project($client);\n\n$result = $project->createVariable(\n variableId: ID::unique(),\n key: '',\n value: '',\n secret: false // optional\n);\n```\n```server-python\nfrom appwrite.client import Client\nfrom appwrite.id import ID\nfrom appwrite.services.project import Project\nfrom appwrite.models import Variable\n\nclient = Client()\nclient.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint\nclient.set_project('') # Your project ID\nclient.set_key('') # Your API key\n\nproject = Project(client)\n\nresult: Variable = project.create_variable(\n variable_id = ID.unique(),\n key = '',\n value = '',\n secret = False # optional\n)\n\nprint(result.model_dump())\n```\n```server-ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('') # Your project ID\n .set_key('') # Your API key\n\nproject = Project.new(client)\n\nresult = project.create_variable(\n variable_id: ID.unique(),\n key: '',\n value: '',\n secret: false # optional\n)\n```\n```server-dotnet\nusing Appwrite;\nusing Appwrite.Models;\nusing Appwrite.Services;\n\nClient client = new Client()\n .SetEndPoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .SetProject(\"\") // Your project ID\n .SetKey(\"\"); // Your API key\n\nProject project = new Project(client);\n\nVariable result = await project.CreateVariable(\n variableId: ID.Unique(),\n key: \"\",\n value: \"\",\n secret: false // optional\n);\n```\n```server-dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\n\nClient client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n .setKey(''); // Your API key\n\nProject project = Project(client);\n\nVariable result = await project.createVariable(\n variableId: ID.unique(),\n key: '',\n value: '',\n secret: false, // (optional)\n);\n```\n```server-kotlin\nimport io.appwrite.Client\nimport io.appwrite.ID\nimport io.appwrite.coroutines.CoroutineCallback\nimport io.appwrite.services.Project\n\nval client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n .setKey(\"\") // Your API key\n\nval project = Project(client)\n\nval response = project.createVariable(\n variableId = ID.unique(),\n key = \"\",\n value = \"\",\n secret = false // optional\n)\n```\n```server-java\nimport io.appwrite.Client;\nimport io.appwrite.ID;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Project;\n\nClient client = new Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n .setKey(\"\"); // Your API key\n\nProject project = new Project(client);\n\nproject.createVariable(\n ID.unique(), // variableId\n \"\", // key\n \"\", // value\n false, // secret (optional)\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(result);\n })\n);\n```\n```server-swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n .setKey(\"\") // Your API key\n\nlet project = Project(client)\n\nlet variable = try await project.createVariable(\n variableId: ID.unique(),\n key: \"\",\n value: \"\",\n secret: false // optional\n)\n```\n```server-go\npackage main\n\nimport (\n \"fmt\"\n \"github.com/appwrite/sdk-for-go/appwrite\"\n \"github.com/appwrite/sdk-for-go/id\"\n)\n\nclient := appwrite.NewClient(\n appwrite.WithEndpoint(\"https://.cloud.appwrite.io/v1\"),\n appwrite.WithProject(\"\"),\n appwrite.WithKey(\"\"),\n)\n\nproject := appwrite.NewProject(client)\n\nresponse, error := project.CreateVariable(\n id.Unique(),\n \"\",\n \"\",\n appwrite.WithCreateVariableSecret(false),\n)\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::id::ID;\nuse appwrite::services::Project;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box> {\n let client = Client::new();\n client.set_endpoint(\"https://.cloud.appwrite.io/v1\"); // Your API Endpoint\n client.set_project(\"\"); // Your project ID\n client.set_key(\"\"); // Your API key\n\n let project = Project::new(&client);\n\n let result = project.create_variable(\n &ID::unique(),\n \"\",\n \"\",\n Some(false) // optional\n ).await?;\n\n let _ = result;\n\n Ok(())\n}\n```\n{% /multicode %}\n\n## List variables {% #list-variables %}\n\nYou 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.\n\n{% multicode %}\n```server-nodejs\nimport { Client, Project } from 'node-appwrite';\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n .setKey(''); // Your API key\n\nconst project = new Project(client);\n\nconst result = await project.listVariables({\n queries: [], // optional\n total: false // optional\n});\n```\n```server-deno\nimport { Client, Project } from \"npm:node-appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n .setKey(''); // Your API key\n\nconst project = new Project(client);\n\nconst result = await project.listVariables({\n queries: [], // optional\n total: false // optional\n});\n```\n```server-php\nsetEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n ->setProject('') // Your project ID\n ->setKey(''); // Your API key\n\n$project = new Project($client);\n\n$result = $project->listVariables(\n queries: [], // optional\n total: false // optional\n);\n```\n```server-python\nfrom appwrite.client import Client\nfrom appwrite.services.project import Project\nfrom appwrite.models import VariableList\n\nclient = Client()\nclient.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint\nclient.set_project('') # Your project ID\nclient.set_key('') # Your API key\n\nproject = Project(client)\n\nresult: VariableList = project.list_variables(\n queries = [], # optional\n total = False # optional\n)\n\nprint(result.model_dump())\n```\n```server-ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('') # Your project ID\n .set_key('') # Your API key\n\nproject = Project.new(client)\n\nresult = project.list_variables(\n queries: [], # optional\n total: false # optional\n)\n```\n```server-dotnet\nusing Appwrite;\nusing Appwrite.Models;\nusing Appwrite.Services;\n\nClient client = new Client()\n .SetEndPoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .SetProject(\"\") // Your project ID\n .SetKey(\"\"); // Your API key\n\nProject project = new Project(client);\n\nVariableList result = await project.ListVariables(\n queries: new List(), // optional\n total: false // optional\n);\n```\n```server-dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\n\nClient client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n .setKey(''); // Your API key\n\nProject project = Project(client);\n\nVariableList result = await project.listVariables(\n queries: [], // (optional)\n total: false, // (optional)\n);\n```\n```server-kotlin\nimport io.appwrite.Client\nimport io.appwrite.coroutines.CoroutineCallback\nimport io.appwrite.services.Project\n\nval client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n .setKey(\"\") // Your API key\n\nval project = Project(client)\n\nval response = project.listVariables(\n queries = listOf(), // optional\n total = false // optional\n)\n```\n```server-java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Project;\n\nClient client = new Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n .setKey(\"\"); // Your API key\n\nProject project = new Project(client);\n\nproject.listVariables(\n List.of(), // queries (optional)\n false, // total (optional)\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(result);\n })\n);\n```\n```server-swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n .setKey(\"\") // Your API key\n\nlet project = Project(client)\n\nlet variableList = try await project.listVariables(\n queries: [], // optional\n total: false // optional\n)\n```\n```server-go\npackage main\n\nimport (\n \"fmt\"\n \"github.com/appwrite/sdk-for-go/appwrite\"\n)\n\nclient := appwrite.NewClient(\n appwrite.WithEndpoint(\"https://.cloud.appwrite.io/v1\"),\n appwrite.WithProject(\"\"),\n appwrite.WithKey(\"\"),\n)\n\nproject := appwrite.NewProject(client)\n\nresponse, error := project.ListVariables(\n appwrite.WithListVariablesQueries([]interface{}{}),\n appwrite.WithListVariablesTotal(false),\n)\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::Project;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box> {\n let client = Client::new();\n client.set_endpoint(\"https://.cloud.appwrite.io/v1\"); // Your API Endpoint\n client.set_project(\"\"); // Your project ID\n client.set_key(\"\"); // Your API key\n\n let project = Project::new(&client);\n\n let result = project.list_variables(\n Some(vec![]), // optional\n Some(false) // optional\n ).await?;\n\n let _ = result;\n\n Ok(())\n}\n```\n{% /multicode %}\n\n## Get a variable {% #get-variable %}\n\n{% multicode %}\n```server-nodejs\nimport { Client, Project } from 'node-appwrite';\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n .setKey(''); // Your API key\n\nconst project = new Project(client);\n\nconst result = await project.getVariable({\n variableId: ''\n});\n```\n```server-deno\nimport { Client, Project } from \"npm:node-appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n .setKey(''); // Your API key\n\nconst project = new Project(client);\n\nconst result = await project.getVariable({\n variableId: ''\n});\n```\n```server-php\nsetEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n ->setProject('') // Your project ID\n ->setKey(''); // Your API key\n\n$project = new Project($client);\n\n$result = $project->getVariable(\n variableId: ''\n);\n```\n```server-python\nfrom appwrite.client import Client\nfrom appwrite.services.project import Project\nfrom appwrite.models import Variable\n\nclient = Client()\nclient.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint\nclient.set_project('') # Your project ID\nclient.set_key('') # Your API key\n\nproject = Project(client)\n\nresult: Variable = project.get_variable(\n variable_id = ''\n)\n\nprint(result.model_dump())\n```\n```server-ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('') # Your project ID\n .set_key('') # Your API key\n\nproject = Project.new(client)\n\nresult = project.get_variable(\n variable_id: ''\n)\n```\n```server-dotnet\nusing Appwrite;\nusing Appwrite.Models;\nusing Appwrite.Services;\n\nClient client = new Client()\n .SetEndPoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .SetProject(\"\") // Your project ID\n .SetKey(\"\"); // Your API key\n\nProject project = new Project(client);\n\nVariable result = await project.GetVariable(\n variableId: \"\"\n);\n```\n```server-dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\n\nClient client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n .setKey(''); // Your API key\n\nProject project = Project(client);\n\nVariable result = await project.getVariable(\n variableId: '',\n);\n```\n```server-kotlin\nimport io.appwrite.Client\nimport io.appwrite.coroutines.CoroutineCallback\nimport io.appwrite.services.Project\n\nval client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n .setKey(\"\") // Your API key\n\nval project = Project(client)\n\nval response = project.getVariable(\n variableId = \"\"\n)\n```\n```server-java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Project;\n\nClient client = new Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n .setKey(\"\"); // Your API key\n\nProject project = new Project(client);\n\nproject.getVariable(\n \"\", // variableId\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(result);\n })\n);\n```\n```server-swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n .setKey(\"\") // Your API key\n\nlet project = Project(client)\n\nlet variable = try await project.getVariable(\n variableId: \"\"\n)\n```\n```server-go\npackage main\n\nimport (\n \"fmt\"\n \"github.com/appwrite/sdk-for-go/appwrite\"\n)\n\nclient := appwrite.NewClient(\n appwrite.WithEndpoint(\"https://.cloud.appwrite.io/v1\"),\n appwrite.WithProject(\"\"),\n appwrite.WithKey(\"\"),\n)\n\nproject := appwrite.NewProject(client)\n\nresponse, error := project.GetVariable(\n \"\",\n)\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::Project;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box> {\n let client = Client::new();\n client.set_endpoint(\"https://.cloud.appwrite.io/v1\"); // Your API Endpoint\n client.set_project(\"\"); // Your project ID\n client.set_key(\"\"); // Your API key\n\n let project = Project::new(&client);\n\n let result = project.get_variable(\n \"\"\n ).await?;\n\n let _ = result;\n\n Ok(())\n}\n```\n{% /multicode %}\n\n## Update a variable {% #update-variable %}\n\nYou 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.\n\n{% multicode %}\n```server-nodejs\nimport { Client, Project } from 'node-appwrite';\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n .setKey(''); // Your API key\n\nconst project = new Project(client);\n\nconst result = await project.updateVariable({\n variableId: '',\n key: '', // optional\n value: '', // optional\n secret: false // optional\n});\n```\n```server-deno\nimport { Client, Project } from \"npm:node-appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n .setKey(''); // Your API key\n\nconst project = new Project(client);\n\nconst result = await project.updateVariable({\n variableId: '',\n key: '', // optional\n value: '', // optional\n secret: false // optional\n});\n```\n```server-php\nsetEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n ->setProject('') // Your project ID\n ->setKey(''); // Your API key\n\n$project = new Project($client);\n\n$result = $project->updateVariable(\n variableId: '',\n key: '', // optional\n value: '', // optional\n secret: false // optional\n);\n```\n```server-python\nfrom appwrite.client import Client\nfrom appwrite.services.project import Project\nfrom appwrite.models import Variable\n\nclient = Client()\nclient.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint\nclient.set_project('') # Your project ID\nclient.set_key('') # Your API key\n\nproject = Project(client)\n\nresult: Variable = project.update_variable(\n variable_id = '',\n key = '', # optional\n value = '', # optional\n secret = False # optional\n)\n\nprint(result.model_dump())\n```\n```server-ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('') # Your project ID\n .set_key('') # Your API key\n\nproject = Project.new(client)\n\nresult = project.update_variable(\n variable_id: '',\n key: '', # optional\n value: '', # optional\n secret: false # optional\n)\n```\n```server-dotnet\nusing Appwrite;\nusing Appwrite.Models;\nusing Appwrite.Services;\n\nClient client = new Client()\n .SetEndPoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .SetProject(\"\") // Your project ID\n .SetKey(\"\"); // Your API key\n\nProject project = new Project(client);\n\nVariable result = await project.UpdateVariable(\n variableId: \"\",\n key: \"\", // optional\n value: \"\", // optional\n secret: false // optional\n);\n```\n```server-dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\n\nClient client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n .setKey(''); // Your API key\n\nProject project = Project(client);\n\nVariable result = await project.updateVariable(\n variableId: '',\n key: '', // (optional)\n value: '', // (optional)\n secret: false, // (optional)\n);\n```\n```server-kotlin\nimport io.appwrite.Client\nimport io.appwrite.coroutines.CoroutineCallback\nimport io.appwrite.services.Project\n\nval client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n .setKey(\"\") // Your API key\n\nval project = Project(client)\n\nval response = project.updateVariable(\n variableId = \"\",\n key = \"\", // optional\n value = \"\", // optional\n secret = false // optional\n)\n```\n```server-java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Project;\n\nClient client = new Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n .setKey(\"\"); // Your API key\n\nProject project = new Project(client);\n\nproject.updateVariable(\n \"\", // variableId\n \"\", // key (optional)\n \"\", // value (optional)\n false, // secret (optional)\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(result);\n })\n);\n```\n```server-swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n .setKey(\"\") // Your API key\n\nlet project = Project(client)\n\nlet variable = try await project.updateVariable(\n variableId: \"\",\n key: \"\", // optional\n value: \"\", // optional\n secret: false // optional\n)\n```\n```server-go\npackage main\n\nimport (\n \"fmt\"\n \"github.com/appwrite/sdk-for-go/appwrite\"\n)\n\nclient := appwrite.NewClient(\n appwrite.WithEndpoint(\"https://.cloud.appwrite.io/v1\"),\n appwrite.WithProject(\"\"),\n appwrite.WithKey(\"\"),\n)\n\nproject := appwrite.NewProject(client)\n\nresponse, error := project.UpdateVariable(\n \"\",\n appwrite.WithUpdateVariableKey(\"\"),\n appwrite.WithUpdateVariableValue(\"\"),\n appwrite.WithUpdateVariableSecret(false),\n)\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::Project;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box> {\n let client = Client::new();\n client.set_endpoint(\"https://.cloud.appwrite.io/v1\"); // Your API Endpoint\n client.set_project(\"\"); // Your project ID\n client.set_key(\"\"); // Your API key\n\n let project = Project::new(&client);\n\n let result = project.update_variable(\n \"\",\n Some(\"\"), // optional\n Some(\"\"), // optional\n Some(false) // optional\n ).await?;\n\n let _ = result;\n\n Ok(())\n}\n```\n{% /multicode %}\n\n## Delete a variable {% #delete-variable %}\n\n{% multicode %}\n```server-nodejs\nimport { Client, Project } from 'node-appwrite';\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n .setKey(''); // Your API key\n\nconst project = new Project(client);\n\nconst result = await project.deleteVariable({\n variableId: ''\n});\n```\n```server-deno\nimport { Client, Project } from \"npm:node-appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n .setKey(''); // Your API key\n\nconst project = new Project(client);\n\nconst result = await project.deleteVariable({\n variableId: ''\n});\n```\n```server-php\nsetEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n ->setProject('') // Your project ID\n ->setKey(''); // Your API key\n\n$project = new Project($client);\n\n$result = $project->deleteVariable(\n variableId: ''\n);\n```\n```server-python\nfrom appwrite.client import Client\nfrom appwrite.services.project import Project\n\nclient = Client()\nclient.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint\nclient.set_project('') # Your project ID\nclient.set_key('') # Your API key\n\nproject = Project(client)\n\nresult = project.delete_variable(\n variable_id = ''\n)\n```\n```server-ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('') # Your project ID\n .set_key('') # Your API key\n\nproject = Project.new(client)\n\nresult = project.delete_variable(\n variable_id: ''\n)\n```\n```server-dotnet\nusing Appwrite;\nusing Appwrite.Models;\nusing Appwrite.Services;\n\nClient client = new Client()\n .SetEndPoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .SetProject(\"\") // Your project ID\n .SetKey(\"\"); // Your API key\n\nProject project = new Project(client);\n\nawait project.DeleteVariable(\n variableId: \"\"\n);\n```\n```server-dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\n\nClient client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n .setKey(''); // Your API key\n\nProject project = Project(client);\n\nawait project.deleteVariable(\n variableId: '',\n);\n```\n```server-kotlin\nimport io.appwrite.Client\nimport io.appwrite.coroutines.CoroutineCallback\nimport io.appwrite.services.Project\n\nval client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n .setKey(\"\") // Your API key\n\nval project = Project(client)\n\nval response = project.deleteVariable(\n variableId = \"\"\n)\n```\n```server-java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Project;\n\nClient client = new Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n .setKey(\"\"); // Your API key\n\nProject project = new Project(client);\n\nproject.deleteVariable(\n \"\", // variableId\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(result);\n })\n);\n```\n```server-swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n .setKey(\"\") // Your API key\n\nlet project = Project(client)\n\nlet result = try await project.deleteVariable(\n variableId: \"\"\n)\n```\n```server-go\npackage main\n\nimport (\n \"fmt\"\n \"github.com/appwrite/sdk-for-go/appwrite\"\n)\n\nclient := appwrite.NewClient(\n appwrite.WithEndpoint(\"https://.cloud.appwrite.io/v1\"),\n appwrite.WithProject(\"\"),\n appwrite.WithKey(\"\"),\n)\n\nproject := appwrite.NewProject(client)\n\nresponse, error := project.DeleteVariable(\n \"\",\n)\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::Project;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box> {\n let client = Client::new();\n client.set_endpoint(\"https://.cloud.appwrite.io/v1\"); // Your API Endpoint\n client.set_project(\"\"); // Your project ID\n client.set_key(\"\"); // Your API key\n\n let project = Project::new(&client);\n\n project.delete_variable(\n \"\"\n ).await?;\n\n Ok(())\n}\n```\n{% /multicode %}\n\n# Secret variables {% #secret-variables %}\n\nMark 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.\n\nYou 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.\n\n# Override behavior {% #overrides %}\n\nWhen the same key is defined in multiple scopes, more specific scopes take precedence:\n\n1. Project variables are merged into the runtime environment first.\n2. Function or site variables override matching keys from project scope.\n3. Appwrite-injected variables (those prefixed with `APPWRITE_`) override matching keys from both scopes and cannot be customized.\n\nThis 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.\n\n# Limits {% #limits %}\n\n| Field | Limit |\n|----------------|------------------------------------------------|\n| Variable ID | 36 characters, `a-z A-Z 0-9 . - _` |\n| Key | 255 characters |\n| Value | 8192 characters |"}, {"path": "docs/advanced/security/gdpr", "title": "GDPR", "description": "The safeguarding of your and your users' data is taken seriously at Appwrite. Learn about Appwrite's measures and compliance with the European General Data Protection Regulation (GDPR).", "content": "Appwrite is compliant with the European General Data Protection Regulation (GDPR).\nGDPR is an EU regulation that concerns data privacy and security in the European Union and the European Economic Area.\n\nBy attesting that Appwrite is GDPR compliant, we have done the following.\n- Appwrite users will retain access to their personal information including the right to correct and delete it.\n- 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”).\n- Appwrite will notify users promptly about policy changes and/or data breaches.\n\nYou can learn more in our [Privacy policy](https://appwrite.io/privacy) and [Cookie policy](https://appwrite.io/cookies).\nYou can also reach us at `privacy@appwrite.io` for more questions.\n\nAppwrite has also implemented the following security measures to achieve technical compliance.\n- Appwrite implements a multi-layered security approach, integrating centralized IAM (Identity and Access Management) to regulate access to production resources.\n- 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).\n- 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.\n- 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.\n\n# DPA {% #dpa %}\n\nA DPA, or Data Processing Agreement, is a contract between a data controller and data processor concerning the rights and obligations\nof both parties when processing personal data.\n\nThis agreement describes how Appwrite and sub-processors handle, secure, and transfer data, as well as outline rights and obligations of both Appwrite and\nyou or your company when personal data is processed.\n\nYou can find and sign a DPA in your organization's **Settings** > **Download DPA document**.\n{% only_dark %}\n![Project settings screen](/images/docs/advanced/security/dark/dpa.avif)\n{% /only_dark %}\n{% only_light %}\n![Project settings screen](/images/docs/advanced/security/dpa.avif)\n{% /only_light %}\n\nPlease 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."}, {"path": "docs/advanced/security/hipaa", "title": "HIPAA", "description": "Learn about Appwrite Cloud's measures to achieve HIPAA compliance.", "content": "Appwrite is compliant with HIPAA (Health Insurance Portability and Accountability Act) regulations.\nHIPAA is an important regulation that protects patients' health data from being disclosed without consent or knowledge.\n\nIf you're building apps that handle information that is considered [PHI (Personal Health Information)](https://privacyruleandresearch.nih.gov/pr_07.asp)\nfor an U.S. user base, data must be stored in a HIPAA-compliant environment.\n\nTo attain HIPAA compliance, we've taken extensive measures, ensuring that our practices align with the highest data protection standards.\nWe have implemented robust measures to safeguard personal information, updating our policies, procedures, and infrastructure to meet the strict requirements of HIPAA regulations.\n\n- A strict data backup schedule.\n- An extended business continuity plan.\n- Data retention rights for individuals as outlined in our [Privacy Policy](https://appwrite.io/privacy).\n- Intrusion detection and penetration testing.\n- Encryption of data transmitted between Appwrite and users using Transport Layer Security (TLS) and HTTP Strict Transport Security,\nensuring confidentiality both at rest and during transmission.\n- Access to environments containing customer data is strictly controlled,\nrequiring authentication and authorization through multi-factor authentication (MFA).\n\nAppwrite 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.\n\n## Appwrite as a Business Associate\n\nAppwrite 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.\n\nFor 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).\n\nFor 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.\n\n## Data retention\n\nAppwrite 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.\n\nIf you're handling PHI (Protected Health Information), you can implement custom data retention policies using Appwrite Functions or database triggers to meet HIPAA requirements.\n\n## Log access and retrieval\n\nAppwrite provides access to different types of logs depending on the context:\n\n- **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.\n\n- **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.\n\n- **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.\n\nPlease note that while Appwrite Cloud serves as a HIPAA-compliant platform to handle data,\nit is the responsibility of developers to ensure that their application is also compliant with HIPAA regulations."}, {"path": "docs/advanced/security/https", "title": "HTTPS", "description": "Learn how Appwrite Cloud enforces secure connections by enforcing HTTPS on all endpoints.", "content": "Appwrite Cloud serves all endpoints over an HTTPS connection by default. \nRequests made through an unsecure HTTP connection will be redirected to.\n\nRedirected requests will show a `301` response status.\n\n```http\nHTTP/1.1 301 Moved Permanently\nContent-Type: application/json\nLocation: https://.cloud.appwrite.io/v1/\n```\n\nAppwrite Cloud does not support HTTP, which is a common practice in modern development, because unencrypted\nHTTP traffic is dangerous and exposes sensitive user data to malicious attackers.\n\n# Strict-Transport-Security {% #strict-transport-security %}\nAppwrite uses the [Strict-Transport-Security header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security)\nto inform browsers that the website should only be accessed using HTTPS, further protecting against \nman-in-the-middle attacks such as protocol downgrade attacks and cookie hijacking. \nBy enforcing HTTPS, Appwrite Cloud's endpoint will always be served over a secure connection, which helps protect users' data and privacy.\n\n# Custom domains {% #custom-domains %}\nYou can add a [custom domain](/docs/products/network/custom-domains) to your Appwrite project so you can access Appwrite API endpoints\non your own domain. Appwrite will [generate TLS certificates](/docs/advanced/security/tls) for your domain and enforce HTTPS communication.\n\n# Function domains {% #function-domains %}\nAppwrite generates domains for Appwrite Functions so they can be executed through HTTPS requests.\nAppwrite also [generates TLS certificates](/docs/advanced/security/tls) for these domains to enforce HTTPS communication."}, {"path": "docs/advanced/security/mfa", "title": "Multi-factor Authentication", "description": "Appwrite helps you secure your developer accounts with MFA (multi-factor authentication).", "content": "Multi-factor authentication (MFA) adds multiple layers of authentication to your Appwrite account.\nWhen MFA is enabled, a malicious actor needs to compromise multiple authentication factors to gain unauthorized access.\nAppwrite currently supports MFA using TOTP (Time-based One-Time Password) with an authenticator app.\nMore factors of authentication will be added in the future.\n\n{% info title=\"Looking to add MFA to your app?\" %}\nThis page covers MFA for your Appwrite Console account.\nIf you're looking to add MFA to your app, follow the [Multi-factor authentication journey](/docs/products/auth/mfa).\n{% /info %}\n\n# Enable MFA {% #enable-mfa %}\nTo enable MFA on your Appwrite account, navigate to your Appwrite Console > your account menu on the top right > **Your account** > **Multi-factor authentication**.\n\n{% only_dark %}\n![Multi-factor authentication settings](/images/docs/advanced/security/dark/mfa.avif)\n{% /only_dark %}\n{% only_light %}\n![Multi-factor authentication settings](/images/docs/advanced/security/mfa.avif)\n{% /only_light %}\n\nToggle **Multi-factor authentication** to enable MFA for your account, then click **Add authentication factor**.\n\n{% only_dark %}\n![Multi-factor authentication modal](/images/docs/advanced/security/dark/mfa-modal.avif)\n{% /only_dark %}\n{% only_light %}\n![Multi-factor authentication modal](/images/docs/advanced/security/mfa-modal.avif)\n{% /only_light %}\n\nScan the QR code with your authenticator app, then enter the code from your authenticator app to verify the setup.\nMake 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."}, {"path": "docs/advanced/security/pci", "title": "PCI", "description": "Learn about Appwrite's measure to achieve PCI compliance when handling payments and transactions, ensuring secure and safe handling of payment information and personal data.", "content": "The Payment Card Industry Data Security Standard (PCI) is a standard that concerns the handling of credit card information, transactions, and payments.\nAppwrite uses [Stripe](https://stripe.com/en-se) to securely handle payments for Appwrite Pro and Scale plans. \nStripe is a [PCI Service Provider Level 1](https://www.visa.com/splisting/searchGrsp.do?companyNameCriteria=stripe) provider\nwith a strong [commitment to security and privacy](https://stripe.com/docs/security) that matches Appwrite's core values.\n\n{% info title=\"Handling payment information\" %}\nIf you're looking to add payment or subscription services to your apps built on Appwrite,\nwe recommend that you **do not store credit card** information directly in Appwrite.\n\nYou can consider using one of the [function templates](/docs/products/functions/quick-start)\nto use a third party service, such as Stripe to handle payment.\n{% /info %}"}, {"path": "docs/advanced/security/penetration-tests", "title": "Penetration tests", "description": "Learn about how Appwrite keeps your data safe by employing manual third-party penetration tests to discover vulnerabilities.", "content": "Appwrite undertakes regular penetration testing and vulnerability assessments conducted by third-party agencies to attest our security standing.\nThese penetration tests and vulnerability assessments are performed periodically. Penetration tests performed by a third-party\nhelps identify vulnerabilities and suggest action plans to constantly improve Appwrite's security.\n\nAppwrite has processes for external and internal information security risk management that seek to identify, \nassess and address risks using a risk treatment plan to implement recommendations and decisions. \nThe risk assessment methodologies utilized include pen-test practices.\n\nSee the \"Data Processing\" addendum of [Appwrite's DPA](/docs/advanced/security/gdpr#dpa) for further details."}, {"path": "docs/advanced/security/permissions", "title": "Permissions", "description": "Enhance data security and access control with Appwrite platform permissions. Learn how to set fine-grained permissions to protect user data and resources.", "content": "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.\n\nUsing 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.\n\nAs 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.\n\nAll 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.\n\nA 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.\n\n# Appwrite resource {% #appwrite-resource %}\n\nAn 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.\n\nUsing the Appwrite permissions mechanism, you can grant resource access to users, teams, and members with different roles.\n\n# Default values {% #default-values %}\n\nIf 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.\n\n# Server integration {% #server-integration %}\n\nServer integrations can be used for increased flexibility. When using a Server SDK in combination with the proper [API key scopes](/docs/advanced/security/api-keys#scopes), you can have any type of access to any of your project resources regardless of their permissions.\n\nUsing the server integration flexibility, you can change resource permissions, share resources between different users and teams, or edit and delete them without any limitations.\n\n# Permission types {% #permission-types %}\n\nIn Client and Server SDKs, you will find a **Permission** class with helper methods for each role described below:\n\n| Type | Description |\n| ---- | ----------- |\n| `Permission.read()` | Access to read a resource. |\n| `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. |\n| `Permission.update()` | Access to change a resource, but not remove or create new resources. Does not apply to functions. |\n| `Permission.delete()` | Access to remove a resource. Does not apply to functions. |\n| `Permission.write()` | Alias to grant create, update, and delete access for tables and buckets and update and delete access for rows and files. |\n\n# Permission roles {% #permission-roles %}\n\nIn Client and Server SDKs, you will find a **Role** class with helper methods for each role described below:\n\n| Type | Description |\n| ---- | ----------- |\n| `Role.any()` | Grants access to anyone. |\n| `Role.guests()` | Grants access to any guest user without a session. Authenticated users don't have access to this role. |\n| `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. |\n| `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. |\n| `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. |\n| `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. |\n| `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. |\n| `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). |\n\n# Examples {% #examples %}\n\nThe examples below will show you how you can use the different Appwrite permissions to manage access control to your project resources.\n\nThe 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).\n\n## Example 1 - Basic usage {% #example-1-basic-usage %}\n\nIn 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`.\n\n```client-web\nimport { Client, TablesDB, Permission, Role } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nconst tablesDB = new TablesDB(client);\n\nlet promise = tablesDB.createRow(\n '',\n '',\n {'actorName': 'Chris Evans', 'height': 183},\n [\n Permission.read(Role.any()), // Anyone can view this row\n Permission.update(Role.team(\"writers\")), // Writers can update this row\n Permission.update(Role.team(\"admin\")), // Admins can update this row\n Permission.delete(Role.user(\"5c1f88b42259e\")), // User 5c1f88b42259e can delete this row\n Permission.delete(Role.team(\"admin\")) // Admins can delete this row\n ]\n);\n\npromise.then(function (response) {\n console.log(response);\n}, function (error) {\n console.log(error);\n});\n```\n\n## Example 2 - Team roles {% #example-2-team-roles %}\n\nIn 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`.\n\n```client-web\nimport { Client, TablesDB, Permission, Role } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nconst tablesDB = new TablesDB(client);\n\nlet promise = tablesDB.createRow(\n '',\n '',\n {'actorName': 'Chris Evans', 'height': 183},\n [\n Permission.read(Role.team(\"5c1f88b87435e\")), // Only users of team 5c1f88b87435e can read the row\n Permission.update(Role.team(\"5c1f88b87435e\", \"owner\")), // Only users of team 5c1f88b87435e with the role owner can update the row\n Permission.delete(Role.team(\"5c1f88b87435e\", \"owner\")) // Only users of team 5c1f88b87435e with the role owner can delete the row\n ]\n);\n\npromise.then(function (response) {\n console.log(response);\n}, function (error) {\n console.log(error);\n});\n```\n\n## Example 3 - Private rows {% #example-3-private-rows %}\n\nA common use case is to allow users to create rows that are only accessible to them. Here's how this can be achieved:\n\n### Configure the table\nFirst, configure your table to:\n1. Enable **Row Security** in Table **Settings**\n2. Grant only **CREATE** permission to **all users** at the table level\n\n{% info title=\"Why this setup?\" %}\n- **Row Security** enables per-row permissions\n- Table-level **CREATE** permission allows users to create rows\n- Omitting **READ/UPDATE/DELETE** at table level prevents users from accessing all rows\n{% /info %}\n\n### Create a row for a user\nWhen creating rows in your application, set row-level permissions to restrict access to only the creator:\n\n{% multicode %}\n```client-web\nimport { Client, TablesDB, Permission, Role } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://cloud.appwrite.io/v1')\n .setProject('');\n\nconst tablesDB = new TablesDB(client);\n\nlet promise = tablesDB.createRow(\n '',\n '',\n { 'title': 'My Private Row' },\n [\n Permission.read(Role.user('')), // Only this user can read\n Permission.update(Role.user('')), // Only this user can update\n Permission.delete(Role.user('')) // Only this user can delete\n ]\n);\n\npromise.then(function (response) {\n console.log(response);\n}, function (error) {\n console.log(error);\n});\n```\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nvoid main() async {\n final client = Client()\n .setEndpoint('https://cloud.appwrite.io/v1')\n .setProject('');\n\n final tablesDB = TablesDB(client);\n\n try {\n final row = await tablesDB.createRow(\n databaseId: '',\n tableId: '',\n data: { 'title': 'My Private Row' },\n permissions: [\n Permission.read(Role.user('')), // Only this user can read\n Permission.update(Role.user('')), // Only this user can update\n Permission.delete(Role.user('')) // Only this user can delete\n ]\n );\n } on AppwriteException catch(e) {\n print(e);\n }\n}\n```\n```client-apple\nimport Appwrite\n\nfunc main() async throws {\n let client = Client()\n .setEndpoint(\"https://cloud.appwrite.io/v1\")\n .setProject(\"\");\n\n let tablesDB = TablesDB(client);\n\n do {\n let row = try await tablesDB.createRow(\n databaseId: \"\",\n tableId: \"\",\n data: [\"title\": \"My Private Row\"],\n permissions: [\n Permission.read(Role.user(\"\")), // Only this user can read\n Permission.update(Role.user(\"\")), // Only this user can update\n Permission.delete(Role.user(\"\")) // Only this user can delete\n ]\n );\n } catch {\n print(error.localizedDescription);\n }\n}\n```\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.Permission\nimport io.appwrite.Role\nimport io.appwrite.services.TablesDB\nimport io.appwrite.exceptions.AppwriteException\n\nsuspend fun main() {\n val client = Client(applicationContext)\n .setEndpoint(\"https://cloud.appwrite.io/v1\")\n .setProject(\"\");\n\n val tablesDB = TablesDB(client);\n\n try {\n val row = tablesDB.createRow(\n databaseId = \"\",\n tableId = \"\",\n data = mapOf(\"title\" to \"My Private Row\"),\n permissions = listOf(\n Permission.read(Role.user(\"\")), // Only this user can read\n Permission.update(Role.user(\"\")), // Only this user can update\n Permission.delete(Role.user(\"\")) // Only this user can delete\n )\n );\n } catch (e: AppwriteException) {\n Log.e(\"Appwrite\", e.message);\n }\n}\n```\n{% /multicode %}\n\n{% info title=\"Understanding the flow\" %}\n1. Table-level **CREATE** permission allows users to create new rows\n2. When a row is created, we set permissions for only the creator\n3. These row-level permissions ensure only the creator can read, update, or delete their rows\n4. Other users can create their own rows but cannot access rows they didn't create\n{% /info %}"}, {"path": "docs/advanced/security/rate-limits", "title": "Rate-limits", "description": "Optimize application performance with Appwrite rate limits. Explore rate limiting strategies, configurations, and how to prevent abuse of your services.", "content": "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.\n\nRate limits only apply to Client SDKs. Rate limits do not apply when accessing Appwrite with a Server SDK authenticated using an API key.\n\n# Headers {% #headers %}\n\nYou can check the returned HTTP headers of any API request to see your current rate limit status:\n\n```http\nHTTP/1.1 200\nDate: Mon, 01 Jul 2013 17:27:06 GMT\nStatus: 200\nX-RateLimit-Limit: 60\nX-RateLimit-Remaining: 56\nX-RateLimit-Reset: 1372700873\n```\n\nThe headers tell you everything you need to know about your current rate limit status:\n\n| Header | Description |\n| -------------------- | ------------------------------------------------------------------------------ |\n| X-RateLimit-Limit | The maximum number of requests that the consumer is permitted to make per hour. |\n| X-RateLimit-Remaining| The number of requests remaining in the current rate limit window. |\n| X-RateLimit-Reset | The time at which the current rate limit window resets in UTC epoch seconds. |\n\nIf 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).\n\n```js\nnew Date(1372700873 * 1000) // => Mon Jul 01 2013 13:47:53 GMT-0400 (EDT)\n```\n\nOnce you go over the rate limit you will receive an error response:\n\n```http\nHTTP/1.1 429\nDate: Tue, 20 Aug 2013 14:50:41 GMT\nStatus: 429\nX-RateLimit-Limit: 60\nX-RateLimit-Remaining: 0\nX-RateLimit-Reset: 1377013266\n{\n \"message\": \"Too many requests\",\n \"code\": 429\n}\n```\n\n# Dev keys {% #dev-keys %}\n\nRate 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.\n\nTo 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.\n\n```http\nX-Appwrite-Dev-Key: 5b0be23...abda7c6\n```\n\nDev 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.\n\n# Service abuse {% #service-abuse %}\n\nTo 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.\n\nIt 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.\n\nIf you are exceeding your rate limit, you can likely fix the issue by caching API responses and using webhooks for data polling.\n\nIf your application triggers this rate limit, you'll receive an informative response:\n\n```http\nHTTP/1.1 429\nContent-Type: application/json; charset=utf-8\nConnection: close\n{\n \"message\": \"Too many login attempts\",\n \"code\": 429\n}\n```"}, {"path": "docs/advanced/security/roles", "title": "Roles", "description": "Learn how to setup role-based access controls in the Appwrite Console", "content": "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.\n\n{% info title=\"Note\" %}\nThis 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).\n{% /info %}\n\n## Owner {% #owner %}\nThe 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.\n\n## Developer {% #developer %}\nDevelopers 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.\n\n## Editor {% #editor %}\nEditors 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.\n\n## Analyst {% #analyst %}\nAnalysts 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.\n\n## Billing {% #billing %}\nBilling 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.\n\n## Custom roles {% #custom-roles %}\nCustom roles will soon be available on the Appwrite Console. Custom roles will be a Scale and Enterprise plans feature."}, {"path": "docs/advanced/security/soc2", "title": "SOC 2", "description": "Learn about Appwrite Cloud's measures to achieve SOC 2 compliance.", "content": "SOC 2 refers to the Service Organization Control 2 standards.\nSOC 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. \n\nSOC 2 is a set of standards defined by the American Institute of CPAs (AICPA) that assess organizations on the criteria of security, \navailability, processing integrity, confidentiality, and privacy. While SOC 2 compliance is voluntary, Appwrite is committed to safeguard the\ndata of developers and their users and has implemented measures to achieve SOC 2 compliance.\n\nAppwrite's service commitments and system requirements were achieved based on the Trust Services Criteria relevant to security set forth in TSP Section 100, 2017. \nOutlined below are some of the key measures Appwrite implements to achieve SOC 2 compliance:\n\n- Appwrite commits to maintain system availability for access and utilization at a minimum of 99.99%, with exceptions made only for scheduled maintenance.\n- Any modifications to the IT environment are thoroughly documented, tested, and approved before implementation.\n- 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.\n- Access control mechanisms and privilege management protocols ensure that only authorized personnel have access to systems, data, and resources. \n- Sensitive data is safeguarded through encryption protocols, both during transit and while at rest, enhancing overall data security.\n- Incident Response plans are in place to swiftly detect, address, and recover from any security breaches.\n- Appwrite oversees vendor management processes to ensure the security of third-party vendors and service providers who may access systems or data.\n\nAppwrite is committed to maintaining the highest standards of data security and privacy. \nBy implementing these measures, Appwrite ensures that developers and their users's data is protected and secure."}, {"path": "docs/advanced/security/tls", "title": "TLS", "description": "Appwrite helps keep the web secure by generating TLS (Transport Layer Security) certificates for all user and generated domains.", "content": "Appwrite generates TLS certificates to ensure your API traffic is appropriately encrypted. The certificate authority used depends on your deployment type:\n\n- **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.\n- **Appwrite Cloud** uses [Certainly](https://docs.fastly.com/products/certainly), Fastly's certificate authority, for Sites and Functions.\n\nTLS certificates are generated for all of the following.\n- Appwrite products and endpoints, like Databases, Storage, Authentication, Functions, Messaging, and all other endpoints.\n- [Custom domains](/docs/products/network/custom-domains) that you configure for your Appwrite projects.\n- [Domains for Appwrite Functions](/docs/products/functions/domains), generated or user provided.\n- [Domains for Appwrite Sites](/docs/products/sites/domains), generated or user provided.\n\nTLS certificates are crucial to ensure all connections between your apps and Appwrite Cloud are encrypted.\nThis protects your users from attack vectors like man-in-the-middle and eavesdropping attacks.\n\n# CAA records {% #caa-records %}\n\nIf 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.\n\n[Learn more about CAA records >](/docs/products/network/caa-records)"}, {"path": "docs/advanced/self-hosting", "title": "Self-hosting", "description": "Set up your self-hosted Appwrite instance easily. Read the installation guide to configure and deploy Appwrite on your infrastructure for complete control.", "content": "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.\n\n{% info title=\"Upgrading from older versions\" %}\nIf you are migrating from an older version of Appwrite, you need to follow the [migration instructions](/docs/advanced/self-hosting/production/updates)\n{% /info %}\n\n# Cloud vs Self-hosting {% #cloud-vs-self-hosting %}\n\nChoose the deployment method that fits your needs.\n\n| Feature | Appwrite Cloud | Self-hosting |\n|---------|---------------|--------------|\n| Setup | Zero setup | Manual setup required |\n| Maintenance | Fully managed | You manage updates and scaling |\n| Data control | Managed infrastructure | Full control over data location |\n| Compliance | Built-in compliance | Configure for your requirements |\n| Scaling | Automatic | Manual configuration |\n\n# When to self-host {% #when-to-self-host %}\n\nSelf-hosting is ideal when you need data control or have specific compliance requirements.\n\n**Consider self-hosting if:**\n- You want to manage your own infrastructure\n- You're a hobbyist or want to experiment with Appwrite in a playground environment\n- You need to develop against a local instance of Appwrite\n\n**Appwrite Cloud is recommended if:**\n- You want to focus on building features, not managing infrastructure\n- Your team lacks extensive DevOps experience\n- You expect self-hosting to save costs (it often costs more when factoring in time and expertise)\n{% info title=\"Enterprise self-hosting\" %}\nFor compliance features and dedicated support, explore [enterprise self-hosting solutions](/contact-us/enterprise).\n{% /info %}\n\n# Quick start {% #quick-start %}\n\nThe fastest way to get started with Appwrite self-hosting:\n\n1. **Use a one-click deployment** - Choose from [marketplace installations](#one-click-deployments) for instant setup\n2. **Or follow the manual installation** - Use our [Docker installation guide](/docs/advanced/self-hosting/installation) for custom setups\n3. **Configure services** - Set up [email](/docs/advanced/self-hosting/configuration/email), [storage](/docs/advanced/self-hosting/configuration/storage), and other services\n\n# Deployment options {% #deployment-options %}\n\nChoose the deployment method that best fits your needs:\n\n## One-click deployments {% #one-click-deployments %}\n\n**Recommended:** Use these pre-configured marketplace apps for instant setup:\n\n{% table %}\n*   {% width=48 %}\n* Provider\n* Installation Link\n---\n* {% only_dark %}{% icon_image src=\"/images/one-click/dark/digitalocean.svg\" alt=\"DigitalOcean logo\" size=\"m\" /%}{% /only_dark %}\n{% only_light %}{% icon_image src=\"/images/one-click/digitalocean.svg\" alt=\"DigitalOcean logo\" size=\"m\" /%}{% /only_light %}\n* DigitalOcean\n* [Click to install](https://marketplace.digitalocean.com/apps/appwrite)\n---\n* {% only_dark %}{% icon_image src=\"/images/one-click/dark/gitpod.svg\" alt=\"Gitpod logo\" size=\"m\" /%}{% /only_dark %}\n{% only_light %}{% icon_image src=\"/images/one-click/gitpod.svg\" alt=\"Gitpod logo\" size=\"m\" /%}{% /only_light %}\n* Gitpod\n* [Click to install](https://gitpod.io/#https://github.com/appwrite/integration-for-gitpod)\n---\n* {% only_dark %}{% icon_image src=\"/images/one-click/dark/akamai.svg\" alt=\"Akamai logo\" size=\"m\" /%}{% /only_dark %}\n{% only_light %}{% icon_image src=\"/images/one-click/akamai.svg\" alt=\"Akamai logo\" size=\"m\" /%}{% /only_light %}\n* Akamai Compute\n* [Click to install](https://www.linode.com/marketplace/apps/appwrite/appwrite/)\n---\n* {% only_dark %}{% icon_image src=\"/images/one-click/dark/aws.svg\" alt=\"AWS logo\" size=\"m\" /%}{% /only_dark %}\n{% only_light %}{% icon_image src=\"/images/one-click/aws.svg\" alt=\"AWS logo\" size=\"m\" /%}{% /only_light %}\n* AWS Marketplace\n* [Click to install](https://aws.amazon.com/marketplace/pp/prodview-2hiaeo2px4md6)\n{% /table %}\n\n## Cloud platforms {% #cloud-platforms %}\n\nFor custom deployments on major cloud providers:\n\n{% cards %}\n{% cards_item href=\"/docs/advanced/self-hosting/platforms/aws\" title=\"Amazon Web Services\" %}\nOne-click AWS Marketplace deployment with custom configuration options.\n{% /cards_item %}\n\n{% cards_item href=\"/docs/advanced/self-hosting/platforms/digitalocean\" title=\"DigitalOcean\" %}\nMarketplace installation with simple Droplet configuration.\n{% /cards_item %}\n\n{% cards_item href=\"/docs/advanced/self-hosting/platforms/google-cloud\" title=\"Google Cloud\" %}\nDeploy using Cloud Run, Compute Engine, or other Google Cloud services.\n{% /cards_item %}\n\n{% cards_item href=\"/docs/advanced/self-hosting/platforms/azure\" title=\"Microsoft Azure\" %}\nDeploy using Container Apps, Virtual Machines, or other Azure services.\n{% /cards_item %}\n{% /cards %}\n\n## Platform-as-a-Service (PaaS) {% #paas-platforms %}\n\nDeploy Appwrite on modern PaaS platforms for simplified management:\n\n{% cards %}\n{% cards_item href=\"/docs/advanced/self-hosting/platforms/coolify\" title=\"Coolify\" %}\nOpen-source platform for easy self-hosting with one-click deployments.\n{% /cards_item %}\n{% /cards %}\n\n# Configuration {% #configuration %}\n\nAfter deployment, configure Appwrite to enable additional features:\n\n{% cards %}\n{% cards_item href=\"/docs/advanced/self-hosting/configuration/email\" title=\"Email delivery\" %}\nSet up SMTP providers for user verification, password recovery, and notifications.\n{% /cards_item %}\n\n{% cards_item href=\"/docs/advanced/self-hosting/configuration/sms\" title=\"SMS Delivery\" %}\nConfigure SMS providers for phone authentication and two-factor authentication.\n{% /cards_item %}\n\n{% cards_item href=\"/docs/advanced/self-hosting/configuration/storage\" title=\"Storage Backends\" %}\nConnect external storage providers like AWS S3, Backblaze, or Wasabi.\n{% /cards_item %}\n\n{% cards_item href=\"/docs/advanced/self-hosting/configuration/functions\" title=\"Functions Runtime\" %}\nEnable serverless functions with custom runtimes and execution environments.\n{% /cards_item %}\n{% /cards %}\n\n# Production readiness {% #production-readiness %}\n\nEnsure your Appwrite deployment is production-ready:\n\n## Security considerations {% #security %}\n\n- **Environment Variables** - Secure sensitive configuration using environment variables\n- **TLS Certificates** - Enable HTTPS with automated certificate management\n- **Network Security** - Configure firewalls and security groups appropriately\n- **Access Control** - Implement proper authentication and authorization\n\n## Performance optimization {% #performance %}\n\n- **Resource Scaling** - Monitor and scale CPU, memory, and storage resources\n- **Database Performance** - Optimize database connections and queries\n- **CDN Integration** - Use content delivery networks for static assets\n- **Load Balancing** - Distribute traffic across multiple instances\n\n## Monitoring and maintenance {% #monitoring %}\n\n- **Health Checks** - Set up monitoring for service availability and performance\n- **Log Management** - Centralize and analyze application logs\n- **Backup Strategy** - Implement regular database and file backups\n- **Update Management** - Keep Appwrite updated with the latest releases\n\n# Get started {% #get-started %}\n\nReady to self-host Appwrite? Choose your preferred path:\n\n**One-Click Deploy** - Use a [marketplace installation](#one-click-deployments) for instant setup (recommended)\n\n**Manual Installation** - Follow our [Docker installation guide](/docs/advanced/self-hosting/installation) for custom setups\n\n**Cloud Platform** - [Choose a cloud platform](#cloud-platforms) for production hosting with custom configuration\n\n**Platform-as-a-Service** - Deploy on [Coolify](/docs/advanced/self-hosting/platforms/coolify) or similar platforms for simplified management"}, {"path": "docs/advanced/self-hosting/configuration/databases", "title": "Databases", "description": "Configure the database backend for your self-hosted Appwrite instance. Learn about the supported database options and their configuration.", "content": "Appwrite supports MongoDB and MariaDB as database backends. The database is selected during [installation](/docs/advanced/self-hosting/installation) via the setup wizard and **cannot be changed after installation**.\n\nRegardless of which database you choose, the Appwrite API remains the same. Only the underlying storage engine differs.\n\n{% info title=\"Default database\" %}\nMongoDB is the default database as of Appwrite 1.9.0.\n{% /info %}\n\n# CLI installation {% #cli-installation %}\n\nIf you prefer to skip the setup wizard, you can set the database directly using the `--database` flag:\n\n{% tabs %}\n{% tabsitem #unix title=\"macOS and Linux\" %}\n```bash\ndocker run -it --rm \\\n --volume /var/run/docker.sock:/var/run/docker.sock \\\n --volume \"$(pwd)\"/appwrite:/usr/src/code/appwrite:rw \\\n --entrypoint=\"install\" \\\n appwrite/appwrite:1.9.0 \\\n --database=mongodb\n```\n{% /tabsitem %}\n\n{% tabsitem #cmd title=\"Windows (CMD)\" %}\n```cmd\ndocker run -it --rm ^\n --volume //var/run/docker.sock:/var/run/docker.sock ^\n --volume \"%cd%\"/appwrite:/usr/src/code/appwrite:rw ^\n --entrypoint=\"install\" ^\n appwrite/appwrite:1.9.0 ^\n --database=mongodb\n```\n{% /tabsitem %}\n\n{% tabsitem #powershell title=\"Windows (PowerShell)\" %}\n```powershell\ndocker run -it --rm `\n --volume /var/run/docker.sock:/var/run/docker.sock `\n --volume ${pwd}/appwrite:/usr/src/code/appwrite:rw `\n --entrypoint=\"install\" `\n appwrite/appwrite:1.9.0 `\n --database=mongodb\n```\n{% /tabsitem %}\n{% /tabs %}\n\nAccepted values are `mongodb` and `mariadb`.\n\n# Supported databases {% #supported-databases %}\n\n## MongoDB {% #mongodb %}\n\nMongoDB is a document-based database and the default option for new Appwrite installations. It is configured as a replica set (`rs0`) automatically during installation.\n\n- **Docker image:** `mongo:8.2.5`\n- **Container name:** `appwrite-mongodb`\n- **Default port:** `27017`\n\n## MariaDB {% #mariadb %}\n\nMariaDB is a relational SQL database. It was the default database in Appwrite versions prior to 1.9.0.\n\n- **Docker image:** `mariadb:10.11`\n- **Container name:** `appwrite-mariadb`\n- **Default port:** `3306`\n\n# Environment variables {% #environment-variables %}\n\nBoth database backends use the same environment variables. The values differ depending on which database was selected during installation.\n\n| Variable | Description | MongoDB default | MariaDB default |\n|---|---|---|---|\n| `_APP_DB_ADAPTER` | Database adapter type | `mongodb` | `mariadb` |\n| `_APP_DB_HOST` | Database server hostname | `mongodb` | `mariadb` |\n| `_APP_DB_PORT` | Database server port | `27017` | `3306` |\n| `_APP_DB_SCHEMA` | Database name | `appwrite` | `appwrite` |\n| `_APP_DB_USER` | Database user | `user` | `user` |\n| `_APP_DB_PASS` | Database user password | `password` | `password` |\n| `_APP_DB_ROOT_PASS` | Database root password | `rootsecretpassword` | `rootsecretpassword` |\n\nThe Docker Compose profile controls which database container runs. This is set automatically by the installer:\n\n- `COMPOSE_PROFILES=mongodb` for MongoDB installations\n- `COMPOSE_PROFILES=mariadb` for MariaDB installations\n\n{% info title=\"Change default passwords\" %}\nAlways change the default database passwords before deploying to production. Update both the environment variables and the database credentials.\n{% /info %}\n\n# Backups {% #backups %}\n\nFor database backup procedures, see the [Backups](/docs/advanced/self-hosting/production/backups) guide, which covers both MongoDB and MariaDB backup and restore commands."}, {"path": "docs/advanced/self-hosting/configuration/email", "title": "Email delivery", "description": "Configure email services for your self-hosted Appwrite instance. Learn how to set up email notifications, templates, and delivery for your applications.", "content": "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.\n\nBecause 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.\n\nIn 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.\n\n{% info title=\"Setting up Appwrite Messaging?\" %}\nThis page describes how to setup messaging for your self-hosted Appwrite instance to send email verifications and magic URLs during login.\n\nIf you are looking to send custom emails for promotions, news letters, and other purposes, view the [documentation for Appwrite Messaging](/docs/products/messaging) documentation.\n{% /info %}\n\n# Environment variables {% #environment-variables %}\n\nAt 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.\n\n| Environment Variable | Description | Default Value |\n| ------------------------------- | --------------------------------------------------------------------------------------------------------------------- | ------------------------ |\n| `_APP_SMTP_HOST` | SMTP server host name address. Use an empty string to disable all mail sending from the server. | Empty string |\n| `_APP_SMTP_PORT` | SMTP server TCP port. | Empty |\n| `_APP_SMTP_SECURE` | SMTP secure connection protocol. Change to 'tls' if running on a secure connection. Valid values: empty, 'tls', 'ssl'. | Empty |\n| `_APP_SMTP_USERNAME` | SMTP server user name. | Empty |\n| `_APP_SMTP_PASSWORD` | SMTP server user password. | Empty |\n| `_APP_SYSTEM_EMAIL_ADDRESS` | Configured sender email address, seen by recipients. | \"team@appwrite.io\" |\n\nHere's a sample configuration if you're using SendGrid as your SMTP provider:\n\n```sh\n_APP_SMTP_HOST=smtp.sendgrid.net\n_APP_SMTP_PORT=587\n_APP_SMTP_SECURE=tls\n_APP_SMTP_USERNAME=YOUR-SMTP-USERNAME\n_APP_SMTP_PASSWORD=YOUR-SMTP-PASSWORD\n_APP_SYSTEM_EMAIL_ADDRESS=YOUR-SENDER-EMAIL\n```\n\nWhen using SendGrid, the SMTP username should be set to the literal string \"apikey\".\n\n{% partial file=\"update-variables.md\" /%}\n\n# Debugging {% #debugging %}\n\nIf you are unable to send emails, there are several common issues to check. Follow these troubleshooting steps:\n\n## Check email worker logs {% #check-logs %}\n\nThe first place to look for errors is the **Appwrite Emails Worker** logs:\n\n```sh\ndocker compose logs -f appwrite-worker-mails\n```\n\nLook for error messages that might indicate authentication failures, network issues, or configuration problems.\n\n## Verify SMTP configuration {% #verify-config %}\n\nCheck your `.env` file configuration:\n\n1. Ensure all SMTP environment variables are correctly set\n2. Verify credentials are valid\n3. Test your SMTP credentials independently using your provider's SDK or cURL requests\n\n## Check authorized recipients {% #authorized-recipients %}\n\nSome SMTP providers have [authorized recipients](https://help.mailgun.com/hc/en-us/articles/217531258-Authorized-Recipients) restrictions in sandbox/development environments.\n\nMake sure the email recipient is added to your provider's authorized recipients list if you're using a sandbox account.\n\n## Verify environment variables are loaded {% #verify-env %}\n\nCheck if environment variables are properly set in the container:\n\n```sh\ndocker compose exec appwrite-worker-mails vars\n```\n\nIf environment variables aren't loaded, rebuild your Appwrite stack:\n\n```sh\ndocker compose up -d --build --force-recreate\n```\n\nNow 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."}, {"path": "docs/advanced/self-hosting/configuration/environment-variables", "title": "Environment variables", "description": "Customize the behavior of your self-hosted Appwrite instance to your unique needs. Customize SMTP, SMS, functions, S3 adaptor, database, and other behaiors.", "content": "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.\n\nUpdating 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.\n\nAfter 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:\n```bash\ndocker compose up -d\n```\n\nYou can verify if the changes have been successfully applied by running this command:\n```bash\ndocker compose exec appwrite vars\n```\n\n# General {% #general %}\n| Name | Description |\n|------|-------------|\n| `_APP_ENV` | Set your server running environment. By default, the var is set to 'development'. When deploying to production, change it to: 'production'. |\n| `_APP_LOCALE` | Set your Appwrite's locale. By default, the locale is set to 'en'. |\n| `_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. |\n| `_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. |\n| `_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. |\n| `_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. |\n| `_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**. |\n| `_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'. |\n| `_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'. |\n| `_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'. |\n| `_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'. |\n| `_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'. |\n| `_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. |\n| `_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. |\n| `_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. |\n| `_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. |\n| `_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. |\n| `_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. |\n| `_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. |\n| `_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. |\n| `_APP_LOGGING_PROVIDER` | Deprecated since 1.6.0, use `_APP_LOGGING_CONFIG` with DSN value instead. This variable allows you to enable logging errors to 3rd party providers. This value is empty by default, set the value to one of `sentry`, `raygun`, `appSignal`, `logOwl` to enable the logger. |\n| `_APP_LOGGING_CONFIG` | This variable allows you to enable logging errors to third party providers. This value is empty by default, set a DSN value to one of the following `sentry://PROJECT_ID:SENTRY_API_KEY@SENTRY_HOST/`, , `logowl://SERVICE_TICKET@SERIVCE_HOST/` `raygun://RAYGUN_API_KEY/`, `appSignal://API_KEY/` to enable the logger. For versions prior `1.5.6` you can use the old syntax. Old syntax: If using Sentry, this should be `SENTRY_API_KEY;SENTRY_APP_ID`. If using Raygun, this should be Raygun API key. If using AppSignal, this should be AppSignal API key. If using LogOwl, this should be LogOwl Service Ticket. |\n| `_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. |\n| `_APP_USAGE_TIMESERIES_INTERVAL` | **(version >= 1.0.0)** Deprecated since 1.1.0 uses `_APP_USAGE_AGGREGATION_INTERVAL` instead. |\n| `_APP_USAGE_DATABASE_INTERVAL` | **(version >= 1.0.0)** Deprecated since 1.1.0 uses `_APP_USAGE_AGGREGATION_INTERVAL` instead. |\n| `_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. |\n\n# Redis {% #redis %}\nAppwrite 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.\n\n| Name | Description |\n|--------------------------|-------------------------------------------------------------------------------------------------------|\n| `_APP_REDIS_HOST` | Redis server hostname address. Default value is: `redis`. |\n| `_APP_REDIS_PORT` | Redis server TCP port. Default value is: `6379`. |\n| `_APP_REDIS_USER` | Redis server user. This is an optional variable. Default value is an empty string. |\n| `_APP_REDIS_PASS` | Redis server password. This is an optional variable. Default value is an empty string.|\n\n# Database {% #database %}\nAppwrite 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.\n\n| Name | Description |\n|------------------------|--------------------------------------------------------------------------------------------------|\n| `_APP_DB_ADAPTER` | Database adapter type. Default value is: `mongodb`. Possible values: `mongodb`, `mariadb`. |\n| `_APP_DB_HOST` | Database server host name address. Default value is: `mongodb` (or `mariadb` for MariaDB). |\n| `_APP_DB_PORT` | Database server TCP port. Default value is: `27017` (or `3306` for MariaDB). |\n| `_APP_DB_SCHEMA` | Database name. Default value is: `appwrite`. |\n| `_APP_DB_USER` | Database server user name. Default value is: `user`. |\n| `_APP_DB_PASS` | Database server user password. Default value is: `password`. |\n| `_APP_DB_ROOT_PASS` | Database server root password. Default value is: `rootsecretpassword`. |\n\n# SMTP {% #smtp %}\n\nAppwrite 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.\n\nIf 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**.\n\n| Name | Description |\n|-----------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| `_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. |\n| `_APP_SMTP_PORT` | SMTP server TCP port. Empty by default. |\n| `_APP_SMTP_SECURE` | SMTP secure connection protocol. Empty by default, change to 'tls' if running on a secure connection. |\n| `_APP_SMTP_USERNAME` | SMTP server user name. Empty by default. |\n| `_APP_SMTP_PASSWORD` | SMTP server user password. Empty by default. |\n\n# Phone {% #phone %}\n\n| Name | Description |\n|---------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| `_APP_SMS_PROVIDER` | **version >= 0.15.0** Provider used for delivering SMS for Phone authentication. Use the following format: `sms://:@`. Ensure `` and `` are URL encoded if they contain any non-alphanumeric characters. Available providers are twilio, text-magic, telesign, msg91, and vonage. |\n| `_APP_SMS_FROM` | **version >= 0.15.0** Phone number used for sending out messages. Must start with a leading '+' and maximum of 15 digits without spaces (+123456789). |\n\n# Storage {% #storage %}\n\n| Name | Description |\n|---------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| `_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. |\n| `_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. |\n| `_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. |\n| `_APP_STORAGE_ANTIVIRUS_HOST` | **version >= 0.7.0** ClamAV server host name address. Default value is: 'clamav'. |\n| `_APP_STORAGE_ANTIVIRUS_PORT` | **version >= 0.7.0** ClamAV server TCP port. Default value is: '3310'. |\n| `_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'. |\n| `_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. |\n| `_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. |\n| `_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. |\n| `_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. |\n| `_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. |\n| `_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. |\n| `_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. |\n| `_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. |\n| `_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. |\n| `_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. |\n| `_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. |\n| `_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. |\n| `_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. |\n| `_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. |\n| `_APP_STORAGE_LINODE_SECRET` | **version >= 0.14.2** Linode object storage secret key. Required when the storage adapter is set to Linode\n\n# Compute (Functions and Sites) {% #compute %}\n| **Name** | **Description** |\n|-------------------------------|-----------------|\n| `_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. |\n| `_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. |\n| `_APP_COMPUTE_SIZE_LIMIT` | **version >= 1.7.0** The maximum size of a function and site deployments in bytes. The default value is 30MB. |\n| `_APP_FUNCTIONS_SIZE_LIMIT` | Deprecated since 1.7.0. The maximum size deployment in bytes. The default value is 30MB. |\n| `_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. |\n| `_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. |\n| `_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. |\n| `_APP_FUNCTIONS_CONTAINERS` | **version >= 0.7.0** Deprecated since 1.2.0. Runtimes now timeout by inactivity using `_APP_FUNCTIONS_INACTIVE_THRESHOLD`. |\n| `_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. |\n| `_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. |\n| `_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. |\n| `_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. |\n| `_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. |\n| `_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).|\n| `_APP_EXECUTOR_SECRET` | **version >= 0.13.0** The secret key used by Appwrite to communicate with the function executor. Make sure to change this! |\n| `_APP_EXECUTOR_HOST` | **version >= 0.13.0** The host used by Appwrite to communicate with the function executor! |\n| `_APP_EXECUTOR_RUNTIME_NETWORK`| **version >= 0.13.0** Deprecated with 0.14.0, use `OPEN_RUNTIMES_NETWORK` instead! |\n| `_APP_FUNCTIONS_ENVS` | **version >= 0.7.0** Deprecated with 0.8.0, use `_APP_FUNCTIONS_RUNTIMES` instead! |\n| `_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. |\n| `_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. |\n| `DOCKERHUB_PULL_USERNAME` | **version >= 0.10.0** Deprecated with 1.2.0, use `_APP_DOCKER_HUB_USERNAME` instead! |\n| `DOCKERHUB_PULL_PASSWORD` | **version >= 0.10.0** Deprecated with 1.2.0, use `_APP_DOCKER_HUB_PASSWORD` instead! |\n| `DOCKERHUB_PULL_EMAIL` | **version >= 0.10.0** Deprecated since 1.2.0. Email is no longer needed. |\n| `OPEN_RUNTIMES_NETWORK` | **version >= 0.13.0** Deprecated with 1.2.0, use `_APP_FUNCTIONS_RUNTIMES_NETWORK` instead! |\n| `_APP_COMPUTE_RUNTIMES_NETWORK` | **version >= 1.7.0** The docker network used for communication between the executor and runtimes for sites and functions. |\n| `_APP_FUNCTIONS_RUNTIMES_NETWORK`| **version >= 1.2.0** Deprecated since 1.7.0. The docker network used for communication between the executor and runtimes. |\n| `_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. |\n| `_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. |\n| `_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). |\n| `_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). |\n| `_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. |\n| `_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) |\n\n# VCS (Version Control System) {% #vcs %}\n\n| **Name** | **Description** |\n|---------------------------------|-----------------------------------------------------------------------------------------------------------------|\n| `_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. |\n| `_APP_VCS_GITHUB_PRIVATE_KEY` | **version >= 1.4.0** - GitHub app RSA private key. You can generate private keys from GitHub application settings. |\n| `_APP_VCS_GITHUB_APP_ID` | **version >= 1.4.0** - GitHub application ID. You can find it in your GitHub application details. |\n| `_APP_VCS_GITHUB_CLIENT_ID` | **version >= 1.4.0** - GitHub client ID. You can find it in your GitHub application details. |\n| `_APP_VCS_GITHUB_CLIENT_SECRET` | **version >= 1.4.0** - GitHub client secret. You can generate secrets in your GitHub application settings. |\n| `_APP_VCS_GITHUB_WEBHOOK_SECRET`| **version >= 1.4.0** - GitHub webhook secret. You can configure it in your GitHub application settings under webhook section. |\n\n# Maintenance {% #maintenance %}\n| **Name** | **Description** |\n|-------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| `_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). |\n| `_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). |\n| `_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). |\n| `_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). |\n| `_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). |\n| `_APP_MAINTENANCE_RETENTION_USAGE_HOURLY` | The maximum duration (in seconds) upto which to retain hourly usage metrics. The default value is `8640000` seconds (100 days). |\n| `_APP_MAINTENANCE_RETENTION_SCHEDULES` | Schedules deletion interval (in seconds). |\n\n# GraphQL {% #graphql %}\n\n| **Name** | **Description** |\n|------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| `_APP_GRAPHQL_MAX_BATCH_SIZE` | **version >= 1.2.0** - Maximum number of batched queries per request. The default value is 10. |\n| `_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. |\n| `_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. |\n\n# Migrations {% #migrations %}\n| **Name** | **Description** |\n|-------------------------------------------------|---------------------------------------------------------------------------------------------------------|\n| `_APP_MIGRATIONS_FIREBASE_CLIENT_ID` | **version >= 1.4.0** - Google OAuth client ID. You can find it in your GCP application settings. |\n| `_APP_MIGRATIONS_FIREBASE_CLIENT_SECRET` | **version >= 1.4.0** - Google OAuth client secret. You can generate secrets in your GCP application settings. |\n\n# Assistant {% #assistant %}\n| **Name** | **Description** |\n|----------------------------------------|-------------------------------------------------------------------------|\n| `_APP_ASSISTANT_OPENAI_API_KEY` | **version >= 1.4.0** - OpenAI API key. You can find it in your OpenAI application settings. |"}, {"path": "docs/advanced/self-hosting/configuration/functions", "title": "Functions", "description": "Harness the full power of self-hosted functions with Appwrite. Explore function deployment, management, and integration in your self-hosted environment.", "content": "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).\n\n# Configure function runtimes {% #functions %}\n\nNot all function runtimes are enabled by default. Enable the runtimes that you need and disable unused runtimes to save disk space on your server.\nTo enable a runtime, add it to the `_APP_FUNCTIONS_RUNTIMES` environment variable as a comma-separated list.\n\nThe example below would enable Dart 3.11, .NET 6.0, and Java 18 runtimes.\n\n```bash\n_APP_FUNCTIONS_RUNTIMES=dart-3.11,dotnet-6.0,java-18.0\n```\n\nYou can find a full list of supported runtimes [here](/docs/products/functions/runtimes#available-runtimes).\n\nYou 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.\n\n{% partial file=\"update-variables.md\" /%}\n\n# SSL certificates for function domains {% #ssl-certificates %}\n\nBefore 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.\n\nAppwrite does not handle certificates for function domains (e.g. `6772722a00331315adc3.functions.appwrite.myapp.com`)\nout of the box, since they require wildcard certificates.\nThere are two ways to handle certificate generation.\n\n## Manual certificate generation\n\nThe simplest way to generate certificates for function domains is to use the Appwrite SSL command.\n\n```bash\ndocker compose exec appwrite ssl --domain=\"6772722a00331315adc3.functions.appwrite.myapp.com\"\n```\n\nThe certificate should be generated within a few seconds.\nIf you encounter any issues, you can check the certificate worker logs.\n\n```bash\ndocker compose logs appwrite-worker-certificates\n```\n\nNote that you'll need to run this command for each function domain, and repeat it every time you create a new function.\nIf you have many functions or frequently create new ones, consider using the automated certificate generation method below.\n\n## Automated certificate generation\n\nFor automated certificate generation, Appwrite uses Traefik's DNS Challenge feature.\nThis is required for wildcard certificates (like `*.functions.appwrite.myapp.com`)\nbecause Let's Encrypt uses the DNS-01 challenge to validate wildcard domain ownership.\n\n### Using DNS challenge with DigitalOcean\n\nTo configure Traefik for automated certificate generation with DigitalOcean,\nyou need to modify your `docker-compose.yml`:\n\n1. Add the following under the `traefik` service's `command` section.\n\n```yaml\ncommand:\n # ... existing commands ...\n - --certificatesresolvers.digitalocean.acme.dnschallenge=true\n - --certificatesresolvers.digitalocean.acme.dnschallenge.provider=digitalocean\n - --certificatesresolvers.digitalocean.acme.email=$_APP_SYSTEM_SECURITY_EMAIL_ADDRESS\n - --certificatesresolvers.digitalocean.acme.storage=/storage/certificates/digitalocean.json\n```\n\n2. Add environment variables under the `traefik` service.\n\n```yaml\nenvironment:\n - DO_AUTH_TOKEN=$_APP_DOMAIN_DO_TOKEN\n```\n\n3. Add the following `labels` under the `appwrite` service.\n\n```yaml\nlabels:\n # ... existing labels ...\n - traefik.http.routers.appwrite_api_https.tls.certresolver=digitalocean\n - traefik.http.routers.appwrite_api_https.tls.domains[0].main=$_APP_DOMAIN_FUNCTIONS\n - traefik.http.routers.appwrite_api_https.tls.domains[0].sans=*.$_APP_DOMAIN_FUNCTIONS\n```\n\n4. Ensure these environment variables are properly configured in your `.env` file before proceeding:\n - `_APP_SYSTEM_SECURITY_EMAIL_ADDRESS` must be set to a valid email for Let's Encrypt notifications\n - `_APP_DOMAIN_FUNCTIONS` must be correctly set to your function domain (e.g., `functions.example.com`)\n - `_APP_DOMAIN_DO_TOKEN` must be set to a valid DigitalOcean API token (generate this in the DigitalOcean Console)\n\n5. Apply the changes.\n\n```bash\ndocker compose up -d --force-recreate\n```\n\n### Troubleshooting DNS propagation\n\nIf certificate generation fails, first check the Traefik logs to identify the specific issue.\n\n```bash\ndocker compose logs traefik\n```\n\nA 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.\n\n```yaml\nenvironment:\n - DO_AUTH_TOKEN=$_APP_DOMAIN_DO_TOKEN\n - DO_POLLING_INTERVAL=1m\n - DO_PROPAGATION_TIMEOUT=1h\n```\n\n{% info title=\"Rate limits\" %}\nLet'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.\n{% /info %}\n\nFor other DNS providers, refer to [Traefik's DNS providers documentation](https://doc.traefik.io/traefik/https/acme/#providers)."}, {"path": "docs/advanced/self-hosting/configuration/sites", "title": "Sites", "description": "Harness the full power of self-hosted sites with Appwrite. Explore site deployment, management, and integration in your self-hosted environment.", "content": "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).\n\n# Configure sites runtimes {% #sites %}\n\nNot all site runtimes are enabled by default. Enable the runtimes that you need and disable unused runtimes to save disk space on your server.\nTo enable a runtime, add it to the `_APP_SITES_RUNTIMES` environment variable as a comma-separated list.\n\nThe three runtimes currently available for Sites are the Static, Node.js 22, and Flutter 3.41 runtimes.\n\n```bash\n_APP_SITES_RUNTIMES=static-1,node-22,flutter-3.41\n```\n\nYou 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.\n\n{% partial file=\"update-variables.md\" /%}\n\n# Add an apex domain for sites {% #root-domain %}\n\nAppwrite allows you to add an apex domain to your instance's configuration that you can publicly expose your Site on.\n\n{% info title=\"What is an apex domain?\" %}\n\nAn 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.\n\n{% /info %}\n\nTo add an apex domain, you must first configure either one of the following environment variables in your `.env` file:\n\n- `_APP_DOMAIN_TARGET_A`: Set this to the IPv4 address of your server.\n- `_APP_DOMAIN_TARGET_AAAA`: Set this to the IPv6 address of your server.\n\nHead 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.\n\nNext, 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.\n\n{% info title=\"DNS conflicts\" %}\nMake 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.\n{% /info %}\n\n# SSL certificates for sites domains {% #ssl-certificates %}\n\nBefore 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.\n\nAppwrite does not handle certificates for sites domains (e.g. `6772722a00331315adc3.sites.appwrite.myapp.com`)\nout of the box, since they require wildcard certificates.\nThere are two ways to handle certificate generation.\n\n## Manual certificate generation\n\nThe simplest way to generate certificates for domains is to use the Appwrite SSL command.\n\n```bash\ndocker compose exec appwrite ssl --domain=\"6772722a00331315adc3.sites.appwrite.myapp.com\"\n```\n\nThe certificate should be generated within a few seconds.\nIf you encounter any issues, you can check the certificate worker logs.\n\n```bash\ndocker compose logs appwrite-worker-certificates\n```\n\nNote that you'll need to run this command for each domain, and repeat it every time you create a new site.\nIf you have many sites or frequently create new ones, consider using the automated certificate generation method below.\n\n## Automated certificate generation\n\nFor automated certificate generation, Appwrite uses Traefik's DNS Challenge feature.\nThis is required for wildcard certificates (like `*.appwrite.myapp.com`)\nbecause Let's Encrypt uses the DNS-01 challenge to validate wildcard domain ownership.\n\n### Using DNS challenge with DigitalOcean\n\nTo configure Traefik for automated certificate generation with DigitalOcean,\nyou need to modify your `docker-compose.yml`:\n\n1. Add the following under the `traefik` service's `command` section.\n\n```yaml\ncommand:\n # ... existing commands ...\n - --certificatesresolvers.digitalocean.acme.dnschallenge=true\n - --certificatesresolvers.digitalocean.acme.dnschallenge.provider=digitalocean\n - --certificatesresolvers.digitalocean.acme.email=$_APP_SYSTEM_SECURITY_EMAIL_ADDRESS\n - --certificatesresolvers.digitalocean.acme.storage=/storage/certificates/digitalocean.json\n```\n\n2. Add environment variables under the `traefik` service.\n\n```yaml\nenvironment:\n - DO_AUTH_TOKEN=$_APP_DOMAIN_DO_TOKEN\n```\n\n3. Add the following `labels` under the `appwrite` service.\n\n```yaml\nlabels:\n # ... existing labels ...\n - traefik.http.routers.appwrite_api_https.tls.certresolver=digitalocean\n - traefik.http.routers.appwrite_api_https.tls.domains[0].main=$_APP_DOMAIN_TARGET_CNAME\n - traefik.http.routers.appwrite_api_https.tls.domains[0].sans=*.$_APP_DOMAIN_TARGET_CNAME\n```\n\n4. Ensure these environment variables are properly configured in your `.env` file before proceeding:\n - `_APP_SYSTEM_SECURITY_EMAIL_ADDRESS` must be set to a valid email for Let's Encrypt notifications\n - `_APP_DOMAIN_SITES` must be correctly set to your site domain (e.g., `sites.example.com`)\n - `_APP_DOMAIN_DO_TOKEN` must be set to a valid DigitalOcean API token (generate this in the DigitalOcean Console)\n\n5. Apply the changes.\n\n```bash\ndocker compose up -d --force-recreate\n```\n\n### Troubleshooting DNS propagation\n\nIf certificate generation fails, first check the Traefik logs to identify the specific issue.\n\n```bash\ndocker compose logs traefik\n```\n\nA 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.\n\n```yaml\nenvironment:\n - DO_AUTH_TOKEN=$_APP_DOMAIN_DO_TOKEN\n - DO_POLLING_INTERVAL=1m\n - DO_PROPAGATION_TIMEOUT=1h\n```\n\n{% info title=\"Rate limits\" %}\nLet'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.\n{% /info %}\n\nFor other DNS providers, refer to [Traefik's DNS providers documentation](https://doc.traefik.io/traefik/https/acme/#providers)."}, {"path": "docs/advanced/self-hosting/configuration/sms", "title": "SMS delivery", "description": "Set up SMS services for your self-hosted Appwrite instance. Discover how to configure SMS notifications, verification, and messaging for your applications.", "content": "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.\n\n{% info title=\"Setting up Appwrite Messaging?\" %}\nThis page describes how to setup messaging for your self-hosted Appwrite instance to send one-time passwords during phone login.\n\nIf you are looking to send custom messages for promotions, reminders, and other purposes, view the [documentation for Appwrite Messaging](/docs/products/messaging) documentation.\n{% /info %}\n\n# SMS providers {% #sms-providers %}\n\nAppwrite supports a growing list of SMS providers that you can choose from. Choose one from the list below and set up an account.\n\n{% table %}\n*   {% width=80 %}\n* SMS provider\n* Create account\n* Get credentials\n---\n* {% icon icon=\"twilio\" size=\"l\" /%}\n* Twilio\n* [Website](https://www.twilio.com)\n* [Documentation](https://www.twilio.com/docs/iam/access-tokens#step-2-api-key)\n---\n* {% icon icon=\"textmagic\" size=\"l\" /%}\n* Textmagic\n* [Website](https://www.textmagic.com)\n* [Documentation](https://www.textmagic.com/docs/api/start/#How-to-obtain-the-API-credentials)\n---\n* {% icon icon=\"telesign\" size=\"l\" /%}\n* Telesign\n* [Website](https://www.telesign.com)\n* [Documentation](https://support.telesign.com/s/article/Find-Customer-ID-and-API-Key)\n---\n* {% icon icon=\"msg91\" size=\"l\" /%}\n* MSG91\n* [Website](https://msg91.com)\n* [Documentation](https://msg91.com/help/where-can-i-find-my-authentication-key)\n---\n* {% icon icon=\"vonage\" size=\"l\" /%}\n* Vonage\n* [Website](https://www.vonage.ca/)\n* [Documentation](https://developer.vonage.com/en/account/secret-management)\n{% /table %}\n\n# Environment variables {% #environment-variables %}\n\nYou 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.\n\n{% info title=\"URL encode\" %}\nEnsure 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.\n{% /info %}\n\n| Provider | \\_APP\\_SMS\\_PROVIDER | \\_APP\\_SMS\\_FROM |\n| -------- | -------------------------------------------------- | ----------------------- |\n| Twilio | `sms://:@twilio` | `` |\n| Textmagic| `sms://:@text-magic` | `` |\n| TeleSign | `sms://:@telesign` | `` |\n| MSG91 | `sms://:@msg91` | `` |\n| Vonage | `sms://:@vonage` | `` |\n\n{% partial file=\"update-variables.md\" /%}"}, {"path": "docs/advanced/self-hosting/configuration/storage", "title": "Storage", "description": "Manage self-hosted storage options with Appwrite. Explore file storage, uploads, and customization in your self-hosted Appwrite environment.", "content": "Appwrite's Storage Service can be configured to store files locally, or with self-hosted and cloud storage services. \nBy default, Appwrite's Storage Service **stores files on your server's local storage**.\nIf you expect large volumes of data or the need to have scalable data storage, you may choose to use a separate storage service.\n\n# Available adapters {% #available-adapters %}\nAppwrite supports AWS S3, Digital Ocean Spaces, Backblaze, Akamai Object Storage, and Wasabi as storage adapters. \nSome of these services can be self-hosted, just like Appwrite.\n\nYou 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.\n\n{% arrow_link href=\"/docs/advanced/self-hosting/environment-variables\" %}\nLearn more about storage environment variables \n{% /arrow_link %}\n\n# Maximum file size {% #maximum-file-size %}\n\nThe maximum size for a single file upload is controlled by the `_APP_STORAGE_LIMIT` environment variable, which defaults to 30 MB. \n[Learn more about environment variables](/docs/advanced/self-hosting/environment-variables).\n\n{% partial file=\"update-variables.md\" /%}"}, {"path": "docs/advanced/self-hosting/configuration/tls-certificates", "title": "TLS Certificates", "description": "Secure your self-hosted Appwrite instance with TLS certificates. Learn how to obtain, configure, and manage TLS certificates for enhanced security.", "content": "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.\n\n1. You need to use a public-facing domain with a known TLD pointing to your Appwrite instance.\n2. 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.\n3. 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.\n4. 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.\n\n# Debugging {% #debugging %}\n\nIf you're still struggling with your certificates, check the Appwrite certificates worker log. You can do that with the following command:\n\n```bash\ndocker compose logs appwrite-worker-certificates\n```\n\n# Generation cycle {% #generation-cycle %}\n\nAppwrite 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.\n\nCertificate 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.\n\n# Manual generation {% #manual-generation %}\n\nSince 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:\n\n```bash\ndocker compose exec appwrite ssl\n```\n\nIf you want to generate a certificate for a specific domain, pass it as a parameter into the command:\n\n```bash\ndocker compose exec appwrite ssl domain=\"api.myapp.com\"\n```\n\n# Development and localhost {% #development-and-localhost %}\n\nYou 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.\n\nWhen 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.\n\n{% partial file=\"update-variables.md\" /%}"}, {"path": "docs/advanced/self-hosting/configuration/version-control", "title": "Version control", "description": "Configure version control integration for Functions and Sites in your self-hosted Appwrite instance.", "content": "{% partial file=\"configure-github-app.md\" /%}\n\n## Apply configuration\n\nAfter creating your GitHub App, restart your Appwrite services to apply the configuration:\n\n```bash\ndocker compose up -d\n```\n\n## Verify configuration\n\nTo verify that your GitHub App is correctly configured:\n\n1. Open the Appwrite Console and navigate to a project.\n2. Go to either the Functions or Sites section.\n3. Try creating a new Function or Site using GitHub as the source.\n4. You should be prompted to install your GitHub App on your repositories.\n\n## Troubleshooting\n\nIf you encounter issues with your GitHub App integration:\n\n- Ensure your Appwrite instance is publicly accessible, as GitHub needs to send webhook events.\n- Check that the webhook URL is correctly formatted with your domain.\n- Verify the permissions granted to the GitHub App are correct.\n- Check the Appwrite logs for any errors related to GitHub integration:\n\n```bash\ndocker compose logs appwrite\n```\n\n{% partial file=\"update-variables.md\" /%}"}, {"path": "docs/advanced/self-hosting/installation", "title": "Installation", "description": "Step-by-step guide to install Appwrite using Docker. Learn how to set up a self-hosted Appwrite instance with Docker Compose on any operating system.", "content": "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.\n\n# System requirements {% #system-requirements %}\n\nBefore installing Appwrite, ensure your system meets these minimum requirements:\n\n- **2 CPU cores**\n- **4GB of RAM**\n- **2GB of swap memory**\n- **Operating system** that supports Docker\n- **Docker Compose Version 2**\n\n# Install with Docker {% #install-with-docker %}\n\nThe 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.\n\nBefore running the installation command, ensure you have [Docker CLI](https://www.docker.com/products/docker-desktop) installed on your host machine.\n\n{% info title=\"Firewall configuration\" %}\nThe 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.\n{% /info %}\n\n## Installation commands {% #installation-commands %}\n\n{% tabs %}\n{% tabsitem #unix title=\"macOS and Linux\" %}\nRun the following command in your terminal:\n\n```bash\ndocker run -it --rm \\\n --publish 20080:20080 \\\n --volume /var/run/docker.sock:/var/run/docker.sock \\\n --volume \"$(pwd)\"/appwrite:/usr/src/code/appwrite:rw \\\n --entrypoint=\"install\" \\\n appwrite/appwrite:1.9.0\n```\n{% /tabsitem %}\n\n{% tabsitem #cmd title=\"Windows (CMD)\" %}\n```cmd\ndocker run -it --rm ^\n --publish 20080:20080 ^\n --volume //var/run/docker.sock:/var/run/docker.sock ^\n --volume \"%cd%\"/appwrite:/usr/src/code/appwrite:rw ^\n --entrypoint=\"install\" ^\n appwrite/appwrite:1.9.0\n```\n{% /tabsitem %}\n\n{% tabsitem #powershell title=\"Windows (PowerShell)\" %}\n```powershell\ndocker run -it --rm `\n --publish 20080:20080 `\n --volume /var/run/docker.sock:/var/run/docker.sock `\n --volume ${pwd}/appwrite:/usr/src/code/appwrite:rw `\n --entrypoint=\"install\" `\n appwrite/appwrite:1.9.0\n```\n{% /tabsitem %}\n{% /tabs %}\n\nOnce the command is running, open your browser and navigate to `http://localhost:20080` to access the setup wizard.\n\n# Setup wizard {% #setup-wizard %}\n\nThe setup wizard walks you through four steps to configure your Appwrite instance.\n\n## Step 1: Setup your app {% #wizard-setup %}\n\nConfigure your Appwrite instance's basic settings.\n\n{% only_dark %}\n![Setup your app](/images/docs/advanced/self-hosting/installation/dark/wizard-setup.avif)\n{% /only_dark %}\n{% only_light %}\n![Setup your app](/images/docs/advanced/self-hosting/installation/wizard-setup.avif)\n{% /only_light %}\n\n- **Hostname** - The domain or IP address where your Appwrite instance will be accessible.\n- **Database** - Choose between [MongoDB or MariaDB](/docs/advanced/self-hosting/configuration/databases) as your database backend. MongoDB is selected by default.\n- **Advanced settings** - Optionally configure HTTP and HTTPS ports, SSL certificate email, and an OpenAI API key for the [Appwrite Assistant](/docs/tooling/assistant).\n\n## Step 2: Secure your app {% #wizard-secure %}\n\nA secret API key is automatically generated for your instance. This key is used to encrypt sensitive data.\n\n{% only_dark %}\n![Secure your app](/images/docs/advanced/self-hosting/installation/dark/wizard-secure.avif)\n{% /only_dark %}\n{% only_light %}\n![Secure your app](/images/docs/advanced/self-hosting/installation/wizard-secure.avif)\n{% /only_light %}\n\n{% info title=\"Save your key\" %}\nYou won't be able to see this key again after proceeding. Copy it somewhere safe before continuing.\n{% /info %}\n\nYou can use the **Copy** button to copy the key or **Regenerate** to create a new one.\n\n## Step 3: Create your account {% #wizard-account %}\n\nSet up the email and password for your Appwrite account. You'll use these credentials to sign in to the Appwrite Console after installation.\n\n{% only_dark %}\n![Create your account](/images/docs/advanced/self-hosting/installation/dark/wizard-account.avif)\n{% /only_dark %}\n{% only_light %}\n![Create your account](/images/docs/advanced/self-hosting/installation/wizard-account.avif)\n{% /only_light %}\n\n## Step 4: Review your setup {% #wizard-review %}\n\nReview 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.\n\n{% only_dark %}\n![Review your setup](/images/docs/advanced/self-hosting/installation/dark/wizard-review.avif)\n{% /only_dark %}\n{% only_light %}\n![Review your setup](/images/docs/advanced/self-hosting/installation/wizard-review.avif)\n{% /only_light %}\n\n# Manual installation {% #manual-installation %}\n\nFor advanced users who prefer manual setup, you can install Appwrite using Docker Compose directly.\n\n## Generate configuration files {% #generate-configuration %}\n\n{% compose_generator %}{% /compose_generator %}\n\n## Install Appwrite {% #install-appwrite %}\n\n1. Create a directory named `appwrite` and place both `docker-compose.yml` and `.env` inside\n2. Edit the `.env` file to customize your installation. At minimum, update `_APP_OPENSSL_KEY_V1` and `_APP_EXECUTOR_SECRET` with unique secret values\n3. Start the Appwrite stack:\n\n```bash\ndocker compose up -d --remove-orphans\n```\n\n# Post-installation {% #post-installation %}\n\nAfter installation completes:\n\n1. **Access the Console** - Navigate to your machine's hostname or IP address in your browser\n2. **Create your first project** - Set up your development environment\n\n{% info title=\"Startup time\" %}\nOn non-Linux hosts, the server might take a few minutes to start after installation completes. This is normal behavior.\n{% /info %}\n\n## SDK version compatibility {% #sdk-version-compatibility %}\n\nThe tables below map each released self-hosted Appwrite version to the SDK versions that were current at the time of that release. Use this to pin your SDK to a version known to work with your server. The latest stable self-hosted Appwrite release is 1.9.0.\n\n### Client SDKs {% #client-compatibility %}\n\n{% table %}\n* Appwrite {% width=120 %}\n* Web\n* Flutter\n* React Native\n* Apple\n* Android\n---\n* 1.7.0\n* 18.0.0\n* 16.0.0\n* 0.9.0\n* 10.0.0\n* 8.0.0\n---\n* 1.7.1 to 1.7.3\n* 18.0.0\n* 16.0.0\n* 0.9.0\n* 10.0.0\n* 8.0.0\n---\n* 1.7.4\n* 18.1.1\n* 17.0.0\n* 0.10.0\n* 10.0.0\n* 8.0.0\n---\n* 1.7.5\n* 18.1.1\n* 17.0.0\n* 0.10.0\n* 10.0.0\n* 8.0.0\n---\n* 1.8.0\n* 21.4.0\n* 20.3.0\n* 0.18.0\n* 13.3.0\n* 11.3.0\n---\n* 1.8.1\n* 23.0.0\n* 22.0.0\n* 0.25.0\n* 15.0.0\n* 13.0.0\n---\n* 1.9.0\n* 24.1.1\n* 23.0.0\n* 0.27.1\n* 16.0.0\n* 14.1.0\n{% /table %}\n\n### Server SDKs {% #server-compatibility %}\n\n{% table %}\n* Appwrite {% width=120 %}\n* Node.js\n* Python\n* PHP\n* Dart\n* Ruby\n* .NET\n* Go\n* Swift\n* Kotlin\n* CLI\n---\n* 1.7.0\n* 17.0.0\n* 11.0.0\n* 15.0.0\n* 16.0.0\n* 16.0.0\n* 0.13.0\n* v0.7.0\n* 10.0.0\n* 9.0.0\n* 6.2.3\n---\n* 1.7.1 to 1.7.3\n* 17.0.0\n* 11.0.0\n* 15.0.0\n* 16.0.0\n* 16.0.0\n* 0.13.0\n* v0.7.0\n* 10.0.0\n* 9.0.0\n* 6.2.3\n---\n* 1.7.4\n* 17.1.0\n* 11.0.0\n* 15.0.0\n* 16.1.0\n* 16.0.0\n* 0.13.0\n* v0.7.0\n* 10.0.0\n* 9.0.0\n* 8.0.0\n---\n* 1.7.5\n* 17.1.0\n* 11.0.0\n* 15.0.0\n* 16.1.0\n* 16.0.0\n* 0.13.0\n* v0.7.0\n* 10.0.0\n* 9.0.0\n* 8.0.0\n---\n* 1.8.0\n* 20.2.1\n* 13.4.1\n* 17.5.0\n* 19.3.0\n* 19.3.0\n* 0.22.0\n* v0.13.1\n* 13.2.2\n* 12.3.0\n* 12.0.1\n---\n* 1.8.1\n* 22.1.3\n* 16.0.0\n* 20.2.1\n* 21.3.0\n* 21.1.0\n* 1.0.0\n* v1.0.0\n* 15.2.0\n* 14.1.0\n* 15.0.0\n---\n* 1.9.0\n* 23.1.0\n* 17.0.0\n* 21.0.0\n* 22.0.0\n* 22.0.0\n* 2.0.0\n* v2.0.0\n* 16.0.0\n* 15.0.0\n* 17.4.0\n{% /table %}\n\n# Managing your installation {% #managing-installation %}\n\n## Stop Appwrite {% #stop %}\n\nTo stop your Appwrite containers:\n\n```bash\ndocker compose stop\n```\n\n## Restart Appwrite {% #restart %}\n\nTo restart your Appwrite containers:\n\n```bash\ndocker compose start\n```\n\n## Uninstall Appwrite {% #uninstall %}\n\nTo completely remove Appwrite and all its data:\n\n```bash\ndocker compose down -v\n```\n\n{% info title=\"Data loss warning\" %}\nThe uninstall command will permanently delete all your Appwrite data. Make sure to backup any important information before running this command.\n{% /info %}\n\n# Next steps {% #next-steps %}\n\nAfter successfully installing Appwrite, you can:\n\n[Deploy on cloud platforms](/docs/advanced/self-hosting/platforms/aws) - Learn how to deploy on AWS, DigitalOcean, and other cloud providers\n\n[Configure services](/docs/advanced/self-hosting/configuration/email) - Set up email, SMS, storage, and other services\n\n[Configure databases](/docs/advanced/self-hosting/configuration/databases) - Learn more about MongoDB and MariaDB configuration\n\n[Production setup](/docs/advanced/self-hosting/production) - Prepare your installation for production use\n\n[Update Appwrite](/docs/advanced/self-hosting/production/updates) - Keep your installation up to date"}, {"path": "docs/advanced/self-hosting/platforms/aws", "title": "AWS deployment", "description": "Deploy Appwrite on Amazon Web Services using the one-click Marketplace app.", "content": "Deploy Appwrite on AWS using the pre-configured Marketplace app.\n\n# One-click installation {% #one-click %}\n\n{% section #marketplace-install step=1 title=\"Launch from AWS Marketplace\" %}\n\n1. Visit the [Appwrite AWS Marketplace page](https://aws.amazon.com/marketplace/pp/prodview-2hiaeo2px4md6)\n2. Click **Continue to Subscribe**\n3. Review and accept the subscription terms\n4. Click **Continue to Configuration**\n5. Choose your preferred region and software version\n6. Click **Continue to Launch**\n\n{% /section %}\n\n{% section #configure-instance step=2 title=\"Configure your instance\" %}\n\n1. Choose **Launch through EC2** action\n2. Select instance type:\n - **t3.medium** minimum (2 vCPU, 4 GB RAM)\n - **t3.large** or larger for production\n3. Configure security group to allow:\n - HTTP (80)\n - HTTPS (443)\n - SSH (22) from your IP\n4. Select your EC2 key pair\n5. Click **Launch**\n\n{% /section %}\n\n{% section #complete-setup step=3 title=\"Complete setup\" %}\n\n1. Wait for instance to launch and pass status checks\n2. Navigate to your instance's public IP address in a web browser\n3. Complete the initial Appwrite setup wizard\n\n{% /section %}\n\n# Custom installations {% #custom-installations %}\n\nFor 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.\n\n# Production considerations {% #production %}\n\nFor production deployments, optimization, and advanced AWS configuration, see the [production deployment guide](/docs/advanced/self-hosting/production).\n\n# Next steps {% #next-steps %}\n\nAfter successful deployment:\n\n- [Configure services](/docs/advanced/self-hosting/configuration) - Set up email, storage, and other services\n- [Production optimization](/docs/advanced/self-hosting/production) - Prepare for production workloads\n- [Updates and maintenance](/docs/advanced/self-hosting/production/updates) - Keep your instance up to date"}, {"path": "docs/advanced/self-hosting/platforms/azure", "title": "Azure deployment", "description": "Deploy Appwrite on Microsoft Azure using Virtual Machines. Learn how to set up a production-ready Appwrite instance on Azure.", "content": "Deploy Appwrite on Microsoft Azure using Virtual Machines.\n\n# Virtual Machines deployment {% #virtual-machines %}\n\nAzure 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.\n\n# Custom installations {% #custom-installations %}\n\nFor 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.\n\n# Production considerations {% #production %}\n\nFor production deployments, optimization, and advanced configuration, see the [production deployment guide](/docs/advanced/self-hosting/production).\n\n# Next steps {% #next-steps %}\n\nAfter successful deployment:\n\n- [Configure services](/docs/advanced/self-hosting/configuration) - Set up email, storage, and other services\n- [Production optimization](/docs/advanced/self-hosting/production) - Prepare for production workloads\n- [Updates and maintenance](/docs/advanced/self-hosting/production/updates) - Keep your instance up to date"}, {"path": "docs/advanced/self-hosting/platforms/coolify", "title": "Coolify", "description": "Learn how to self-host Appwrite on your infrastructure with Coolify.", "content": "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.\n\n# Prerequisites {% #prerequisites %}\n\nBefore starting, ensure your server meets the [minimum requirements](/docs/advanced/self-hosting#system-requirements) for hosting Appwrite with Coolify.\n\n# Installation {% #installation %}\n\nInstall Coolify on your server using the command below:\n\n```bash\ncurl -sSL https://coolify.io/install | bash\n```\n\nOnce the installation is complete, open the Coolify dashboard in your web browser.\n\n1. Sign up for a new account.\n2. Click the **Create Project** button to start a new project.\n3. Select or create an environment type. By default, a **production** environment is already available.\n4. Click **Add new resource**.\n5. Search for and select **Appwrite** from the list of services.\n\nThe 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.\n\nAfter deployment, access your Appwrite instance's console by clicking the console link in the **Links** section of the Appwrite service.\n\n# Configuration {% #configuration %}\n\nCoolify 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.\n\n**Assistant**\nTo enable the assistant, which allows you to generate code snippets and assist with documentation for your Appwrite project, set your OpenAI API key:\n\n```bash\n_APP_ASSISTANT_OPENAI_API_KEY=sk-1234567890\n```\n\n**SMS Notifications**\nTo enable SMS-based OTP authentication, configure the following environment variables:\n\n```bash\n_APP_SMS_FROM=123456789\n_APP_SMS_PROVIDER=sms://username:password@mock\n```\n\n**Email Notifications**\nTo enable email notifications, configure these environment variables:\n\n```bash\n_APP_SMTP_HOST=smtp.example.com\n_APP_SMTP_PASSWORD=password\n_APP_SMTP_PORT=587\n_APP_SMTP_SECURE=true\n_APP_SMTP_USERNAME=username\n```\n\n**GitHub Integration**\nTo enable GitHub authentication for your Appwrite console, set these environment variables:\n\n```bash\n_APP_VCS_GITHUB_APP_ID\n_APP_VCS_GITHUB_APP_NAME\n_APP_VCS_GITHUB_CLIENT_ID\n_APP_VCS_GITHUB_CLIENT_SECRET\n_APP_VCS_GITHUB_PRIVATE_KEY\n```\n\nThe [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.\n\n# Troubleshooting {% #troubleshooting %}\n\n1. **Site redirected you too many times**\n 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."}, {"path": "docs/advanced/self-hosting/platforms/digitalocean", "title": "DigitalOcean deployment", "description": "Deploy Appwrite on DigitalOcean using the one-click Marketplace app.", "content": "Deploy Appwrite on DigitalOcean using the pre-configured Marketplace app.\n\n\n# One-click installation {% #one-click %}\n\n{% section #marketplace-install step=1 title=\"Create marketplace Droplet\" %}\n\n1. Visit the [Appwrite Marketplace page](https://marketplace.digitalocean.com/apps/appwrite)\n2. Click **Create Appwrite Droplet**\n3. Choose your configuration:\n - **Plan**: Minimum 4GB RAM recommended\n - **Region**: Select closest to your users\n - **SSH keys**: Add your SSH key for access\n4. Click **Create Droplet**\n\n{% /section %}\n\n{% section #marketplace-setup step=2 title=\"Complete setup\" %}\n\n1. Wait for Droplet provisioning to complete\n2. Navigate to your Droplet's IP address in a web browser\n3. Complete the initial setup wizard following the prompts\n\n{% /section %}\n\n# Custom installations {% #custom-installations %}\n\nFor 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.\n\n# Production considerations {% #production %}\n\nFor production deployments, optimization, and advanced configuration, see the [production deployment guide](/docs/advanced/self-hosting/production).\n\n# Next steps {% #next-steps %}\n\nAfter successful deployment:\n\n- [Configure services](/docs/advanced/self-hosting/configuration) - Set up email, storage, and other services\n- [Production optimization](/docs/advanced/self-hosting/production) - Prepare for production workloads\n- [Updates and maintenance](/docs/advanced/self-hosting/production/updates) - Keep your instance up to date"}, {"path": "docs/advanced/self-hosting/platforms/google-cloud", "title": "Google Cloud deployment", "description": "Deploy Appwrite on Google Cloud Platform using Compute Engine. Learn how to set up a production-ready Appwrite instance on GCP.", "content": "Deploy Appwrite on Google Cloud Platform using Compute Engine virtual machines.\n\n# Compute Engine deployment {% #compute-engine %}\n\nGoogle 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.\n\n# Custom installations {% #custom-installations %}\n\nFor 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.\n\n# Production considerations {% #production %}\n\nFor production deployments, optimization, and advanced configuration, see the [production deployment guide](/docs/advanced/self-hosting/production).\n\n# Next steps {% #next-steps %}\n\nAfter successful deployment:\n\n- [Configure services](/docs/advanced/self-hosting/configuration) - Set up email, storage, and other services\n- [Production optimization](/docs/advanced/self-hosting/production) - Prepare for production workloads\n- [Updates and maintenance](/docs/advanced/self-hosting/production/updates) - Keep your instance up to date"}, {"path": "docs/advanced/self-hosting/production", "title": "Preparation", "description": "Optimize self-hosted Appwrite for production environments. Learn key concepts and best practices for deploying Appwrite in production.", "content": "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.\n\nThis guide assumes you have some basic understanding of Docker and Docker Compose command-line tools.\n\n# Production checklist {% #production-checklist %}\n\nBefore deploying Appwrite to production, ensure you have configured:\n\n- **[Security](/docs/advanced/self-hosting/production/security)** - Implement essential security practices\n- **[Scaling](/docs/advanced/self-hosting/production/scaling)** - Configure horizontal and vertical scaling for your containers\n- **[Rate limits](/docs/advanced/self-hosting/production/rate-limits)** - Enable rate limiting to protect against abuse\n- **[Email delivery](/docs/advanced/self-hosting/production/emails)** - Set up reliable SMTP for production email delivery\n- **[Error monitoring](/docs/advanced/self-hosting/production/errors)** - Configure error tracking and logging\n- **[Backups](/docs/advanced/self-hosting/production/backups)** - Set up regular database and storage backups\n- **[Updates](/docs/advanced/self-hosting/production/updates)** - Plan for version updates and migrations\n- **[Debugging](/docs/advanced/self-hosting/production/debugging)** - Set up monitoring and debugging tools\n\n# Key principles {% #key-principles %}\n\nWhen deploying Appwrite in production:\n\n- **Security first** - Always use HTTPS, secure your console access, and implement proper authentication\n- **Monitor everything** - Set up logging, error tracking, and performance monitoring\n- **Plan for scale** - Design your infrastructure to handle growth\n- **Backup regularly** - Implement automated backup strategies for data protection\n- **Stay updated** - Keep Appwrite and dependencies up to date with security patches\n\n{% arrow_link href=\"/docs/advanced/self-hosting/production/scaling\" %}\nStart with scaling configuration\n{% /arrow_link %}"}, {"path": "docs/advanced/self-hosting/production/backups", "title": "Backups", "description": "Learn how to set up and manage backups for your self-hosted Appwrite instance to ensure data safety and disaster recovery.", "content": "{% info title=\"Looking for automated backups?\" %}\nAppwrite Cloud offers automated [Backups as a Service](/docs/products/databases/backups) with scheduling and one-click restore.\n\nFor self-hosted instances, you'll need to implement manual backup procedures as outlined on this page.\n{% /info %}\n\nSelf-hosted Appwrite requires manual backup procedures to protect your data.\n\n# What to back up {% #what-to-backup %}\n\nYour Appwrite installation has several components that need backing up:\n\n1. **Database** - User data, documents, and configuration\n2. **Storage volumes** - Uploaded files and function code\n3. **Environment variables** - Configuration in `.env`\n4. **System snapshots** - Complete server state (alternative approach)\n\n# Database backups {% #database-backup %}\n\nAppwrite supports MariaDB and MongoDB as database backends. Use the backup method that matches your installation.\n\n## MariaDB backups {% #mariadb-backup %}\n\nUse `mysqldump` for MariaDB installations:\n\n```bash\n# Create database backup (all databases)\ndocker compose exec mariadb sh -c 'exec mysqldump --all-databases --add-drop-database --single-transaction --routines --triggers -uroot -p\"$MYSQL_ROOT_PASSWORD\"' > ./dump.sql\n\n# Restore (fresh installation only)\ndocker compose exec -T mariadb sh -c 'exec mysql -uroot -p\"$MYSQL_ROOT_PASSWORD\"' < dump.sql\n```\n\nFor large databases, consider `mariabackup` for physical backups.\n\n## MongoDB backups {% #mongodb-backup %}\n\nUse `mongodump` for MongoDB installations:\n\n```bash\n# Create database backup\ndocker compose exec mongodb sh -c 'exec mongodump --username=root --password=\"$MONGO_INITDB_ROOT_PASSWORD\" --authenticationDatabase=admin --archive' > ./dump.archive\n\n# Restore (fresh installation only)\ndocker compose exec -T mongodb sh -c 'exec mongorestore --username=root --password=\"$MONGO_INITDB_ROOT_PASSWORD\" --authenticationDatabase=admin --archive' < dump.archive\n```\n\n{% info title=\"Fresh installation only\" %}\nOnly restore to fresh Appwrite installations to avoid data corruption.\n{% /info %}\n\n# Storage volume backups {% #storage-backup %}\n\nShut down Appwrite before backing up volumes to avoid data inconsistency.\n\nAppwrite uses these Docker volumes:\n\n- `appwrite-uploads` - User files\n- `appwrite-functions` - Function code\n- `appwrite-builds` - Build artifacts\n- `appwrite-sites` - Static sites\n- `appwrite-certificates` - SSL certificates\n- `appwrite-config` - Configuration\n- `appwrite-cache` and `appwrite-redis` - Cache data\n- `appwrite-mariadb` - Database files (MariaDB installations)\n- `appwrite-mongodb` - Database files (MongoDB installations)\n\n## Backup methods\n\n**Docker volume backup:**\n```bash\n# Backup volume\ndocker run --rm -v volume_name:/data -v $(pwd)/backup:/backup ubuntu tar czf \"/backup/volume_name.tar.gz\" -C /data .\n\n# Restore volume\ndocker run --rm -v volume_name:/data -v $(pwd)/backup:/backup ubuntu tar xzf \"/backup/volume_name.tar.gz\" -C /data\n```\n\n**Direct copy:**\n```bash\ndocker volume inspect volume_name\nsudo cp -a /var/lib/docker/volumes/volume_name/_data /backup/volume_name_backup\n```\n\n\n\n{% info title=\"External storage\" %}\nFor S3/GCS/Azure storage, use your provider's native backup tools.\n{% /info %}\n\n# Environment variables {% #env-backup %}\n\nBack up your `.env` file containing configuration and secrets:\n\n```bash\ncp .env .env.backup.$(date +\"%Y%m%d\")\n```\n\n{% info title=\"Critical variable\" %}\nThe `_APP_OPENSSL_KEY_V1` encrypts your data. Copy this exact value when restoring, or encrypted data becomes inaccessible.\n{% /info %}\n\nStore `.env` backups securely due to sensitive data.\n\n# System snapshots {% #system-snapshots %}\n\nAs an alternative to individual backups, snapshot your entire server:\n\n- **AWS EC2:** Actions > Image > Create Image\n- **GCP/Azure/DigitalOcean:** Use provider snapshot features\n\nSystem snapshots capture complete server state and enable fast recovery, but use more storage than selective backups.\n\n# Best practices {% #best-practices %}\n\n## Automation\n\n**Schedule backups** with cron jobs or cloud automation:\n```bash\n# Daily database backup at 2 AM\n0 2 * * * /path/to/backup-script.sh\n```\n\n**Follow 3-2-1 rule:** 3 copies, 2 different media, 1 offsite.\n\n**Monitor backup jobs** and set alerts for failures.\n\n## Third-party tools\n\nFor production environments:\n- **Restic** - Cross-platform backup with encryption\n- **Borg** - Deduplicating backup program\n- **Cloud provider tools** - AWS/Azure/GCP backup services\n- **Third-party backup services** - Automated backup solutions\n\n## Disaster recovery\n\nDefine your requirements:\n- **RPO (Recovery Point Objective)** - Acceptable data loss window\n- **RTO (Recovery Time Objective)** - Acceptable downtime window\n\n**Test restores quarterly** to verify backup integrity.\n\nKeep backups **offsite** and **encrypted**. Document recovery procedures and update contact information.\n\n## Security\n\n- Encrypt backup files\n- Restrict backup storage access\n- Audit backup systems regularly\n- Meet compliance requirements for your industry"}, {"path": "docs/advanced/self-hosting/production/debugging", "title": "Debug", "description": "Master debugging techniques for self-hosted Appwrite. Discover best practices and tools for troubleshooting and maintaining a robust deployment.", "content": "Appwrite comes with a few built-in tools and methods that easily debug and investigate issues on your Appwrite stack environment.\n\n# Doctor CLI {% #doctor-cli %}\n\nThe 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.\n\nTo 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).\n\n```bash\ndocker exec appwrite doctor\n```\n\n# Logs {% #logs %}\n\nChecking 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:\n\n```bash\ndocker ps\n```\n\nThe 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:\n\n```bash\ndocker logs [CONTAINER-ID]\n```\n\n# Status codes {% #status-codes %}\n\nAppwrite 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.\n\n{% arrow_link href=\"/docs/apis/response-codes\" %}\nLearn more about Appwrite status codes\n{% /arrow_link %}\n\n# Development mode {% #development-mode%}\n\nWhen 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).\n\nTo 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.\n\n```text\n_APP_ENV=development\n_APP_OPENSSL_KEY_V1=your-secret-key\n_APP_DOMAIN=localhost\n```\n{% partial file=\"update-variables.md\" /%}"}, {"path": "docs/advanced/self-hosting/production/emails", "title": "Email delivery", "description": "Configure reliable email delivery for your self-hosted Appwrite instance in production environments.", "content": "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.\n\nYou 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."}, {"path": "docs/advanced/self-hosting/production/errors", "title": "Error monitoring", "description": "Configure error reporting and monitoring for your self-hosted Appwrite instance in production.", "content": "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.\n\nIn 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`.\n\nTo monitor errors in production, configure the `_APP_LOGGING_CONFIG` environment variable with your provider's DSN. The supported DSN formats are:\n\n- Sentry: `sentry://PUBLIC_KEY@HOST:PORT/PROJECT_ID`\n- LogOwl: `logowl://SERVICE_TICKET@SERVICE_HOST/`\n- Raygun: `raygun://RAYGUN_API_KEY/`\n- AppSignal: `appsignal://API_KEY/`"}, {"path": "docs/advanced/self-hosting/production/rate-limits", "title": "Rate limits", "description": "Configure rate limiting for your self-hosted Appwrite instance to protect against abuse and attacks.", "content": "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`.\n\nRate 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.\n\n# How rate limits work {% #how-rate-limits-work %}\n\nRate limits in self-hosted Appwrite apply differently depending on how you're accessing the API:\n\n- **Client SDKs**: Rate limits apply to all requests from client applications\n- **Server SDKs with API keys**: Rate limits do not apply when using API keys\n\n{% arrow_link href=\"/docs/advanced/security/rate-limits\" %}\nLearn more about how rate limits work\n{% /arrow_link %}\n\n# Abuse log retention {% #abuse-logs %}\n\nConfigure how long abuse attempt logs are retained using the `_APP_MAINTENANCE_RETENTION_ABUSE` environment variable. The default value is `86400` seconds (1 day).\n\n```bash\n_APP_MAINTENANCE_RETENTION_ABUSE=86400\n```\n\nShorter retention periods reduce storage usage, while longer periods provide better security audit trails.\n\n# Development vs production {% #development-vs-production %}\n\nFor development environments, you can temporarily disable rate limits to avoid interruptions during testing:\n\n```bash\n_APP_OPTIONS_ABUSE=disabled\n```\n\n**Important**: Always re-enable rate limits before deploying to production by setting:\n\n```bash\n_APP_OPTIONS_ABUSE=enabled\n```\n\n{% arrow_link href=\"/docs/advanced/self-hosting/configuration/environment-variables\" %}\nLearn more about environment variables\n{% /arrow_link %}"}, {"path": "docs/advanced/self-hosting/production/scaling", "title": "Scaling", "description": "Learn how to scale your self-hosted Appwrite instance horizontally and vertically to handle increased load.", "content": "Appwrite is built with scalability in mind. Appwrite can scale both horizontally and vertically.\n\nEach 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.\n\nIf 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.\n\nTwo 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.\n\n# Performance considerations {% #performance-considerations %}\n\nYou may want to adjust the `_APP_WORKER_PER_CORE` environment variable to optimize worker processes per CPU core based on your hardware:\n\n```bash\n_APP_WORKER_PER_CORE=\n```\n\nThis 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.\n\n# Resource management considerations {% #resource-management %}\n\nWhen scaling Docker containers, consider implementing resource limits and monitoring:\n\n## Log rotation\nConsider configuring log rotation to prevent disk space issues:\n```yaml\nx-logging: &x-logging\n logging:\n driver: 'json-file'\n options:\n max-file: ''\n max-size: ''\n```\n\n## Redis memory management\nYou may want to set memory limits for Redis to prevent out-of-memory issues:\n\n# Monitoring considerations {% #monitoring %}\n\n- **Health checks**: Consider implementing health check endpoints for services\n- **Resource monitoring**: Monitor CPU, memory, and disk usage as needed\n- **Log aggregation**: Centralized logging can help with debugging scaled deployments\n- **Alert thresholds**: Consider setting alerts for high resource usage"}, {"path": "docs/advanced/self-hosting/production/security", "title": "Security", "description": "Implement essential security practices for your self-hosted Appwrite instance to protect your data and infrastructure.", "content": "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.\n\n# Encryption {% #encryption %}\n\nAppwrite 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.\n\nYou **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.\n\nMake sure to keep this key in a safe place and never make it publicly accessible.\n\n{% info title=\"Best practice\" %}\nYou should always prefer **HTTPS** over HTTP in production environments. This keeps your APIs secure and prevents any redirects from interfering with your requests.\nYou can force the use of HTTPS with the [_APP_OPTIONS_FORCE_HTTPS](/docs/advanced/self-hosting/environment-variables) environment variable.\n{% /info %}\n\n\n# Console access {% #console-access %}\n\nAppwrite provides three different methods to limit access to your Appwrite Console.\n\n1. Whitelist a group of developers by IP using the `_APP_CONSOLE_WHITELIST_IPS` environment variable.\n2. Whitelist a group of developers by email using the `_APP_CONSOLE_WHITELIST_EMAILS` environment variable.\n3. 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.\n\nBy 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.\n\n{% arrow_link href=\"/docs/advanced/self-hosting/environment-variables\" %}\nLearn more about environment variables\n{% /arrow_link %}\n\n# Security auditing {% #security-auditing %}\n\nIn 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.\n\n{% partial file=\"update-variables.md\" /%}"}, {"path": "docs/advanced/self-hosting/production/updates", "title": "Updates and migrations", "description": "Keep your self-hosted Appwrite instance up-to-date. Learn how to perform updates, manage versions, and ensure your self-hosted Appwrite stays current.", "content": "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.\n\nYou 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.\n\nIf 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:\n\n1. `1.5.11`\n1. `1.6.2`\n1. `1.7.4`\n\nBefore upgrading, be sure to:\n\n1. [back up your server](/docs/advanced/self-hosting/production/backups) data before running the migration\n1. review the [changelog](https://github.com/appwrite/appwrite/releases) for any breaking changes\n1. test the migration process on a non-production instance to make sure your application is working well\n\n# Installing the next version {% #install-next-version %}\n\nThe first step is to install the latest version of Appwrite. Head to the directory where you ran your previous Appwrite install command.\n\n```text\nparent_directory <= you run the command in this directory\n└── appwrite\n └── docker-compose.yml\n```\n\nThe parent directory is where you will find the appwrite directory, inside which there are `docker-compose.yml` and `.env` files.\n\n{% info title=\"Parent directory naming\" %}\nYour Appwrite installation's parent directory name is expected to be `appwrite`. Changing the directory name will result in mismatched Docker project names.\n{% /info %}\n\n{% info title=\"Choose an image tag\" %}\nReplace `` below with the specific Appwrite image tag you intend to run (for example, `1.7.4`). Avoid using `latest` in production.\n{% /info %}\n\n## Unix\n\n```sh\ndocker run -it --rm \\\n --publish 20080:20080 \\\n --volume /var/run/docker.sock:/var/run/docker.sock \\\n --volume \"$(pwd)\"/appwrite:/usr/src/code/appwrite:rw \\\n --entrypoint=\"upgrade\" \\\n appwrite/appwrite:\n```\n\n## CMD\n\n```cmd\ndocker run -it --rm ^\n --publish 20080:20080 ^\n --volume //var/run/docker.sock:/var/run/docker.sock ^\n --volume \"%cd%\"/appwrite:/usr/src/code/appwrite:rw ^\n --entrypoint=\"upgrade\" ^\n appwrite/appwrite:\n```\n\n## PowerShell\n\n```powershell\ndocker run -it --rm `\n --publish 20080:20080 `\n --volume /var/run/docker.sock:/var/run/docker.sock `\n --volume ${pwd}/appwrite:/usr/src/code/appwrite:rw `\n --entrypoint=\"upgrade\" `\n appwrite/appwrite:\n```\n\nThis will pull the `docker-compose.yml` for the selected version/tag and perform the upgrade steps.\nOnce the setup completes, verify that you have the latest version of Appwrite.\n\n```sh\ndocker ps | grep appwrite/appwrite\n```\n\nVerify that the `STATUS` doesn't have any errors and all the `appwrite/appwrite` containers have the same version.\n\n# Running the Migration {% #running-the-migration %}\n\nWe can now start the migration. Navigate to the `appwrite` directory where your `docker-compose.yml` is present and run the following command.\n\n```sh\ncd appwrite/\ndocker compose exec appwrite migrate\n```\n\nThe 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.\n\nOnce the migration process has been completed successfully, you're all set to use the latest version of Appwrite!"}, {"path": "docs/apis/(overview)", "title": "APIs", "description": "Explore the ways to talk to Appwrite. Access every service through the REST and GraphQL APIs, subscribe to changes in Realtime, and react to events with webhooks.", "content": "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).\n\nMost 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.\n\n# Protocols {% #protocols %}\n\nChoose the protocol that fits your use case. REST and GraphQL cover the same endpoints, while Realtime delivers updates as they happen.\n\n{% cards %}\n{% cards_item href=\"/docs/apis/rest\" title=\"REST\" %}\nAccess every Appwrite endpoint over HTTP without an SDK, including authentication, file uploads, queries, and permissions.\n{% /cards_item %}\n{% cards_item href=\"/docs/apis/graphql\" title=\"GraphQL\" %}\nQuery and mutate your data through a single GraphQL endpoint.\n{% /cards_item %}\n{% cards_item href=\"/docs/apis/realtime\" title=\"Realtime\" %}\nSubscribe to channels and receive updates over WebSockets the moment your data changes.\n{% /cards_item %}\n{% /cards %}\n\n# Event-driven workflows {% #event-driven-workflows %}\n\nReact to project changes with events emitted by Appwrite and webhooks delivered to your own endpoints.\n\n{% cards %}\n{% cards_item href=\"/docs/apis/events\" title=\"Events\" %}\nThe full list of events Appwrite emits so you can react to changes across your project.\n{% /cards_item %}\n{% cards_item href=\"/docs/apis/webhooks\" title=\"Webhooks\" %}\nTrigger external workflows by delivering events to your own HTTP endpoints.\n{% /cards_item %}\n{% /cards %}\n\n# API responses {% #api-responses %}\n\nUnderstand the responses Appwrite returns and handle errors so your application can recover gracefully.\n\n{% cards %}\n{% cards_item href=\"/docs/apis/response-codes\" title=\"Response codes\" %}\nHTTP status codes, error types, and patterns for handling errors gracefully in your application.\n{% /cards_item %}\n{% /cards %}"}, {"path": "docs/apis/events", "title": "Events", "description": "Harness the power of events in Appwrite. Explore event-driven architecture, event types, and how to use events to create dynamic applications.", "content": "Appwrite provides a variety of events that allows your application to react to changes as they happen.\nAn 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.\nYou can subscribe to these events with Appwrite [Functions](/docs/products/functions), [Realtime](/docs/apis/realtime), or [Webhooks](/docs/apis/webhooks).\n\nYou 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.\nYou can also filter for events of specific actions like create, update, upsert, or delete.\n\n\nYou can find a list of events for Storage, Databases, Functions, Sites, and Authentication services below.\n\n{% accordion %}\n{% accordion_item title=\"Authentication\" %}\n{% partial file=\"auth-events.md\" /%}\n{% /accordion_item %}\n{% accordion_item title=\"Databases\" %}\n{% partial file=\"databases-events.md\" /%}\n\n{% info title=\"Note\" %}\nCollections, documents, and attributes have been deprecated in favour of tables, rows, and columns. The `databases.*` event prefix has been replaced by `tablesdb.*`.\n{% /info %}\n{% /accordion_item %}\n{% accordion_item title=\"Storage\" %}\n{% partial file=\"storage-events.md\" /%}\n{% /accordion_item %}\n{% accordion_item title=\"Functions\" %}\n{% partial file=\"functions-events.md\" /%}\n{% /accordion_item %}\n{% accordion_item title=\"Sites\" %}\n{% partial file=\"sites-events.md\" /%}\n{% /accordion_item %}\n{% accordion_item title=\"Messaging\" %}\n{% partial file=\"messaging-events.md\" /%}\n{% /accordion_item %}\n{% /accordion %}\n\n# Known limitations {% #known-limitations %}\n\nWhen 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.\n\nFor self-hosted instances, when the Appwrite containers are shut down and restarted, events with pending webhooks and subscription updates will not be delivered.\n\nA 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.\n\n```json\n{\n \"events\": [\n \"tablesdb.default.tables.movies.rows.lion-king.create\",\n \"tablesdb.*.tables.*.rows.*.create\",\n \"tablesdb.default.tables.*.rows.lion-king.create\",\n \"tablesdb.*.tables.*.rows.lion-king.create\",\n \"tablesdb.*.tables.movies.rows.lion-king.create\",\n \"tablesdb.default.tables.movies.rows.*.create\",\n \"tablesdb.*.tables.movies.rows.*.create\",\n \"tablesdb.default.tables.*.rows.*.create\",\n \"tablesdb.default.tables.movies.rows.lion-king\",\n \"tablesdb.*.tables.*.rows.*\",\n \"tablesdb.default.tables.*.rows.lion-king\",\n \"tablesdb.*.tables.*.rows.lion-king\",\n \"tablesdb.*.tables.movies.rows.lion-king\",\n \"tablesdb.default.tables.movies.rows.*\",\n \"tablesdb.*.tables.movies.rows.*\",\n \"tablesdb.default.tables.*.rows.*\",\n \"tablesdb.default.tables.movies\",\n \"tablesdb.*.tables.*\",\n \"tablesdb.default.tables.*\",\n \"tablesdb.*.tables.movies\",\n \"tablesdb.default\",\n \"tablesdb.*\"\n ]\n}\n```"}, {"path": "docs/apis/graphql", "title": "GraphQL", "description": "Get to know Appwrite GraphQL API for flexible data querying & manipulation. Our docs cover the schema, queries, mutations, integration tips and more.", "content": "Appwrite supports multiple protocols for accessing the platform, including [REST](/docs/apis/rest), [GraphQL](/docs/apis/graphql), and [Realtime](/docs/apis/realtime).\n\nThe GraphQL API allows you to query and mutate any resource type on the Appwrite platform through the endpoint `/v1/graphql`.\nEvery endpoint available through REST is available through GraphQL, except for OAuth.\n\n# Requests {% #requests %}\n\nAlthough every query executes through the same endpoint, there are multiple ways to make a GraphQL request. All requests, however, share a common structure.\n\n| Name | Type | Description |\n|----------------|--------|---------------------------------------------------------------------------|\n| query | string | **Required**, the GraphQL query to execute. |\n| operationName | string | **Optional**, if the query contains several named operations, controls which one to execute. |\n| variables | object | **Optional**, an object containing variable names and values for the query. Variables are made available to your query with the `$` prefix. |\n\n## GraphQL model parameters {% #graphql-model-parameters %}\n\nIn Appwrite's GraphQL API, all internal model parameters are prefixed with `_` instead of `$` because `$` is reserved by GraphQL.\n\nFor example, `$tableId` in the REST API would be referenced as `_tableId` in the GraphQL API.\n\n## GET requests {% #get-resquest %}\n\nYou can execute a GraphQL query via a GET request, passing a `query` and optionally `operationName` and `variables` as query parameters.\n\n## POST requests {% #post-request %}\n\nThere are multiple ways to make a GraphQL POST request, differentiated by content type.\n\n{% tabs %}\n{% tabsitem #json title=\"JSON\" %}\n\nThere are two ways to make requests with the `application/json` content type.\nYou can send a JSON object containing a `query` and optionally `operationName` and `variables`, or an array of objects with the same structure.\n\n### Object\n\n```json\n{\n \"query\": \"\",\n \"operationName\": \"\",\n \"variables\": {}\n}\n```\n\n### Array\n\n```json\n[\n {\n \"query\": \"\",\n \"operationName\": \"\",\n \"variables\": {}\n }\n]\n```\n{% /tabsitem %}\n\n{% tabsitem #graphql title=\"GraphQL\" %}\nThe `application/graphql` content type can be used to send a query as the raw POST body.\n\n```graphql\nquery GetAccount {\n accountGet {\n _id\n email\n }\n}\n```\n{% /tabsitem %}\n{% /tabs %}\n\n\n## Multipart form data {% #multipart-form-data %}\n\nThe `multipart/form-data` content type can be used to upload files via GraphQL.\nIn this case, the form data must include the following parts in addition to the files to upload.\n\n| Name | Type | Description |\n|-------------|--------|---------------------------------------------------------------------------------------------------------------------------|\n| operations |string | **Required**, JSON encoded GraphQL query and optionally operation name and variables. File variables should contain null values. |\n| map | string | **Required**, JSON encoded map of form-data filenames to the operations dot-path to inject the file to, e.g. `variables.file`. |\n\n# Responses {% #responses %}\n\nA response to a GraphQL request will have the following structure:\n\n| Name | Type | Description |\n|--------|----------|--------------------------------------------------------------------------------|\n| data | object | The data returned by the query, maps requested field names to their results. |\n| errors | object[] | An array of errors that occurred during the request. |\n\nThe data object will contain a map of requested field names to their results.\nIf no data is returned, the object will not be present in the response.\n\nThe errors array will contain error objects, each with their own **message** and **path**.\nThe path will contain the field key that is null due to the error.\nIf no errors occur, the array will not be present in the response.\n\n# Authentication {% #authentication %}\n\nGraphQL authenticates using Appwrite accounts and sessions.\nBoth accounts and sessions can be created with GraphQL using the `accountCreate`, `accountCreateEmailPasswordSession`,\n`accountCreateAnonymousSession`, or `accountCreatePhoneToken` mutations.\n\nMore information and examples of authenticating users can be found in the dedicated [authentication guide](/docs/products/auth).\n\n# Database queries {% #database-queries %}\n\nThe 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).\n\n# GraphQL vs REST {% #graphql-vs-rest %}\n\nThere are two main features that make GraphQL appealing when compared to the REST API: **selection sets** and **query batching**.\n\n## Selection sets {% #selection-sets %}\n\nSelection sets can be used to tell a GraphQL API exactly which fields of a particular resource you would like to receive in the response.\nThe server will respond with only those fields, nothing more, nothing less. This gives you full control over what data comes into your application.\n\nFor example, to retrieve only the email of a currently authenticated user, you could query the `accountGet` field,\npassing only email as the **field selection set**.\n\n```graphql\nquery GetAccount {\n accountGet {\n _id\n email\n }\n}\n```\n\nGiven this query, the GraphQL API will respond with:\n\n```json\n{\n \"data\": {\n \"accountGet\": {\n \"_id\": \"...\",\n \"email\": \"...\"\n }\n }\n}\n```\n\nThis can be a useful feature for performance, network efficiency, and app responsiveness.\nAs the processing happens on the server, the bandwidth consumed for the request can be dramatically reduced.\n\n# Query batching {% #query-batching %}\n\nGraphQL allows sending multiple queries or mutations in the same request.\nThere are two different ways to batch queries. The simplest way is to include multiple fields in a single query **or** mutation.\n\n```graphql\nquery GetAccountAndLocale {\n accountGet {\n _id\n email\n }\n localeGet {\n ip\n }\n}\n```\n\nIf both field executions succeed, the response will contain a data key for each field, containing the values of the selected fields.\n\n```json\n{\n \"data\": {\n \"accountGet\": {\n \"_id\": \"...\",\n \"email\": \"...\"\n },\n \"localeGet\": {\n \"ip\": \"...\"\n }\n }\n}\n```\n\nIf there was no authenticated user, the `accountGet` field would fail to resolve.\nIn 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.\n\n```json\n{\n \"data\": {\n \"accountGet\": null,\n \"localeGet\": {\n \"ip\": \"...\",\n \"country\": \"...\"\n }\n },\n \"errors\": [\n {\n \"message\": \"User (role: guest) missing scope (account)\",\n \"path\": [\"accountGet\"]\n }\n ]\n}\n```\n\nBatching with a single query or mutation has some down-sides.\nYou cannot mix and match queries and mutations within the same request unless you provide an operationName,\nin which case you can only execute one query per request.\n\nAdditionally, all **variables** must be passed in the same object, which can be cumbersome and hard to maintain.\n\nThe second way to batch is to pass an array of queries or mutations in the request.\nIn this way, you can execute queries **and** mutations and keep variables separated for each.\n\n```json\n[\n {\n \"query\": \"query GetAccount { accountGet{ email } }\",\n },\n {\n \"query\": \"query GetLocale { localeGet { ip } }\"\n }\n]\n```\n\nThis allows you to execute complex actions in a single network request.\n\n# SDK usage {% #sdk-usage %}\n\nAppwrite SDKs also support GraphQL in addition to the REST services.\n\n{% multicode %}\n```client-web\nimport { Client, Graphql } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your Appwrite Endpoint\n .setProject(''); // Your project ID\n\nconst graphql = new Graphql(client);\n\nconst mutation = graphql.mutation({\n query: `mutation CreateAccount(\n $email: String!,\n $password: String!,\n $name: String\n ) {\n accountCreate(\n email: $email,\n password: $password,\n name: $name,\n userId: \"unique()\"\n ) {\n _id\n }\n }`,\n variables: {\n email: '...',\n password: '...',\n name: '...'\n }\n});\n\nmutation.then(response => {\n console.log(response);\n}).catch(error => {\n console.log(error);\n});\n```\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your Appwrite Endpoint\n .setProject(''); // Your project ID\n\nfinal graphql = Graphql(client);\n\nFuture mutation = graphql.mutation({\n 'query': '''mutation CreateAccount(\n \\$email: String!,\n \\$password: String!,\n \\$name: String\n ) {\n accountCreate(\n email: \\$email,\n password: \\$password,\n name: \\$name,\n userId: \"unique()\"\n ) {\n _id\n }\n }''',\n 'variables': {\n 'email': '...',\n 'password': '...',\n 'name': '...'\n }\n});\n\nmutation.then((response) {\n print(response);\n}).catchError((error) {\n print(error.message);\n});\n```\n```client-apple\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n\nlet graphql = Graphql(client)\n\ndo {\n let response = try await graphql.mutation([\n \"query\": \"\"\"\n mutation CreateAccount(\n $email: String!,\n $password: String!,\n $name: String\n ) {\n accountCreate(\n email: $email,\n password: $password,\n name: $name,\n userId: \"unique()\"\n\n ) {\n _id\n }\n }\n \"\"\",\n \"variables\": [\n \"email\": \"...\",\n \"password\": \"...\",\n \"name\": \"...\"\n ]\n ])\n\n print(String(describing: response))\n} catch {\n print(error.localizedDescription)\n}\n```\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Graphql\n\nval client = Client(context)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n\nval graphql = Graphql(client)\n\ntry {\n val response = graphql.mutation(mapOf(\n \"query\" to \"\"\"mutation CreateAccount(\n ${'$'}email: String!,\n ${'$'}password: String!,\n ${'$'}name: String\n ) {\n accountCreate(\n email: ${'$'}email,\n password: ${'$'}password,\n name: ${'$'}name,\n userId: \"unique()\"\n ) {\n _id\n }\n }\"\"\",\n \"variables\" to mapOf(\n \"email\" to \"...\",\n \"password\" to \"...\",\n \"name\" to \"...\"\n )\n ))\n\n Log.d(javaClass.name, response)\n} catch (ex: AppwriteException) {\n ex.printStackTrace()\n}\n```\n{% /multicode %}"}, {"path": "docs/apis/realtime", "title": "Realtime", "description": "Want to build dynamic and interactive applications with real-time data updates? Appwrite Realtime API makes it possible, get started with our intro guide.", "content": "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.\n\nInstead 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.\n\nThis 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.\n\n{% multicode %}\n```client-web\nimport { Client, Realtime, Channel } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nconst realtime = new Realtime(client);\n\n// Subscribe to files channel\nconst subscription = await realtime.subscribe(Channel.files(), response => {\n if(response.events.includes('buckets.*.files.*.create')) {\n // Log when a new file is uploaded\n console.log(response.payload);\n }\n});\n```\n\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nfinal realtime = Realtime(client);\n\n// Subscribe to files channel\nfinal subscription = realtime.subscribe([Channel.files()]);\n\nsubscription.stream.listen((response) {\n if(response.events.contains('buckets.*.files.*.create')) {\n // Log when a new file is uploaded\n print(response.payload);\n }\n});\n```\n\n```client-apple\nimport Appwrite\nimport AppwriteModels\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nlet realtime = Realtime(client)\n\n// Subscribe to files channel\nlet subscription = realtime.subscribe(channels: [Channel.files()]) { response in\n if (response.events!.contains(\"buckets.*.files.*.create\")) {\n // Log when a new file is uploaded\n print(String(describing: response))\n }\n}\n```\n\n```client-android-kotlin\nimport io.appwrite.Channel\nimport io.appwrite.Client\nimport io.appwrite.services.Realtime\n\nval client = Client(context)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nval realtime = Realtime(client)\n\n// Subscribe to files channel\nval subscription = realtime.subscribe(Channel.files()) {\n if(it.events.contains(\"buckets.*.files.*.create\")) {\n // Log when a new file is uploaded\n print(it.payload.toString());\n }\n}\n```\n\n```client-android-java\nimport io.appwrite.Client;\nimport io.appwrite.models.RealtimeResponseEvent;\nimport io.appwrite.services.Realtime;\nimport kotlin.Unit;\n\nClient client = new Client(context)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\");\n\nRealtime realtime = new Realtime(client);\n\n// Subscribe to files channel\nrealtime.subscribe(new String[] {\"files\"}, (RealtimeResponseEvent response) -> {\n if (response.getEvents().contains(\"buckets.*.files.*.create\")) {\n // Log when a new file is uploaded\n System.out.println(response.getPayload());\n }\n return Unit.INSTANCE;\n});\n```\n\n{% /multicode %}\n\nTo 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.\n\nIf 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.\n\n[View a list of all available events](/docs/apis/events).\n\n{% info title=\"Permissions\" %}\nAll 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.\n\nUsing `Role.any()` on read permissions will allow any client to receive updates.\n{% /info %}\n\n# Limitations {% #limitations %}\n\nWhile the Realtime API offers robust capabilities, there are currently some limitations to keep in mind.\n\n## Subscription changes {% #subscription-changes %}\n\nClient 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.\n\nManage 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.\n\n## Server SDKs {% #server-sdks %}\n\nWe currently are not offering access to realtime with Server SDKs and an API key."}, {"path": "docs/apis/realtime/authentication", "title": "Authentication", "description": "Learn how authentication works with Appwrite Realtime subscriptions and how to handle session-based access.", "content": "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.\n\nMore information and examples of authenticating users can be found in the dedicated [authentication docs](/docs/products/auth).\n\n{% info title=\"Permissions\" %}\nAll 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.\n\nUsing `Role.any()` on read permissions will allow any client to receive updates.\n{% /info %}\n\n# Session lifecycle {% #session-lifecycle %}\n\nWhen working with Realtime subscriptions and authentication, keep the following in mind:\n\n1. **Create session first** - Always authenticate the user before creating subscriptions that require access to protected resources.\n2. **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.\n3. **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."}, {"path": "docs/apis/realtime/channels", "title": "Channels", "description": "Explore the available Realtime channels and learn how to use Channel helpers for type-safe subscriptions in Appwrite.", "content": "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.\n\n# Channel helpers {% #channel-helpers %}\n\nInstead 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.\n\n{% multicode %}\n```client-web\nimport { Client, Realtime, Channel } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nconst realtime = new Realtime(client);\n\n// Subscribe to account channel\nconst subscription = await realtime.subscribe(Channel.account(), response => {\n console.log(response);\n});\n\n// Subscribe to a specific row\nconst rowSubscription = await realtime.subscribe(\n Channel.tablesdb('').table('').row(''),\n response => {\n console.log(response);\n }\n);\n```\n\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nfinal realtime = Realtime(client);\n\n// Subscribe to account channel\nfinal subscription = realtime.subscribe([Channel.account()]);\n\n// Subscribe to a specific row\nfinal docSubscription = realtime.subscribe([\n Channel.tablesdb('').table('').row('')\n]);\n```\n\n```client-apple\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nlet realtime = Realtime(client)\n\n// Subscribe to account channel\nlet subscription = realtime.subscribe(channels: [Channel.account()]) { response in\n print(String(describing: response))\n}\n\n// Subscribe to a specific row\nlet docSubscription = realtime.subscribe(\n channels: [Channel.tablesdb(\"\").table(\"\").row(\"\")]\n) { response in\n print(String(describing: response))\n}\n```\n\n```client-android-kotlin\nimport io.appwrite.Channel\nimport io.appwrite.Client\nimport io.appwrite.services.Realtime\n\nval client = Client(context)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nval realtime = Realtime(client)\n\n// Subscribe to account channel\nval subscription = realtime.subscribe(Channel.account()) {\n print(it.toString())\n}\n\n// Subscribe to a specific row\nval docSubscription = realtime.subscribe(\n Channel.tablesdb(\"\").table(\"\").row(\"\")\n) {\n print(it.toString())\n}\n```\n\n```client-android-java\nimport io.appwrite.Channel;\nimport io.appwrite.ChannelKt;\nimport io.appwrite.Client;\nimport io.appwrite.models.RealtimeResponseEvent;\nimport io.appwrite.models.RealtimeSubscription;\nimport io.appwrite.services.Realtime;\nimport kotlin.Unit;\n\nClient client = new Client(context)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\");\n\nRealtime realtime = new Realtime(client);\n\n// Subscribe to account channel\nString accountChannel = Channel.Companion.account();\nRealtimeSubscription subscription = realtime.subscribe(\n new String[] {accountChannel},\n (RealtimeResponseEvent response) -> {\n System.out.println(response);\n return Unit.INSTANCE;\n }\n);\n\n// Subscribe to a specific row\nChannel rowChannel = ChannelKt.row(\n ChannelKt.table(Channel.Companion.tablesdb(\"\"), \"\"),\n \"\"\n);\n\nRealtimeSubscription rowSubscription = realtime.subscribe(\n new Channel[] {rowChannel},\n (RealtimeResponseEvent response) -> {\n System.out.println(response);\n return Unit.INSTANCE;\n }\n);\n```\n\n{% /multicode %}\n\nThe `Channel` helper supports all available channels and allows you to:\n- Build channels with a fluent, chainable API\n- Optionally specify resource IDs (omit IDs to subscribe to all resources)\n- Add event filters like `.create()`, `.update()`, or `.delete()`\n\n# Available channels {% #available-channels %}\n\nA list of all channels available you can subscribe to. When using `Channel` helpers, leaving an ID blank will subscribe using `*`.\n\n{% table %}\n* Channel\n* Channel Helper\n* Description\n---\n* `account`\n* `Channel.account()`\n* All account related events (session create, name update...)\n---\n* `tablesdb..tables..rows`\n* `Channel.tablesdb('').table('').row()`\n* Any create/update/delete events to any row in a table\n---\n* `rows`\n* `Channel.rows()`\n* Any create/update/delete events to any row\n---\n* `tablesdb..tables..rows.`\n* `Channel.tablesdb('').table('').row('')`\n* Any update/delete events to a given row\n---\n* `files`\n* `Channel.files()`\n* Any create/update/delete events to any file\n---\n* `buckets..files.`\n* `Channel.bucket('').file('')`\n* Any update/delete events to a given file of the given bucket\n---\n* `buckets..files`\n* `Channel.bucket('').file()`\n* Any update/delete events to any file of the given bucket\n---\n* `teams.*`\n* `Channel.teams()`\n* Any create/update/delete events to any team\n---\n* `teams.`\n* `Channel.team('')`\n* Any update/delete events to a given team\n---\n* `memberships`\n* `Channel.memberships()`\n* Any create/update/delete events to any membership\n---\n* `memberships.`\n* `Channel.membership('')`\n* Any update/delete events to a given membership\n---\n* `executions`\n* `Channel.executions()`\n* Any update to executions\n---\n* `executions.`\n* `Channel.execution('')`\n* Any update to a given execution\n---\n* `functions.`\n* `Channel.function('')`\n* Any execution event to a given function\n---\n* `presences`\n* `Channel.presences()`\n* Any upsert, update, or delete event on any [presence](/docs/apis/realtime/presences) the subscriber can read.\n---\n* `presences.`\n* `Channel.presence('')`\n* Any upsert, update, or delete event on a given presence record.\n\n{% /table %}\n\n# Event filters {% #event-filters %}\n\nYou can also filter events by appending event methods to the channel helpers:\n- `.create()` - Listen only to create events\n- `.update()` - Listen only to update events\n- `.delete()` - Listen only to delete events\n\nFor example, `Channel.tablesdb('').table('').row('').update()` will only trigger on row updates."}, {"path": "docs/apis/realtime/custom-endpoint", "title": "Custom endpoint", "description": "Learn how to configure a custom WebSocket endpoint for the Appwrite Realtime API when using a custom proxy.", "content": "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.\n\nBy default the endpoint is `wss://.cloud.appwrite.io/v1/realtime`.\n\n{% multicode %}\n```client-web\nimport { Client } from \"appwrite\";\nconst client = new Client();\n\nclient.setEndpointRealtime('wss://.cloud.appwrite.io/v1/realtime');\n```\n\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nfinal client = Client();\nclient.setEndpointRealtime('wss://.cloud.appwrite.io/v1/realtime');\n```\n\n```client-apple\nimport Appwrite\n\nlet client = Client()\nclient.setEndpointRealtime(\"wss://.cloud.appwrite.io/v1/realtime\")\n```\n\n```client-android-kotlin\nimport io.appwrite.Client\n\nval client = Client(context)\nclient.setEndpointRealtime(\"wss://.cloud.appwrite.io/v1/realtime\")\n```\n\n```client-android-java\nimport io.appwrite.Client;\n\nClient client = new Client(context);\nclient.setEndpointRealtime(\"wss://.cloud.appwrite.io/v1/realtime\");\n```\n\n{% /multicode %}"}, {"path": "docs/apis/realtime/payload", "title": "Payload", "description": "Understand the structure of Appwrite Realtime subscription payloads and learn how to work with the response data.", "content": "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.\n\n# Response structure {% #response-structure %}\n\nThe payload from the subscription will contain the following properties:\n\n{% table %}\n* Name\n* Type\n* Description\n---\n* events\n* string[]\n* The [Appwrite events](/docs/apis/events) that triggered this update.\n---\n* channels\n* string[]\n* An array of [channels](/docs/apis/realtime/channels) that can receive this message.\n---\n* timestamp\n* string\n* The [ISO 8601 timestamp](https://en.wikipedia.org/wiki/ISO_8601) in UTC timezone from the server\n---\n* payload\n* object\n* Payload contains the data equal to the response model.\n{% /table %}\n\n# Example {% #example %}\n\nIf 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.\n\nThe response will look like this:\n\n```json\n{\n \"events\": [\n \"tablesdb.default.tables.sample.rows.63c98b9baea0938e1206.update\",\n \"tablesdb.*.tables.*.rows.*.update\",\n \"tablesdb.default.tables.*.rows.63c98b9baea0938e1206.update\",\n \"tablesdb.*.tables.*.rows.63c98b9baea0938e1206.update\",\n \"tablesdb.*.tables.sample.rows.63c98b9baea0938e1206.update\",\n \"tablesdb.default.tables.sample.rows.*.update\",\n \"tablesdb.*.tables.sample.rows.*.update\",\n \"tablesdb.default.tables.*.rows.*.update\",\n \"tablesdb.default.tables.sample.rows.63c98b9baea0938e1206\",\n \"tablesdb.*.tables.*.rows.*\",\n \"tablesdb.default.tables.*.rows.63c98b9baea0938e1206\",\n \"tablesdb.*.tables.*.rows.63c98b9baea0938e1206\",\n \"tablesdb.*.tables.sample.rows.63c98b9baea0938e1206\",\n \"tablesdb.default.tables.sample.rows.*\",\n \"tablesdb.*.tables.sample.rows.*\",\n \"tablesdb.default.tables.*.rows.*\",\n \"tablesdb.default.tables.sample\",\n \"tablesdb.*.tables.*\",\n \"tablesdb.default.tables.*\",\n \"tablesdb.*.tables.sample\",\n \"tablesdb.default\",\n \"tablesdb.*\"\n ],\n \"channels\": [\n \"rows\",\n \"tablesdb.default.tables.sample.rows\",\n \"tablesdb.default.tables.sample.rows.63c98b9baea0938e1206\"\n ],\n \"timestamp\": \"2023-01-19T18:30:04.051+00:00\",\n \"payload\": {\n \"ip\": \"127.0.0.1\",\n \"stringArray\": [\n \"sss\"\n ],\n \"email\": \"joe@example.com\",\n \"stringRequired\": \"req\",\n \"float\": 3.3,\n \"boolean\": false,\n \"integer\": 3,\n \"enum\": \"apple\",\n \"stringDefault\": \"default\",\n \"datetime\": \"2023-01-19T10:27:09.428+00:00\",\n \"url\": \"https://appwrite.io\",\n \"$id\": \"63c98b9baea0938e1206\",\n \"$createdAt\": \"2023-01-19T18:27:39.715+00:00\",\n \"$updatedAt\": \"2023-01-19T18:30:04.040+00:00\",\n \"$permissions\": [],\n \"$tableId\": \"sample\",\n \"$databaseId\": \"default\"\n }\n}\n```"}, {"path": "docs/apis/realtime/presences", "title": "Presences", "description": "Use the Appwrite Presences API to track which users are currently active, broadcast their status, and subscribe to live presence updates over Realtime.", "content": "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.\n\nA 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.\n\nPresences 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.\n\n# How it works {% #how-it-works %}\n\nA presence has two sides that are always in sync.\n\n**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.\n\n**It is live.** Every change to a presence fires an event on the `presences` and `presences.` [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.\n\nA typical \"online dot\" loop looks like this:\n\n1. Client A signs in and calls `presences.upsert({...})`. An `upsert` event fires on the presence channels.\n2. Client B, subscribed to `Channel.presences()`, receives the event and shows A as online.\n3. Client A keeps the record alive by upserting again on focus, route change, or a periodic timer, which slides `expiresAt` forward.\n4. When `expiresAt` passes, the record is removed and a `delete` event fires. B drops A from its list.\n5. If A signs out cleanly, they call `presences.delete(...)` and the `delete` event fires immediately, no waiting on expiry.\n\nThis gives you two ways to keep a presence alive, and you pick whichever fits your UI:\n\n- **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.\n- **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.\n\nThe `realtime.upsertPresence(...)` call mirrors the REST `presences.upsert(...)` signature, but the record's lifetime is tied to the WebSocket rather than to `expiresAt`:\n\n{% multicode %}\n```client-web\nimport { Client, Realtime, ID, Permission, Role } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nconst realtime = new Realtime(client);\n\nawait realtime.upsertPresence({\n presenceId: ID.unique(),\n status: 'online',\n permissions: [\n Permission.read(Role.users())\n ]\n});\n```\n\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nfinal realtime = Realtime(client);\n\nawait realtime.upsertPresence(\n presenceId: ID.unique(),\n status: 'online',\n permissions: [\n Permission.read(Role.users()),\n ],\n);\n```\n\n```client-apple\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nlet realtime = Realtime(client)\n\ntry await realtime.upsertPresence(\n presenceId: ID.unique(),\n status: \"online\",\n permissions: [\n Permission.read(Role.users())\n ]\n)\n```\n\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.ID\nimport io.appwrite.Permission\nimport io.appwrite.Role\nimport io.appwrite.services.Realtime\n\nval client = Client(context)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nval realtime = Realtime(client)\n\nrealtime.upsertPresence(\n presenceId = ID.unique(),\n status = \"online\",\n permissions = listOf(\n Permission.read(Role.users())\n )\n)\n```\n{% /multicode %}\n\nThe 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).\n\n# Upsert a presence {% #upsert-a-presence %}\n\n`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/advanced/security/api-keys) with the `presences.write` scope.\n\n{% multicode %}\n```client-web\nimport { Client, Presences, ID, Permission, Role } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nconst presences = new Presences(client);\n\nconst presence = await presences.upsert({\n presenceId: ID.unique(),\n status: 'online',\n metadata: { page: '/dashboard' },\n permissions: [\n Permission.read(Role.users())\n ]\n});\n```\n\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nfinal presences = Presences(client);\n\nfinal presence = await presences.upsert(\n presenceId: ID.unique(),\n status: 'online',\n metadata: { 'page': '/dashboard' },\n permissions: [\n Permission.read(Role.users()),\n ],\n);\n```\n\n```client-apple\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nlet presences = Presences(client)\n\nlet presence = try await presences.upsert(\n presenceId: ID.unique(),\n status: \"online\",\n metadata: [\"page\": \"/dashboard\"],\n permissions: [\n Permission.read(Role.users())\n ]\n)\n```\n\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.ID\nimport io.appwrite.Permission\nimport io.appwrite.Role\nimport io.appwrite.services.Presences\n\nval client = Client(context)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nval presences = Presences(client)\n\nval presence = presences.upsert(\n presenceId = ID.unique(),\n status = \"online\",\n metadata = mapOf(\"page\" to \"/dashboard\"),\n permissions = listOf(\n Permission.read(Role.users())\n )\n)\n```\n\n```server-nodejs\nconst sdk = require('node-appwrite');\nconst { Permission, Role } = sdk;\n\nconst client = new sdk.Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nconst presences = new sdk.Presences(client);\n\nconst presence = await presences.upsert({\n presenceId: '',\n userId: '',\n status: 'online',\n permissions: [\n Permission.read(Role.users())\n ]\n});\n```\n\n```server-python\nfrom appwrite.client import Client\nfrom appwrite.services.presences import Presences\nfrom appwrite.permission import Permission\nfrom appwrite.role import Role\n\nclient = Client()\nclient.set_endpoint('https://.cloud.appwrite.io/v1')\nclient.set_project('')\nclient.set_key('')\n\npresences = Presences(client)\n\npresence = presences.upsert(\n presence_id = '',\n user_id = '',\n status = 'online',\n permissions = [\n Permission.read(Role.users())\n ]\n)\n```\n\n```server-php\nsetEndpoint('https://.cloud.appwrite.io/v1')\n ->setProject('')\n ->setKey('');\n\n$presences = new Presences($client);\n\n$presence = $presences->upsert(\n presenceId: '',\n userId: '',\n status: 'online',\n permissions: [\n Permission::read(Role::users())\n ]\n);\n```\n\n```server-ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://.cloud.appwrite.io/v1')\n .set_project('')\n .set_key('')\n\npresences = Presences.new(client)\n\npresence = presences.upsert(\n presence_id: '',\n user_id: '',\n status: 'online',\n permissions: [\n Permission.read(Role.users())\n ]\n)\n```\n\n```server-dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\n\nClient client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nPresences presences = Presences(client);\n\nPresence presence = await presences.upsert(\n presenceId: '',\n userId: '',\n status: 'online',\n permissions: [\n Permission.read(Role.users()),\n ],\n);\n```\n\n```server-kotlin\nimport io.appwrite.Client\nimport io.appwrite.Permission\nimport io.appwrite.Role\nimport io.appwrite.services.Presences\n\nval client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\")\n\nval presences = Presences(client)\n\nval presence = presences.upsert(\n presenceId = \"\",\n userId = \"\",\n status = \"online\",\n permissions = listOf(\n Permission.read(Role.users())\n )\n)\n```\n\n```server-java\nimport io.appwrite.Client;\nimport io.appwrite.Permission;\nimport io.appwrite.Role;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Presences;\n\nClient client = new Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\");\n\nPresences presences = new Presences(client);\n\npresences.upsert(\n \"\", // presenceId\n \"\", // userId\n \"online\", // status\n null, // metadata (optional)\n null, // expiresAt (optional)\n List.of(Permission.read(Role.users())), // permissions (optional)\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n System.out.println(result);\n })\n);\n```\n\n```server-swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\")\n\nlet presences = Presences(client)\n\nlet presence = try await presences.upsert(\n presenceId: \"\",\n userId: \"\",\n status: \"online\",\n permissions: [\n Permission.read(Role.users())\n ]\n)\n```\n\n```server-dotnet\nusing Appwrite;\nusing Appwrite.Models;\nusing Appwrite.Services;\n\nClient client = new Client()\n .SetEndPoint(\"https://.cloud.appwrite.io/v1\")\n .SetProject(\"\")\n .SetKey(\"\");\n\nPresences presences = new Presences(client);\n\nPresence presence = await presences.Upsert(\n presenceId: \"\",\n userId: \"\",\n status: \"online\",\n permissions: new List {\n Permission.Read(Role.Users())\n }\n);\n```\n\n```server-go\npackage main\n\nimport (\n \"fmt\"\n \"github.com/appwrite/sdk-for-go/client\"\n \"github.com/appwrite/sdk-for-go/presences\"\n)\n\nfunc main() {\n cli := client.New(\n client.WithEndpoint(\"https://.cloud.appwrite.io/v1\"),\n client.WithProject(\"\"),\n client.WithKey(\"\"),\n )\n\n service := presences.New(cli)\n\n presence, err := service.Upsert(\n \"\",\n \"\",\n \"online\",\n presences.WithUpsertPermissions([]string{`read(\"users\")`}),\n )\n if err != nil {\n panic(err)\n }\n fmt.Println(presence)\n}\n```\n\n```server-rust\nuse appwrite::Client;\nuse appwrite::permission::Permission;\nuse appwrite::role::Role;\nuse appwrite::services::Presences;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box> {\n let client = Client::new()\n .set_endpoint(\"https://.cloud.appwrite.io/v1\")\n .set_project(\"\")\n .set_key(\"\");\n\n let presences = Presences::new(&client);\n\n let presence = presences.upsert(\n \"\",\n \"\",\n \"online\",\n Some(vec![Permission::read(Role::users(None)).to_string()]),\n None,\n None,\n ).await?;\n\n println!(\"{:?}\", presence);\n\n Ok(())\n}\n```\n{% /multicode %}\n\nA few notes on the parameters:\n\n- `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.\n- `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`).\n- `userId` is set automatically from the authenticated session on client SDKs and is required on server SDKs.\n- `metadata` is an arbitrary JSON object. Use it to carry any context that subscribers should see together with the status.\n- `expiresAt` is optional. Without it, Appwrite applies a default TTL (see [Expiry and cleanup](#expiry-and-cleanup) below).\n- `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.\n\n# Get a presence {% #get-a-presence %}\n\nFetch a single presence by its `presenceId`. Records whose `expiresAt` is in the past are treated as not found.\n\n{% multicode %}\n```client-web\nimport { Client, Presences } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nconst presences = new Presences(client);\n\nconst presence = await presences.get({\n presenceId: ''\n});\n```\n\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nfinal presences = Presences(client);\n\nfinal presence = await presences.get(\n presenceId: '',\n);\n```\n\n```client-apple\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nlet presences = Presences(client)\n\nlet presence = try await presences.get(\n presenceId: \"\"\n)\n```\n\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Presences\n\nval client = Client(context)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nval presences = Presences(client)\n\nval presence = presences.get(\n presenceId = \"\"\n)\n```\n\n```server-nodejs\nconst sdk = require('node-appwrite');\n\nconst client = new sdk.Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nconst presences = new sdk.Presences(client);\n\nconst presence = await presences.get({\n presenceId: ''\n});\n```\n\n```server-python\nfrom appwrite.client import Client\nfrom appwrite.services.presences import Presences\n\nclient = Client()\nclient.set_endpoint('https://.cloud.appwrite.io/v1')\nclient.set_project('')\nclient.set_key('')\n\npresences = Presences(client)\n\npresence = presences.get(\n presence_id = ''\n)\n```\n\n```server-php\nsetEndpoint('https://.cloud.appwrite.io/v1')\n ->setProject('')\n ->setKey('');\n\n$presences = new Presences($client);\n\n$presence = $presences->get(\n presenceId: ''\n);\n```\n\n```server-ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://.cloud.appwrite.io/v1')\n .set_project('')\n .set_key('')\n\npresences = Presences.new(client)\n\npresence = presences.get(\n presence_id: ''\n)\n```\n\n```server-dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\n\nClient client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nPresences presences = Presences(client);\n\nPresence presence = await presences.get(\n presenceId: '',\n);\n```\n\n```server-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Presences\n\nval client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\")\n\nval presences = Presences(client)\n\nval presence = presences.get(\n presenceId = \"\"\n)\n```\n\n```server-java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Presences;\n\nClient client = new Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\");\n\nPresences presences = new Presences(client);\n\npresences.get(\n \"\", // presenceId\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n System.out.println(result);\n })\n);\n```\n\n```server-swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\")\n\nlet presences = Presences(client)\n\nlet presence = try await presences.get(\n presenceId: \"\"\n)\n```\n\n```server-dotnet\nusing Appwrite;\nusing Appwrite.Models;\nusing Appwrite.Services;\n\nClient client = new Client()\n .SetEndPoint(\"https://.cloud.appwrite.io/v1\")\n .SetProject(\"\")\n .SetKey(\"\");\n\nPresences presences = new Presences(client);\n\nPresence presence = await presences.Get(\n presenceId: \"\"\n);\n```\n\n```server-go\npackage main\n\nimport (\n \"fmt\"\n \"github.com/appwrite/sdk-for-go/client\"\n \"github.com/appwrite/sdk-for-go/presences\"\n)\n\nfunc main() {\n cli := client.New(\n client.WithEndpoint(\"https://.cloud.appwrite.io/v1\"),\n client.WithProject(\"\"),\n client.WithKey(\"\"),\n )\n\n service := presences.New(cli)\n\n presence, err := service.Get(\"\")\n if err != nil {\n panic(err)\n }\n fmt.Println(presence)\n}\n```\n\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::Presences;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box> {\n let client = Client::new()\n .set_endpoint(\"https://.cloud.appwrite.io/v1\")\n .set_project(\"\")\n .set_key(\"\");\n\n let presences = Presences::new(&client);\n\n let presence = presences.get(\"\").await?;\n\n println!(\"{:?}\", presence);\n\n Ok(())\n}\n```\n{% /multicode %}\n\n# List presences {% #list-presences %}\n\n`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.\n\n{% multicode %}\n```client-web\nimport { Client, Presences, Query } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nconst presences = new Presences(client);\n\nconst result = await presences.list({\n queries: [Query.equal('status', ['online'])]\n});\n```\n\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nfinal presences = Presences(client);\n\nfinal result = await presences.list(\n queries: [Query.equal('status', ['online'])],\n);\n```\n\n```client-apple\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nlet presences = Presences(client)\n\nlet result = try await presences.list(\n queries: [Query.equal(\"status\", value: [\"online\"])]\n)\n```\n\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.Query\nimport io.appwrite.services.Presences\n\nval client = Client(context)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nval presences = Presences(client)\n\nval result = presences.list(\n queries = listOf(Query.equal(\"status\", listOf(\"online\")))\n)\n```\n\n```server-nodejs\nconst sdk = require('node-appwrite');\n\nconst client = new sdk.Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nconst presences = new sdk.Presences(client);\n\nconst result = await presences.list({\n queries: [sdk.Query.equal('status', ['online'])]\n});\n```\n\n```server-python\nfrom appwrite.client import Client\nfrom appwrite.services.presences import Presences\nfrom appwrite.query import Query\n\nclient = Client()\nclient.set_endpoint('https://.cloud.appwrite.io/v1')\nclient.set_project('')\nclient.set_key('')\n\npresences = Presences(client)\n\nresult = presences.list(\n queries = [Query.equal('status', ['online'])]\n)\n```\n\n```server-php\nsetEndpoint('https://.cloud.appwrite.io/v1')\n ->setProject('')\n ->setKey('');\n\n$presences = new Presences($client);\n\n$result = $presences->list(\n queries: [Query::equal('status', ['online'])]\n);\n```\n\n```server-ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://.cloud.appwrite.io/v1')\n .set_project('')\n .set_key('')\n\npresences = Presences.new(client)\n\nresult = presences.list(\n queries: [Query.equal('status', ['online'])]\n)\n```\n\n```server-dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\n\nClient client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nPresences presences = Presences(client);\n\nPresenceList result = await presences.list(\n queries: [Query.equal('status', ['online'])],\n);\n```\n\n```server-kotlin\nimport io.appwrite.Client\nimport io.appwrite.Query\nimport io.appwrite.services.Presences\n\nval client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\")\n\nval presences = Presences(client)\n\nval response = presences.list(\n queries = listOf(Query.equal(\"status\", listOf(\"online\")))\n)\n```\n\n```server-java\nimport io.appwrite.Client;\nimport io.appwrite.Query;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Presences;\n\nimport java.util.List;\n\nClient client = new Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\");\n\nPresences presences = new Presences(client);\n\npresences.list(\n List.of(Query.equal(\"status\", List.of(\"online\"))), // queries\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n System.out.println(result);\n })\n);\n```\n\n```server-swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\")\n\nlet presences = Presences(client)\n\nlet response = try await presences.list(\n queries: [Query.equal(\"status\", value: [\"online\"])]\n)\n```\n\n```server-dotnet\nusing Appwrite;\nusing Appwrite.Models;\nusing Appwrite.Services;\n\nClient client = new Client()\n .SetEndPoint(\"https://.cloud.appwrite.io/v1\")\n .SetProject(\"\")\n .SetKey(\"\");\n\nPresences presences = new Presences(client);\n\nPresenceList result = await presences.List(\n queries: new List { Query.Equal(\"status\", new List { \"online\" }) }\n);\n```\n\n```server-go\npackage main\n\nimport (\n \"fmt\"\n \"github.com/appwrite/sdk-for-go/client\"\n \"github.com/appwrite/sdk-for-go/presences\"\n \"github.com/appwrite/sdk-for-go/query\"\n)\n\nfunc main() {\n cli := client.New(\n client.WithEndpoint(\"https://.cloud.appwrite.io/v1\"),\n client.WithProject(\"\"),\n client.WithKey(\"\"),\n )\n\n service := presences.New(cli)\n\n result, err := service.List(\n service.WithListQueries([]string{\n query.Equal(\"status\", \"online\"),\n }),\n )\n if err != nil {\n panic(err)\n }\n fmt.Println(result)\n}\n```\n\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::Presences;\nuse appwrite::query::Query;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box> {\n let client = Client::new()\n .set_endpoint(\"https://.cloud.appwrite.io/v1\")\n .set_project(\"\")\n .set_key(\"\");\n\n let presences = Presences::new(&client);\n\n let result = presences.list(\n Some(vec![Query::equal(\"status\", vec![\"online\".to_string()]).to_string()]),\n None,\n None,\n ).await?;\n\n println!(\"{:?}\", result);\n\n Ok(())\n}\n```\n{% /multicode %}\n\n# Update a presence {% #update-a-presence %}\n\n`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.\n\n{% multicode %}\n```client-web\nimport { Client, Presences } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nconst presences = new Presences(client);\n\nconst presence = await presences.update({\n presenceId: '',\n status: 'away'\n});\n```\n\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nfinal presences = Presences(client);\n\nfinal presence = await presences.update(\n presenceId: '',\n status: 'away',\n);\n```\n\n```client-apple\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nlet presences = Presences(client)\n\nlet presence = try await presences.update(\n presenceId: \"\",\n status: \"away\"\n)\n```\n\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Presences\n\nval client = Client(context)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nval presences = Presences(client)\n\nval presence = presences.update(\n presenceId = \"\",\n status = \"away\"\n)\n```\n\n```server-nodejs\nconst sdk = require('node-appwrite');\n\nconst client = new sdk.Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nconst presences = new sdk.Presences(client);\n\nconst presence = await presences.updatePresence({\n presenceId: '',\n userId: '',\n status: 'away'\n});\n```\n\n```server-python\nfrom appwrite.client import Client\nfrom appwrite.services.presences import Presences\n\nclient = Client()\nclient.set_endpoint('https://.cloud.appwrite.io/v1')\nclient.set_project('')\nclient.set_key('')\n\npresences = Presences(client)\n\npresence = presences.update_presence(\n presence_id = '',\n user_id = '',\n status = 'away'\n)\n```\n\n```server-php\nsetEndpoint('https://.cloud.appwrite.io/v1')\n ->setProject('')\n ->setKey('');\n\n$presences = new Presences($client);\n\n$presence = $presences->updatePresence(\n presenceId: '',\n userId: '',\n status: 'away'\n);\n```\n\n```server-ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://.cloud.appwrite.io/v1')\n .set_project('')\n .set_key('')\n\npresences = Presences.new(client)\n\npresence = presences.update_presence(\n presence_id: '',\n user_id: '',\n status: 'away'\n)\n```\n\n```server-dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\n\nClient client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nPresences presences = Presences(client);\n\nPresence presence = await presences.updatePresence(\n presenceId: '',\n userId: '',\n status: 'away',\n);\n```\n\n```server-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Presences\n\nval client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\")\n\nval presences = Presences(client)\n\nval presence = presences.updatePresence(\n presenceId = \"\",\n userId = \"\",\n status = \"away\"\n)\n```\n\n```server-java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Presences;\n\nClient client = new Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\");\n\nPresences presences = new Presences(client);\n\npresences.updatePresence(\n \"\", // presenceId\n \"\", // userId\n \"away\", // status\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n System.out.println(result);\n })\n);\n```\n\n```server-swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\")\n\nlet presences = Presences(client)\n\nlet presence = try await presences.updatePresence(\n presenceId: \"\",\n userId: \"\",\n status: \"away\"\n)\n```\n\n```server-dotnet\nusing Appwrite;\nusing Appwrite.Models;\nusing Appwrite.Services;\n\nClient client = new Client()\n .SetEndPoint(\"https://.cloud.appwrite.io/v1\")\n .SetProject(\"\")\n .SetKey(\"\");\n\nPresences presences = new Presences(client);\n\nPresence presence = await presences.UpdatePresence(\n presenceId: \"\",\n userId: \"\",\n status: \"away\"\n);\n```\n\n```server-go\npackage main\n\nimport (\n \"fmt\"\n \"github.com/appwrite/sdk-for-go/client\"\n \"github.com/appwrite/sdk-for-go/presences\"\n)\n\nfunc main() {\n cli := client.New(\n client.WithEndpoint(\"https://.cloud.appwrite.io/v1\"),\n client.WithProject(\"\"),\n client.WithKey(\"\"),\n )\n\n service := presences.New(cli)\n\n presence, err := service.UpdatePresence(\n \"\",\n \"\",\n service.WithUpdatePresenceStatus(\"away\"),\n )\n if err != nil {\n panic(err)\n }\n fmt.Println(presence)\n}\n```\n\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::Presences;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box> {\n let client = Client::new()\n .set_endpoint(\"https://.cloud.appwrite.io/v1\")\n .set_project(\"\")\n .set_key(\"\");\n\n let presences = Presences::new(&client);\n\n let presence = presences.update_presence(\n \"\",\n \"\",\n Some(\"away\"),\n None,\n None,\n None,\n None,\n ).await?;\n\n println!(\"{:?}\", presence);\n\n Ok(())\n}\n```\n{% /multicode %}\n\n# Delete a presence {% #delete-a-presence %}\n\n`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.\n\n{% multicode %}\n```client-web\nimport { Client, Presences } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nconst presences = new Presences(client);\n\nawait presences.delete({\n presenceId: ''\n});\n```\n\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nfinal presences = Presences(client);\n\nawait presences.delete(\n presenceId: '',\n);\n```\n\n```client-apple\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nlet presences = Presences(client)\n\n_ = try await presences.delete(\n presenceId: \"\"\n)\n```\n\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Presences\n\nval client = Client(context)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nval presences = Presences(client)\n\npresences.delete(\n presenceId = \"\"\n)\n```\n\n```server-nodejs\nconst sdk = require('node-appwrite');\n\nconst client = new sdk.Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nconst presences = new sdk.Presences(client);\n\nawait presences.delete({\n presenceId: ''\n});\n```\n\n```server-python\nfrom appwrite.client import Client\nfrom appwrite.services.presences import Presences\n\nclient = Client()\nclient.set_endpoint('https://.cloud.appwrite.io/v1')\nclient.set_project('')\nclient.set_key('')\n\npresences = Presences(client)\n\npresences.delete(\n presence_id = ''\n)\n```\n\n```server-php\nsetEndpoint('https://.cloud.appwrite.io/v1')\n ->setProject('')\n ->setKey('');\n\n$presences = new Presences($client);\n\n$presences->delete(\n presenceId: ''\n);\n```\n\n```server-ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://.cloud.appwrite.io/v1')\n .set_project('')\n .set_key('')\n\npresences = Presences.new(client)\n\npresences.delete(\n presence_id: ''\n)\n```\n\n```server-dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\n\nClient client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nPresences presences = Presences(client);\n\nawait presences.delete(\n presenceId: '',\n);\n```\n\n```server-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Presences\n\nval client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\")\n\nval presences = Presences(client)\n\npresences.delete(\n presenceId = \"\"\n)\n```\n\n```server-java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Presences;\n\nClient client = new Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\");\n\nPresences presences = new Presences(client);\n\npresences.delete(\n \"\", // presenceId\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n System.out.println(result);\n })\n);\n```\n\n```server-swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\")\n\nlet presences = Presences(client)\n\n_ = try await presences.delete(\n presenceId: \"\"\n)\n```\n\n```server-dotnet\nusing Appwrite;\nusing Appwrite.Services;\n\nClient client = new Client()\n .SetEndPoint(\"https://.cloud.appwrite.io/v1\")\n .SetProject(\"\")\n .SetKey(\"\");\n\nPresences presences = new Presences(client);\n\nawait presences.Delete(\n presenceId: \"\"\n);\n```\n\n```server-go\npackage main\n\nimport (\n \"fmt\"\n \"github.com/appwrite/sdk-for-go/client\"\n \"github.com/appwrite/sdk-for-go/presences\"\n)\n\nfunc main() {\n cli := client.New(\n client.WithEndpoint(\"https://.cloud.appwrite.io/v1\"),\n client.WithProject(\"\"),\n client.WithKey(\"\"),\n )\n\n service := presences.New(cli)\n\n _, err := service.Delete(\"\")\n if err != nil {\n panic(err)\n }\n fmt.Println(\"Presence deleted\")\n}\n```\n\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::Presences;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box> {\n let client = Client::new()\n .set_endpoint(\"https://.cloud.appwrite.io/v1\")\n .set_project(\"\")\n .set_key(\"\");\n\n let presences = Presences::new(&client);\n\n presences.delete(\"\").await?;\n\n Ok(())\n}\n```\n{% /multicode %}\n\n# Subscribe to presence updates {% #subscribe-to-presence-updates %}\n\nPresence 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('')` 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.\n\n{% multicode %}\n```client-web\nimport { Client, Realtime, Channel } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nconst realtime = new Realtime(client);\n\nconst subscription = await realtime.subscribe(Channel.presences(), response => {\n if (response.events.includes('presences.*.delete')) {\n console.log('Presence expired or removed', response.payload);\n } else if (response.events.includes('presences.*.upsert') || response.events.includes('presences.*.update')) {\n console.log('Presence created or updated', response.payload);\n }\n});\n```\n\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nfinal realtime = Realtime(client);\n\nfinal subscription = realtime.subscribe([Channel.presences()]);\n\nsubscription.stream.listen((response) {\n if (response.events.contains('presences.*.delete')) {\n print('Presence expired or removed: ${response.payload}');\n } else if (response.events.contains('presences.*.upsert') || response.events.contains('presences.*.update')) {\n print('Presence created or updated: ${response.payload}');\n }\n});\n```\n\n```client-apple\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nlet realtime = Realtime(client)\n\nlet subscription = realtime.subscribe(channels: [Channel.presences()]) { response in\n if (response.events?.contains(\"presences.*.delete\") == true) {\n print(\"Presence expired or removed: \\(String(describing: response.payload))\")\n } else if (response.events?.contains(\"presences.*.upsert\") == true || response.events?.contains(\"presences.*.update\") == true) {\n print(\"Presence created or updated: \\(String(describing: response.payload))\")\n }\n}\n```\n\n```client-android-kotlin\nimport io.appwrite.Channel\nimport io.appwrite.Client\nimport io.appwrite.services.Realtime\n\nval client = Client(context)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nval realtime = Realtime(client)\n\nval subscription = realtime.subscribe(Channel.presences()) {\n if (it.events.contains(\"presences.*.delete\")) {\n println(\"Presence expired or removed: ${it.payload}\")\n } else if (it.events.contains(\"presences.*.upsert\") || it.events.contains(\"presences.*.update\")) {\n println(\"Presence created or updated: ${it.payload}\")\n }\n}\n```\n{% /multicode %}\n\nThe `events` array follows the same pattern as every other Appwrite resource:\n\n- `presences.*.upsert` and `presences..upsert` for the unified create-or-update path that fires on every `upsert()` call.\n- `presences.*.update` and `presences..update` for status, metadata, or expiry changes made via the REST `update()` operation.\n- `presences.*.delete` and `presences..delete` for records that were deleted explicitly or expired automatically.\n\nNote that there is no separate `create` event, the `upsert` event covers both first-time creation and subsequent writes.\n\nThis gives you a clean signal for \"user just came online\", \"user changed status\", and \"user went offline\", without writing any custom socket logic.\n\n# Presence channels {% #presence-channels %}\n\n{% table %}\n* Channel\n* Channel Helper\n* Description\n---\n* `presences`\n* `Channel.presences()`\n* Any upsert, update, or delete event on any presence the subscriber can read.\n---\n* `presences.`\n* `Channel.presence('')`\n* Any upsert, update, or delete event on a specific presence record.\n{% /table %}\n\nYou can also append `.upsert()`, `.update()`, or `.delete()` to `Channel.presence('')` to narrow the stream to a single event type, identical to how channel filters work on every other resource.\n\n# Expiry and cleanup {% #expiry-and-cleanup %}\n\nEvery 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.\n\nYou 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.\n\n{% multicode %}\n```client-web\nimport { Client, Presences, ID, Permission, Role } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nconst presences = new Presences(client);\n\nconst presence = await presences.upsert({\n presenceId: ID.unique(),\n status: 'online',\n expiresAt: new Date(Date.now() + 5 * 60 * 1000).toISOString(),\n permissions: [\n Permission.read(Role.users())\n ]\n});\n```\n\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nfinal presences = Presences(client);\n\nfinal presence = await presences.upsert(\n presenceId: ID.unique(),\n status: 'online',\n expiresAt: DateTime.now().add(Duration(minutes: 5)).toIso8601String(),\n permissions: [\n Permission.read(Role.users()),\n ],\n);\n```\n\n```client-apple\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nlet presences = Presences(client)\n\nlet formatter = ISO8601DateFormatter()\nlet presence = try await presences.upsert(\n presenceId: ID.unique(),\n status: \"online\",\n expiresAt: formatter.string(from: Date().addingTimeInterval(300)),\n permissions: [\n Permission.read(Role.users())\n ]\n)\n```\n\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.ID\nimport io.appwrite.Permission\nimport io.appwrite.Role\nimport io.appwrite.services.Presences\nimport java.time.Instant\nimport java.time.temporal.ChronoUnit\n\nval client = Client(context)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nval presences = Presences(client)\n\nval presence = presences.upsert(\n presenceId = ID.unique(),\n status = \"online\",\n expiresAt = Instant.now().plus(5, ChronoUnit.MINUTES).toString(),\n permissions = listOf(\n Permission.read(Role.users())\n )\n)\n```\n{% /multicode %}\n\nTo 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.\n\n# Permissions and scopes {% #permissions-and-scopes %}\n\nPresences use the standard Appwrite [permissions system](/docs/advanced/security/permissions). Set read permissions on a presence to control who can subscribe to it:\n\n- `Role.any()` makes the presence visible to anyone, including unauthenticated visitors.\n- `Role.users()` restricts visibility to signed-in users.\n- `Role.team('')` shares the presence with a specific team, which is the right choice for collaboration features where only teammates should see each other's status.\n\nPass a `permissions` array to `upsert()` to attach roles to a presence. For example, to make a presence visible only to a specific team:\n\n{% multicode %}\n```client-web\nimport { Client, Presences, ID, Permission, Role } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nconst presences = new Presences(client);\n\nconst presence = await presences.upsert({\n presenceId: ID.unique(),\n status: 'online',\n permissions: [\n Permission.read(Role.team(''))\n ]\n});\n```\n\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nfinal presences = Presences(client);\n\nfinal presence = await presences.upsert(\n presenceId: ID.unique(),\n status: 'online',\n permissions: [\n Permission.read(Role.team('')),\n ],\n);\n```\n\n```client-apple\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nlet presences = Presences(client)\n\nlet presence = try await presences.upsert(\n presenceId: ID.unique(),\n status: \"online\",\n permissions: [\n Permission.read(Role.team(\"\"))\n ]\n)\n```\n\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.ID\nimport io.appwrite.Permission\nimport io.appwrite.Role\nimport io.appwrite.services.Presences\n\nval client = Client(context)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nval presences = Presences(client)\n\nval presence = presences.upsert(\n presenceId = ID.unique(),\n status = \"online\",\n permissions = listOf(\n Permission.read(Role.team(\"\"))\n )\n)\n```\n{% /multicode %}\n\nServer 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.\n\nIf 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.\n\n# Use cases {% #use-cases %}\n\nThe 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\":\n\n- **Online indicators** in a directory or contacts list\n- **Collaboration cursors** that show which document or section each teammate is viewing\n- **Typing indicators** in chat or comment threads\n- **Live attendee lists** for live streams, classrooms, or shared dashboards\n- **Locking signals** that warn a teammate when someone else is already editing a row\n\nFor 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.\n\n# Related {% #related %}\n\n- [Realtime overview](/docs/apis/realtime)\n- [Realtime channels reference](/docs/apis/realtime/channels)\n- [Realtime payload structure](/docs/apis/realtime/payload)\n- [Authentication: Presences](/docs/products/auth/presences)"}, {"path": "docs/apis/realtime/queries", "title": "Queries", "description": "Filter realtime events using queries. Use familiar SDK query methods to receive only the updates that match your conditions.", "content": "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.\n\n{% multicode %}\n```client-web\nimport { Client, Realtime, Channel, Query } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nconst realtime = new Realtime(client);\n\n// Subscribe to all updates\nconst allVotes = await realtime.subscribe(\n Channel.tablesdb('').table('').row(),\n response => {\n console.log(response.payload);\n }\n);\n\n// Subscribe to updates where person equals 'person1'\nconst person1Votes = await realtime.subscribe(\n Channel.tablesdb('').table('').row(),\n response => {\n console.log(response.payload);\n },\n [Query.equal('person', ['person1'])]\n);\n\n// Subscribe to updates where person is not 'person1'\nconst otherVotes = await realtime.subscribe(\n Channel.tablesdb('').table('').row(),\n response => {\n console.log(response.payload);\n },\n [Query.notEqual('person', 'person1')]\n);\n```\n\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nfinal realtime = Realtime(client);\n\n// Subscribe to all updates\nfinal allVotes = realtime.subscribe(\n [Channel.tablesdb('').table('').row()]\n);\n\nallVotes.stream.listen((response) {\n print(response.payload);\n});\n\n// Subscribe to updates where person equals 'person1'\nfinal person1Votes = realtime.subscribe(\n [Channel.tablesdb('').table('').row()],\n queries: [Query.equal('person', ['person1'])]\n);\n\nperson1Votes.stream.listen((response) {\n print(response.payload);\n});\n\n// Subscribe to updates where person is not 'person1'\nfinal otherVotes = realtime.subscribe(\n [Channel.tablesdb('').table('').row()],\n queries: [Query.notEqual('person', 'person1')]\n);\n\notherVotes.stream.listen((response) {\n print(response.payload);\n});\n```\n\n```client-apple\nimport Appwrite\nimport AppwriteModels\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nlet realtime = Realtime(client)\n\n// Subscribe to all updates\nlet allVotes = realtime.subscribe(\n channels: [Channel.tablesdb(\"\").table(\"\").row()]\n) { response in\n print(String(describing: response.payload))\n}\n\n// Subscribe to updates where person equals 'person1'\nlet person1Votes = realtime.subscribe(\n channels: [Channel.tablesdb(\"\").table(\"\").row()],\n callback: { response in\n print(String(describing: response.payload))\n },\n queries: [Query.equal(\"person\", value: [\"person1\"])]\n)\n\n// Subscribe to updates where person is not 'person1'\nlet otherVotes = realtime.subscribe(\n channels: [Channel.tablesdb(\"\").table(\"\").row()],\n callback: { response in\n print(String(describing: response.payload))\n },\n queries: [Query.notEqual(\"person\", value: \"person1\")]\n)\n```\n\n```client-android-kotlin\nimport io.appwrite.Channel\nimport io.appwrite.Client\nimport io.appwrite.Query\nimport io.appwrite.services.Realtime\n\nval client = Client(context)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nval realtime = Realtime(client)\n\n// Subscribe to all updates\nval allVotes = realtime.subscribe(\n Channel.tablesdb(\"\").table(\"\").row()\n) {\n print(it.payload.toString())\n}\n\n// Subscribe to updates where person equals 'person1'\nval person1Votes = realtime.subscribe(\n Channel.tablesdb(\"\").table(\"\").row(),\n payloadType = Any::class.java,\n queries = setOf(Query.equal(\"person\", listOf(\"person1\")))\n) {\n print(it.payload.toString())\n}\n\n// Subscribe to updates where person is not 'person1'\nval otherVotes = realtime.subscribe(\n Channel.tablesdb(\"\").table(\"\").row(),\n payloadType = Any::class.java,\n queries = setOf(Query.notEqual(\"person\", \"person1\"))\n) {\n print(it.payload.toString())\n}\n```\n\n```client-android-java\nimport io.appwrite.Client;\nimport io.appwrite.Query;\nimport io.appwrite.models.RealtimeResponseEvent;\nimport io.appwrite.models.RealtimeSubscription;\nimport io.appwrite.services.Realtime;\nimport java.util.Arrays;\nimport java.util.HashSet;\nimport kotlin.Unit;\n\nClient client = new Client(context)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\");\n\nRealtime realtime = new Realtime(client);\n\n// Subscribe to all updates\nRealtimeSubscription allVotes = realtime.subscribe(\n new String[] {\"tablesdb..tables..rows\"},\n (RealtimeResponseEvent response) -> {\n System.out.println(response.getPayload());\n return Unit.INSTANCE;\n }\n);\n\n// Subscribe to updates where person equals 'person1'\nRealtimeSubscription person1Votes = realtime.subscribe(\n new String[] {\"tablesdb..tables..rows\"},\n Object.class,\n new HashSet<>(Arrays.asList(Query.equal(\"person\", Arrays.asList(\"person1\")))),\n (RealtimeResponseEvent response) -> {\n System.out.println(response.getPayload());\n return Unit.INSTANCE;\n }\n);\n\n// Subscribe to updates where person is not 'person1'\nRealtimeSubscription otherVotes = realtime.subscribe(\n new String[] {\"tablesdb..tables..rows\"},\n Object.class,\n new HashSet<>(Arrays.asList(Query.notEqual(\"person\", \"person1\"))),\n (RealtimeResponseEvent response) -> {\n System.out.println(response.getPayload());\n return Unit.INSTANCE;\n }\n);\n```\n\n{% /multicode %}\n\n# Supported queries {% #supported-queries %}\n\nThe following query methods are supported for realtime filtering:\n\n{% table %}\n* Category\n* Queries\n---\n* Comparison\n* `Query.equal()`, `Query.notEqual()`, `Query.greaterThan()`, `Query.greaterThanEqual()`, `Query.lessThan()`, `Query.lessThanEqual()`\n---\n* Null checks\n* `Query.isNull()`, `Query.isNotNull()`\n---\n* Logical\n* `Query.and()`, `Query.or()`\n{% /table %}"}, {"path": "docs/apis/realtime/subscribe", "title": "Subscribe", "description": "Learn how to subscribe to realtime events from Appwrite services. Subscribe to single or multiple channels and manage your subscriptions.", "content": "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`.\n\n# Subscribe to a channel {% #subscribe-to-a-channel %}\n\nIn 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.\n\n{% multicode %}\n```client-web\nimport { Client, Realtime, Channel } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nconst realtime = new Realtime(client);\n\nconst subscription = await realtime.subscribe(Channel.account(), response => {\n // Callback will be executed on all account events.\n console.log(response);\n});\n```\n\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nfinal realtime = Realtime(client);\n\nfinal subscription = realtime.subscribe([Channel.account()]);\n\nsubscription.stream.listen((response) {\n // Callback will be executed on all account events.\n print(response);\n});\n```\n\n```client-apple\nimport Appwrite\nimport AppwriteModels\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nlet realtime = Realtime(client)\n\nlet subscription = realtime.subscribe(channels: [Channel.account()]) { response in\n // Callback will be executed on all account events.\n print(String(describing: response))\n}\n```\n\n```client-android-kotlin\nimport io.appwrite.Channel\nimport io.appwrite.Client\nimport io.appwrite.services.Realtime\n\nval client = Client(context)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nval realtime = Realtime(client)\n\nval subscription = realtime.subscribe(Channel.account()) {\n // Callback will be executed on all account events.\n print(it.payload.toString())\n}\n```\n\n```client-android-java\nimport io.appwrite.Client;\nimport io.appwrite.models.RealtimeResponseEvent;\nimport io.appwrite.models.RealtimeSubscription;\nimport io.appwrite.services.Realtime;\nimport kotlin.Unit;\n\nClient client = new Client(context)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\");\n\nRealtime realtime = new Realtime(client);\n\nRealtimeSubscription subscription = realtime.subscribe(\n new String[] {\"account\"},\n (RealtimeResponseEvent response) -> {\n // Callback will be executed on all account events.\n System.out.println(response);\n return Unit.INSTANCE;\n }\n);\n```\n\n{% /multicode %}\n\n# Subscribe to multiple channels {% #subscribe-to-multiple-channels %}\n\nYou 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.\n\nIn this example we are listening to a specific row and all files by subscribing to `Channel.tablesdb(\"\").table(\"\").row(\"\")` and `Channel.files()` channels.\n\n{% multicode %}\n```client-web\nimport { Client, Realtime, Channel } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nconst realtime = new Realtime(client);\n\nconst subscription = await realtime.subscribe([\n Channel.tablesdb('').table('').row(''),\n Channel.files()\n], response => {\n // Callback will be executed on changes for the row and all files.\n console.log(response);\n});\n```\n\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nfinal realtime = Realtime(client);\n\nfinal subscription = realtime.subscribe([\n Channel.tablesdb('').table('').row(''),\n Channel.files()\n]);\n\nsubscription.stream.listen((response) {\n // Callback will be executed on changes for the row and all files.\n print(response);\n});\n```\n\n```client-apple\nimport Appwrite\nimport AppwriteModels\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nlet realtime = Realtime(client)\n\nrealtime.subscribe(channels: [\n Channel.tablesdb(\"\").table(\"\").row(\"\"),\n Channel.files()\n]) { response in\n // Callback will be executed on changes for the row and all files.\n print(String(describing: response))\n}\n```\n\n```client-android-kotlin\nimport io.appwrite.Channel\nimport io.appwrite.Client\nimport io.appwrite.services.Realtime\n\nval client = Client(context)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\nval realtime = Realtime(client)\n\nrealtime.subscribe(\n Channel.tablesdb(\"\").table(\"\").row(\"\"),\n Channel.files()\n) {\n // Callback will be executed on changes for the row and all files.\n print(it.toString())\n}\n```\n\n```client-android-java\nimport io.appwrite.Client;\nimport io.appwrite.models.RealtimeResponseEvent;\nimport io.appwrite.models.RealtimeSubscription;\nimport io.appwrite.services.Realtime;\nimport kotlin.Unit;\n\nClient client = new Client(context)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\");\nRealtime realtime = new Realtime(client);\n\nRealtimeSubscription subscription = realtime.subscribe(\n new String[] {\n \"tablesdb..tables..rows.\",\n \"files\"\n },\n (RealtimeResponseEvent response) -> {\n // Callback will be executed on changes for the row and all files.\n System.out.println(response);\n return Unit.INSTANCE;\n }\n);\n```\n\n{% /multicode %}\n\n# Update channels or queries {% #update %}\n\nChannels 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.\n\n`update()` accepts either or both of `channels` and `queries`. Pass only the field you want to replace; omitted fields are left unchanged.\n\n{% multicode %}\n```client-web\nimport { Client, Realtime, Channel } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nconst realtime = new Realtime(client);\n\nconst subscription = await realtime.subscribe(\n Channel.tablesdb('').table('').row(''),\n response => console.log(response),\n);\n\n// Switch to a different row — no reconnect required.\nawait subscription.update({\n channels: [Channel.tablesdb('').table('').row('')],\n});\n```\n\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nfinal realtime = Realtime(client);\n\nfinal subscription = realtime.subscribe([\n Channel.tablesdb('').table('').row(''),\n]);\n\nsubscription.stream.listen((response) {\n print(response);\n});\n\n// Switch to a different row — no reconnect required.\nawait subscription.update(\n channels: [Channel.tablesdb('').table('').row('')],\n);\n```\n\n```client-apple\nimport Appwrite\nimport AppwriteModels\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nlet realtime = Realtime(client)\n\nlet subscription = realtime.subscribe(channels: [\n Channel.tablesdb(\"\").table(\"\").row(\"\"),\n]) { response in\n print(String(describing: response))\n}\n\n// Switch to a different row — no reconnect required.\ntry await subscription.update(RealtimeSubscriptionUpdate(\n channels: [Channel.tablesdb(\"\").table(\"\").row(\"\")]\n))\n```\n\n```client-android-kotlin\nimport io.appwrite.Channel\nimport io.appwrite.Client\nimport io.appwrite.services.Realtime\nimport io.appwrite.models.RealtimeSubscriptionUpdate\n\nval client = Client(context)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nval realtime = Realtime(client)\n\nval subscription = realtime.subscribe(\n Channel.tablesdb(\"\").table(\"\").row(\"\"),\n) {\n print(it.toString())\n}\n\n// Switch to a different row — no reconnect required.\nsubscription.update(RealtimeSubscriptionUpdate(\n channels = listOf(Channel.tablesdb(\"\").table(\"\").row(\"\")),\n))\n```\n\n```client-android-java\nimport io.appwrite.Client;\nimport io.appwrite.models.RealtimeResponseEvent;\nimport io.appwrite.models.RealtimeSubscription;\nimport io.appwrite.models.RealtimeSubscriptionUpdate;\nimport io.appwrite.services.Realtime;\nimport kotlin.Unit;\n\nimport java.util.Arrays;\n\nClient client = new Client(context)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\");\n\nRealtime realtime = new Realtime(client);\n\nRealtimeSubscription subscription = realtime.subscribe(\n new String[] {\"tablesdb..tables..rows.\"},\n (RealtimeResponseEvent response) -> {\n System.out.println(response);\n return Unit.INSTANCE;\n }\n);\n\n// Switch to a different row — no reconnect required.\nsubscription.update(new RealtimeSubscriptionUpdate(\n Arrays.asList(\"tablesdb..tables..rows.\"),\n null\n));\n```\n\n{% /multicode %}\n\n# Unsubscribe {% #unsubscribe %}\n\nIf 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.\n\n{% multicode %}\n```client-web\nimport { Client, Realtime, Channel } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nconst realtime = new Realtime(client);\n\nconst subscription = await realtime.subscribe(Channel.files(), response => {\n // Callback will be executed on changes for all files.\n console.log(response);\n});\n\n// Removes only this subscription. Other subscriptions and the WebSocket stay open.\nawait subscription.unsubscribe();\n```\n\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nfinal realtime = Realtime(client);\n\nfinal subscription = realtime.subscribe([Channel.files()]);\n\nsubscription.stream.listen((response) {\n // Callback will be executed on changes for all files.\n print(response);\n});\n\n// Removes only this subscription. Other subscriptions and the WebSocket stay open.\nawait subscription.unsubscribe();\n```\n\n```client-apple\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nlet realtime = Realtime(client)\n\nlet subscription = realtime.subscribe(channels: [Channel.files()]) { response in\n // Callback will be executed on changes for all files.\n print(response.toString())\n}\n\n// Removes only this subscription. Other subscriptions and the WebSocket stay open.\ntry await subscription.unsubscribe()\n```\n\n```client-android-kotlin\nimport io.appwrite.Channel\nimport io.appwrite.Client\nimport io.appwrite.services.Realtime\n\nval client = Client(context)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nval realtime = Realtime(client)\n\nval subscription = realtime.subscribe(Channel.files()) {\n // Callback will be executed on changes for all files.\n print(it.toString())\n}\n\n// Removes only this subscription. Other subscriptions and the WebSocket stay open.\nsubscription.unsubscribe()\n```\n\n```client-android-java\nimport io.appwrite.Client;\nimport io.appwrite.models.RealtimeResponseEvent;\nimport io.appwrite.models.RealtimeSubscription;\nimport io.appwrite.services.Realtime;\nimport kotlin.Unit;\n\nClient client = new Client(context)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\");\n\nRealtime realtime = new Realtime(client);\n\nRealtimeSubscription subscription = realtime.subscribe(\n new String[] {\"files\"},\n (RealtimeResponseEvent response) -> {\n // Callback will be executed on changes for all files.\n System.out.println(response);\n return Unit.INSTANCE;\n }\n);\n\n// Removes only this subscription. Other subscriptions and the WebSocket stay open.\nsubscription.unsubscribe();\n```\n\n{% /multicode %}\n\n{% info title=\"Legacy close()\" %}\n`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.\n{% /info %}\n\n# Disconnect {% #disconnect %}\n\nCall `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.\n\n{% multicode %}\n```client-web\nimport { Client, Realtime } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nconst realtime = new Realtime(client);\n\n// ... subscribe to one or more channels ...\n\n// Drop all subscriptions and close the WebSocket.\nawait realtime.disconnect();\n```\n\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nfinal realtime = Realtime(client);\n\n// ... subscribe to one or more channels ...\n\n// Drop all subscriptions and close the WebSocket.\nawait realtime.disconnect();\n```\n\n```client-apple\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nlet realtime = Realtime(client)\n\n// ... subscribe to one or more channels ...\n\n// Drop all subscriptions and close the WebSocket.\ntry await realtime.disconnect()\n```\n\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Realtime\n\nval client = Client(context)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nval realtime = Realtime(client)\n\n// ... subscribe to one or more channels ...\n\n// Drop all subscriptions and close the WebSocket.\nrealtime.disconnect()\n```\n\n```client-android-java\nimport io.appwrite.Client;\nimport io.appwrite.services.Realtime;\n\nClient client = new Client(context)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\");\n\nRealtime realtime = new Realtime(client);\n\n// ... subscribe to one or more channels ...\n\n// Drop all subscriptions and close the WebSocket.\nrealtime.disconnect();\n```\n\n{% /multicode %}"}, {"path": "docs/apis/response-codes", "title": "Response codes", "description": "Understand Appwrite platform response codes and error handling. Learn to interpret HTTP status codes, error types, and implement best practices for handling errors gracefully.", "content": "Appwrite uses conventional HTTP response codes to indicate the success or failure of an API request.\n\n- Codes in the `2xx` range indicate success.\n- Codes in the `4xx` range indicate an error caused by invalid request, usually caused by user error.\n- Codes in the `5xx` range indicate an error with Appwrite, please check Docker container logs.\n\n# Response codes {% #response-codes %}\n| Code | Text | Description |\n|------|------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| 200 | OK | Success! |\n| 201 | Created | The requested resource has been created successfully. |\n| 202 | Accepted | The requested change has been accepted for processing but has not been completed. |\n| 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. |\n| 301 | Moved Permanently | The URL of the requested resource has been changed permanently. The new URL is given in the response. |\n| 304 | Not Modified | There was no new data to return. |\n| 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. |\n| 401 | Unauthorized | Missing or incorrect authentication credentials can happen when the API key or user permission is not sufficient. |\n| 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. |\n| 404 | Not Found | The URI requested is invalid or the resource requested, such as a user, does not exist. |\n| 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. |\n| 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 |\n| 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. |\n| 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). |\n| 500 | Internal Server Error | Something is broken. Contact our [team](/support), or raise a [GitHub issue](https://github.com/appwrite/appwrite/issues/new). |\n| 501 | Not Implemented | The feature is not implemented. Usually returned when the project owner has disabled an auth method or an entire service. |\n| 503 | Service Unavailable | The Appwrite servers are up but overloaded with requests. Try again later. |\n| 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. |\n\n# Error messages {% #error-messages %}\n\nWhen the Appwrite APIs return error messages, it does so in JSON format. For example, an error might look like this:\n\n```json\n{\n \"message\": \"Invalid id: Parameter must be a valid number\",\n \"type\": \"argument_invalid\",\n \"code\": 400\n}\n```\n\n# Error types {% #error-types %}\n\nAppwrite 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.\n\nFor 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:\n\n## Platform errors {% #platform-errors %}\nPlatform errors are not related to an individual product, but can occur across the Appwrite platform.\n| Code | Type | Description |\n| ---- | ---- | ----------- |\n| 400 | general_mock | General errors thrown by the mock controller used for testing. |\n| 400 | general_argument_invalid | The request contains one or more invalid arguments. Please refer to the endpoint documentation. |\n| 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. |\n| 400 | general_query_invalid | The query's syntax is invalid. Please check the query and try again. |\n| 400 | general_cursor_not_found | The cursor is invalid. This can happen if the item represented by the cursor has been deleted. |\n| 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. |\n| 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. |\n| 400 | project_invalid_success_url | Invalid redirect URL for OAuth success. |\n| 400 | project_invalid_failure_url | Invalid redirect URL for OAuth failure. |\n| 400 | project_reserved_project | The project ID is reserved. Please choose another project ID. |\n| 400 | project_smtp_config_invalid | Provided SMTP config is invalid. Please check the configured values and try again. |\n| 401 | project_key_expired | The project key has expired. Please generate a new key using the Appwrite console. |\n| 401 | rule_verification_failed | Domain verification failed. Please check if your DNS records are correct and try again. |\n| 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. |\n| 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. |\n| 401 | general_access_forbidden | Access to this API is forbidden. |\n| 401 | general_unauthorized_scope | The current user or API key does not have the required scopes to access the requested resource. |\n| 404 | general_route_not_found | The requested route was not found. Please refer to the API docs and try again. |\n| 404 | webhook_not_found | Webhook with the requested ID could not be found. |\n| 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. |\n| 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. |\n| 404 | key_not_found | Key with the requested ID could not be found. |\n| 404 | platform_not_found | Platform with the requested ID could not be found. |\n| 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. |\n| 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. |\n| 405 | general_not_implemented | This method was not fully implemented yet. If you believe this is a mistake, please upgrade your Appwrite server version. |\n| 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. |\n| 409 | rule_already_exists | Domain is already used. Please try again with a different domain. |\n| 412 | project_provider_disabled | The chosen OAuth provider is disabled. You can enable the OAuth provider using the Appwrite console. |\n| 429 | general_rate_limit_exceeded | Rate limit for the current endpoint has been exceeded. Please try again after some time. |\n| 500 | general_unknown | An unknown error has occurred. Please check the logs for more information. |\n| 500 | general_server_error | An internal server error occurred. |\n| 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. |\n| 500 | general_codes_disabled | Invitation codes are disabled on this server. Please contact the server administrator. |\n| 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. |\n| 501 | general_usage_disabled | Usage stats is not configured. Please check the value of the `_APP_USAGE_STATS` environment variable of your Appwrite server. |\n| 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. |\n| 503 | general_service_disabled | The requested service is disabled. You can enable the service from the Appwrite console. |\n| 503 | general_smtp_disabled | SMTP is disabled on your Appwrite instance. You can learn more about setting up SMTP in our docs. |\n| 503 | general_phone_disabled | Phone provider is not configured. Please check the `_APP_SMS_PROVIDER` environment variable of your Appwrite server. |\n\n## Authentication errors {% #authentication-errors %}\nErrors found when using Appwrite Authentication.\n| Code | Type | Description |\n| ---- | ---- | ----------- |\n| 400 | user_password_mismatch | Passwords do not match. Please check the password and confirm password. |\n| 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. |\n| 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. |\n| 400 | user_phone_not_found | The current user does not have a phone number associated with their account. |\n| 400 | user_missing_id | Missing ID from OAuth2 provider. |\n| 400 | user_oauth2_bad_request | OAuth2 provider rejected the bad request. |\n| 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. |\n| 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. |\n| 401 | user_invalid_token | Invalid token passed in the request. |\n| 401 | user_email_not_whitelisted | Console registration is restricted to specific emails. Contact your administrator for more information. |\n| 401 | user_invalid_code | The specified code is not valid. Contact your administrator for more information. |\n| 401 | user_ip_not_whitelisted | Console registration is restricted to specific IPs. Contact your administrator for more information. |\n| 401 | user_invalid_credentials | Invalid credentials. Please check the email and password. |\n| 401 | user_anonymous_console_prohibited | Anonymous users cannot be created for the console project. |\n| 401 | user_session_already_exists | Creation of anonymous users is prohibited when a session is active. |\n| 401 | user_unauthorized | The current user is not authorized to perform the requested action. |\n| 401 | user_oauth2_unauthorized | OAuth2 provider rejected the unauthorized request. |\n| 401 | team_invalid_secret | The team invitation secret is invalid. Please request a new invitation and try again. |\n| 401 | team_invite_mismatch | The invite does not belong to the current user. |\n| 404 | user_not_found | User with the requested ID could not be found. |\n| 404 | user_session_not_found | The current user session could not be found. |\n| 404 | user_identity_not_found | The identity could not be found. Please sign in with OAuth provider to create identity first. |\n| 404 | team_not_found | Team with the requested ID could not be found. |\n| 404 | team_invite_not_found | The requested team invitation could not be found. |\n| 404 | team_membership_mismatch | The membership ID does not belong to the team ID. |\n| 404 | membership_not_found | Membership with the requested ID could not be found. |\n| 409 | user_already_exists | A user with the same id, email, or phone already exists in this project. |\n| 409 | user_email_already_exists | A user with the same email already exists in the current project. |\n| 409 | user_phone_already_exists | A user with the same phone number already exists in the current project. |\n| 409 | team_invite_already_exists | User has already been invited or is already a member of this team |\n| 409 | team_already_exists | Team with requested ID already exists. Please choose a different ID and try again. |\n| 409 | membership_already_confirmed | Membership is already confirmed. |\n| 412 | user_password_reset_required | The current user requires a password reset. |\n| 424 | user_oauth2_provider_error | OAuth2 provider returned some error. |\n| 501 | user_count_exceeded | The current project has exceeded the maximum number of users. Please check your user limit in the Appwrite console. |\n| 501 | user_auth_method_unsupported | The requested authentication method is either disabled or unsupported. Please check the supported authentication methods in the Appwrite console. |\n\n## Databases errors {% #databases-errors %}\nErrors found when using Appwrite Databases.\n| Code | Type | Description |\n| ---- | ---- | ----------- |\n| 400 | table_limit_exceeded | The maximum number of tables has been reached. |\n| 400 | row_invalid_structure | The row structure is invalid. Please ensure the columns match the table definition. |\n| 400 | row_missing_data | The row data is missing. Try again with row data populated. |\n| 400 | row_missing_payload | The row data and permissions are missing. You must provide either row data or permissions to be updated. |\n| 400 | column_unknown | The column required for the index could not be found. Please confirm all your columns are in the available state. |\n| 400 | column_not_available | The requested column is not yet available. Please try again later. |\n| 400 | column_format_unsupported | The requested column format is not supported. |\n| 400 | column_default_unsupported | Default values cannot be set for array or required columns. |\n| 400 | column_limit_exceeded | The maximum number of columns has been reached. |\n| 400 | column_value_invalid | The column value is invalid. Please check the type, range and value of the column. |\n| 400 | column_type_invalid | The column type is invalid. |\n| 400 | index_limit_exceeded | The maximum number of indexes has been reached. |\n| 400 | index_invalid | Index invalid. |\n| 403 | row_delete_restricted | Row cannot be deleted because it is referenced by another row. |\n| 404 | execution_not_found | Execution with the requested ID could not be found. |\n| 404 | database_not_found | Database not found |\n| 404 | table_not_found | Table with the requested ID could not be found. |\n| 404 | row_not_found | Row with the requested ID could not be found. |\n| 404 | column_not_found | Column with the requested ID could not be found. |\n| 404 | index_not_found | Index with the requested ID could not be found. |\n| 409 | database_already_exists | Database already exists |\n| 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. |\n| 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. |\n| 409 | row_update_conflict | Remote row is newer than local. |\n| 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. |\n| 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. |\n\n## Storage errors {% #storage-errors %}\nErrors found when using Appwrite Storage.\n| Code | Type | Description |\n| ---- | ---- | ----------- |\n| 400 | storage_device_not_found | The requested storage device could not be found. |\n| 400 | storage_file_empty | Empty file passed to the endpoint. |\n| 400 | storage_file_type_unsupported | The given file extension is not supported. |\n| 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. |\n| 400 | storage_invalid_content_range | The content range is invalid. Please check the value of the `Content-Range` header. |\n| 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()`. |\n| 403 | storage_invalid_file | The uploaded file is invalid. Please check the file and try again. |\n| 404 | storage_file_not_found | The requested file could not be found. |\n| 404 | storage_bucket_not_found | Storage bucket with the requested ID could not be found. |\n| 409 | storage_file_already_exists | A storage file with the requested ID already exists. |\n| 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. |\n| 416 | storage_invalid_range | The requested range is not satisfiable. Please check the value of the `Range` header. |\n\n## Functions errors {% #functions-errors %}\nErrors found when using Appwrite Functions.\n| Code | Type | Description |\n| ---- | ---- | ----------- |\n| 400 | build_not_ready | Build with the requested ID is building and not ready for execution. |\n| 400 | build_in_progress | Build with the requested ID is already in progress. Please wait before you can retry. |\n| 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. |\n| 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. |\n| 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. |\n| 404 | function_not_found | Function with the requested ID could not be found. |\n| 404 | function_runtime_unsupported | The requested runtime is either inactive or unsupported. Please check the value of the `_APP_FUNCTIONS_RUNTIMES` environment variable. |\n| 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\". |\n| 404 | build_not_found | Build with the requested ID could not be found. |\n| 404 | deployment_not_found | Deployment with the requested ID could not be found. |\n| 404 | variable_not_found | Variable with the requested ID could not be found. |\n| 409 | provider_contribution_conflict | External contribution is already authorized. |\n| 409 | variable_already_exists | Variable with the same ID already exists in this project. Try again with a different ID. |\n\n## Migrations errors {% #migrations-errors %}\nErrors when using Appwrite Migrations.\n| Code | Type | Description |\n| ---- | ---- | ----------- |\n| 404 | migration_not_found | Migration with the requested ID could not be found. Please verify that the provided ID is correct and try again. |\n| 409 | migration_already_exists | Migration with the requested ID already exists. Try again with a different ID. |\n| 409 | migration_in_progress | Migration is already in progress. You can check the status of the migration in your Appwrite Console's \"Settings\" > \"Migrations\". |\n\n\n## Avatars errors {% #avatars-errors %}\nErrors from the Appwrite Avatars API.\n| Code | Type | Description |\n| ---- | ---- | ----------- |\n| 404 | avatar_set_not_found | The requested avatar set could not be found. |\n| 404 | avatar_not_found | The request avatar could not be found. |\n| 404 | avatar_image_not_found | The requested image was not found at the URL. |\n| 404 | avatar_remote_url_failed | Failed to fetch favicon from the requested URL. |\n| 404 | avatar_icon_not_found | The requested favicon could not be found. |\n\n## GraphQL errors {% #graphql-errors %}\nErrors from the Appwrite GraphQL API.\n| Code | Type | Description |\n| ---- | ---- | ----------- |\n| 400 | graphql_no_query | Param \"query\" is not optional. |\n| 400 | graphql_too_many_queries | Too many queries. |\n\n# Error handling {% #error-handling %}\n\nThe 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.\n\n## Return user-friendly messages {% #user-friendly-messages %}\n\nAvoid 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.\n\nThe `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.\n\n| Error type | User-friendly message |\n|------------|----------------------|\n| `user_invalid_credentials` | \"The email or password you entered is incorrect. Please try again.\" |\n| `user_blocked` | \"Your account has been temporarily suspended. Please contact support.\" |\n| `general_rate_limit_exceeded` | \"Please wait a moment before trying again.\" |\n| `storage_file_not_found` | \"The file you requested is not available.\" |\n| `row_not_found` | \"The information you're looking for could not be found.\" |\n\n## Recommended practices {% #recommended-practices %}\n\n- **Log the full error server-side** for debugging, while showing only the friendly message to users.\n- **Handle common scenarios explicitly** and offer clear next steps, such as \"Try resetting your password.\"\n- **Retry transient errors** like rate limiting (`429`) or service unavailability (`503`), ideally with backoff.\n- **Keep error messaging consistent** in tone and styling across your application."}, {"path": "docs/apis/rest", "title": "REST", "description": "Discover the Appwrite REST API for building robust and scalable applications. Access detailed documentation on REST endpoints, authentication, and data management.", "content": "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.\n\n# Headers {% #headers %}\n\nAppwrite's REST APIs expect certain headers to be included with each request:\n\n{% table %}\n\n- Header\n-\n- Description\n\n---\n\n- X-Appwrite-Project: [PROJECT-ID]\n- required\n- The ID of your Appwrite project\n\n---\n\n- Content-Type: application/json\n- required\n- Content type of the HTTP request. Typically set to `application/json`.\n\n---\n\n- X-Appwrite-Key: [API-KEY]\n- optional\n- API key used for server authentication. Your API key is a secret, **do not** use it in client applications.\n\n---\n\n- X-Appwrite-JWT: [TOKEN]\n- optional\n- Token used for JWT authentication, tokens can be generated using the [Create JWT](/docs/products/auth/jwt) method.\n\n---\n\n- X-Appwrite-Response-Format: [VERSION-NUMBER]\n- optional\n- 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.\n\n---\n\n- X-Fallback-Cookies: [FALLBACK-COOKIES]\n- optional\n- 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.\n\n---\n\n- X-Appwrite-Impersonate-User-Id: [USER-ID]\n- optional\n- Resolves the effective user for an already authenticated impersonator request by Appwrite user ID. Only works when the authenticated user has impersonation enabled.\n\n---\n\n- X-Appwrite-Impersonate-User-Email: [EMAIL]\n- optional\n- Resolves the effective user for an already authenticated impersonator request by email address. Only works when the authenticated user has impersonation enabled.\n\n---\n\n- X-Appwrite-Impersonate-User-Phone: [PHONE]\n- optional\n- Resolves the effective user for an already authenticated impersonator request by phone number. Only works when the authenticated user has impersonation enabled.\n\n{% /table %}\n\n# Authentication {% #authentication %}\n\nAppwrite 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.\n\n## Client integrations {% #client-integration %}\n\nYou 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.\n\nThe example below shows creating an account session with the Create Account Session with Email endpoint.\n\n```json\nPOST /v1/account/sessions/email HTTP/1.1\nContent-Type: application/json\nX-Appwrite-Project: \n\n{\n \"email\": \"example@email.com\",\n \"password\": \"password\"\n}\n```\n\nYou can find the cookies used to persist the new session in the response headers.\n\n```json\nSet-Cookie: a_session_61e71ec784ab035f7259_legacy=eyJ0...aSJ9; expires=Tue, 19-Dec-2023 21:26:51 GMT; path=/; domain=.cloud.appwrite.io; secure; httponly\nSet-Cookie: a_session_61e71ec784ab035f7259=eyJ0...aSJ9; expires=Tue, 19-Dec-2023 21:26:51 GMT; path=/; domain=.cloud.appwrite.io; secure; httponly; samesite=None\n```\n\nThese cookies are used in subsequent requests to authenticate the user.\n\n```json\nGET /v1/account HTTP/1.1\nCookie: a_session_61e71ec784ab035f7259_legacy=eyJ0...aSJ9; a_session_61e71ec784ab035f7259=eyJ0...aSJ9\nContent-Type: application/json\nX-Appwrite-Project: \n```\n\n## Server integrations {% #server-integrations %}\n\nServer integrations use API keys to authenticate and are typically used for backend applications.\n\nServer APIs are authenticated with API keys instead of account sessions. Simply pass an [API key](/docs/advanced/security/api-keys) in the `X-Appwrite-key: [API-KEY]` header with the appropriate scopes.\n\n```json\nGET /v1/tablesdb/{databaseId}/tables/{tableId}/rows HTTP/1.1\nContent-Type: application/json\nX-Appwrite-Project: \nX-Appwrite-Key: [API_KEY]\n```\n\n## JWT {% #jwt %}\n\nJWT 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.\n\n```json\nGET /v1/account HTTP/1.1\nContent-Type: application/json\nX-Appwrite-Project: \nX-Appwrite-JWT: [TOKEN]\n```\n\n## Impersonation headers {% #impersonation-headers %}\n\nWhen 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.\n\nUse exactly one of these headers:\n\n- `X-Appwrite-Impersonate-User-Id`\n- `X-Appwrite-Impersonate-User-Email`\n- `X-Appwrite-Impersonate-User-Phone`\n\nThese 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.\n\nIf you are using an Appwrite SDK instead of raw REST calls, use the corresponding client setters rather than manually attaching these headers.\n\nLearn more in the [user impersonation docs](/docs/products/auth/impersonation).\n\n# Files {% #files %}\n\nAppwrite 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.\n\nUpload 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).\n\n{% table %}\n\n- Header\n-\n- Description\n\n---\n\n- X-Appwrite-Project: [PROJECT-ID]\n- required\n- The ID of your Appwrite project\n\n---\n\n- Content-Type: multipart/form-data; boundary=[FORM-BOUNDARY]\n- required\n- 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.\n\n---\n\n- Content-Range: bytes [BYTE-RANGE]\n- required\n- Contains information about which bytes are being transmitted in this chunk, with the format `[FIRST-BYTE]-[LAST-BYTE]/[TOTAL-BYTES]`.\n\n---\n\n- X-Appwrite-ID: [FILE-ID]\n- required\n- Contains ID of the file this chunk belongs to.\n\n---\n\n- X-Appwrite-Key: [API-KEY]\n- optional\n- API key used for server authentication. Your API key is a secret, **do not** use it in client applications.\n \n{% /table %}\n\nThe multipart form data is structured as follows:\n\n{% table %}\n\n- Key\n-\n- Value\n- File Name\n- Description\n\n---\n\n- fileId\n- optional\n- [FILE-ID]\n- N/A\n- Contains the file ID of the new file. Only used by file chunks following the first chunk uploaded.\n\n---\n\n- file\n- required\n- [CHUNK-DATA]\n- [FILE-NAME]\n- Contains file chunk data.\n\n---\n\n- permissions\n- required\n- [PERMISSION ARRAY]\n- N/A\n- Contains an array of permission strings about who can access the new file.\n \n{% /table %}\n\nWhile 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.\n\nThe multipart form data posted to file upload endpoints have the following format:\n\n```json\nPOST /v1/storage/buckets/default/files HTTP/1.1\nContent-Type: multipart/form-data; boundary=----WebKitFormBoundarye0m6iNBQNHlzTpVM\nX-Appwrite-Project: demo-project\nContent-Range: bytes 10485760-12582912/12582912\nX-Appwrite-ID: 6369b0bc1dcf4ff59051\n\n------WebKitFormBoundarye0m6iNBQNHlzTpVM\nContent-Disposition: form-data; name=\"fileId\"\n\nunique()\n------WebKitFormBoundarye0m6iNBQNHlzTpVM\nContent-Disposition: form-data; name=\"file\"; filename=\"file.txt\"\nContent-Type: application/octet-stream\n\n[CHUNKED-DATA]\n------WebKitFormBoundarye0m6iNBQNHlzTpVM\nContent-Disposition: form-data; name=\"permissions[]\"\n\nread(\"user:627a958ded6424a98a9f\")\n------WebKitFormBoundarye0m6iNBQNHlzTpVM--\n```\n\n# Images {% #images %}\n\nSome 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.\n\n```HTML\n\"/>\n```\n\n# Permissions {% #permissions %}\n\nAppwrite SDKs have helpers to generate permission string formats, but when using Appwrite without SDKs, you'd need to create the strings yourself.\n\n{% table %}\n\n- Query method\n- API string\n\n---\n\n- `Permission.read()`\n- `read(\"\")`\n\n---\n\n- `Permission.create()`\n- `read(\"\")`\n\n---\n\n- `Permission.update()`\n- `update(\"\")`\n\n---\n\n- `Permission.delete()`\n- `delete(\"\")`\n\n---\n\n- `Permission.write()`\n- `write(\"\")`\n \n{% /table %}\n\n## Roles {% #roles %}\n\nAppwrite SDKs have helpers to generate roles string formats, but when using Appwrite without SDKs, you'd need to create the strings yourself.\n\n{% table %}\n\n- Role method\n- API string\n\n---\n\n- `Role.any()`\n- `any`\n\n---\n\n- `Role.guests()`\n- `guests`\n\n---\n\n- `Role.users()`\n- `users`\n\n---\n\n- `Role.users([STATUS])`\n- `users/[STATUS]`\n\n---\n\n- `Role.user([USER_ID])`\n- `user:[USER_ID]`\n\n---\n\n- `Role.user([USER_ID], [STATUS])`\n- `user:[USER_ID]/[STATUS]`\n\n---\n\n- `Role.team([TEAM_ID])`\n- `team:[TEAM_ID]`\n\n---\n\n- `Role.team([TEAM_ID], [ROLE])`\n- `team:[TEAM_ID]/[ROLE]`\n\n---\n\n- `Role.member([MEMBERSHIP_ID])`\n- `member:[MEMBERSHIP_ID]`\n \n{% /table %}\n\n# Unique ID {% #unique-id %}\n\nAppwrite'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.\n\n# Queries {% #queries %}\n\nAppwrite's SDKs provide a `Query` class to generate JSON query strings.\nWhen using Appwrite without an SDK, you can template your own JSON strings.\nYou can discover the query methods available in the [Queries page.](/docs/products/databases/queries)\n\n## Query string format {% #queries-string-format %}\n\nAppwrite Queries are escaped JSON strings, which look like this.\n\n```json\n\"{\\\"method\\\":\\\"equal\\\",\\\"column\\\":\\\"name\\\",\\\"values\\\":[\\\"John\\\"]}\"\n```\n\nQuery strings are passed to Appwrite using the `queries` parameter.\nYou can attach multiple query strings by including the array parameter multiple times in the query string: `queries[]=\"...\"&queries[]=\"...\"`\n\nFor example, the unescaped query string might look like this.\n\n```text\n?queries[0]={\"method\":\"equal\",\"column\":\"name\",\"values\":[\"John\"]}&queries[1]={\"method\":\"limit\",\"values\":[6]}\n```\n\nThe JSON has a general format like this.\n\n```json\n{\n \"method\": \"\",\n \"column\": \"\",\n \"values\": [\n ,\n ,\n ...\n ]\n}\n```\n\n{% info title=\"Best practice\" %}\nWhen 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.\nWhile 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.\n{% /info %}\n\nFor example, to query for all rows with the name \"John\" or \"Jane\", the query string would look like this.\n\n```json\n{\n \"method\": \"equal\",\n \"column\": \"name\",\n \"values\": [\"John\", \"Jane\"]\n}\n```\n\nHere are some more examples of the JSON query format.\nWhen in doubt, you can use the Appwrite SDKs to generate the query strings for you.\n\n```json\n{\n \"method\": \"isNull\",\n \"column\": \"name\"\n}\n{\n \"method\": \"select\",\n \"values\": [\"name\", \"age\", \"email\"]\n}\n{\n \"method\": \"between\",\n \"column\": \"age\",\n \"values\": [18, 30]\n}\n{\n \"method\": \"cursorAfter\",\n \"values\": [\"rowId\"]\n}\n```\n\n## Query nesting {% #query-nesting %}\n\nSome Appwrite query methods, like `and` and `or`, allow you to nest queries.\nWhen using Appwrite without an SDK, you can template your own JSON strings.\n\nIn these cases, `column` is empty and `values` is an array of queries.\n\n```json\n{\n \"method\": \"and\",\n \"values\": [\n {\n \"method\": \"equal\",\n \"column\": \"name\",\n \"values\": [\"John\"]\n },\n {\n \"method\": \"between\",\n \"column\": \"age\",\n \"values\": [20, 30]\n }\n ]\n}\n```\n\n# Rate limits {% #rate-limits %}\n\nAppwrite'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.\n\n[Learn more about Rate Limits](/docs/advanced/security/rate-limits).\n\n# Specifications {% #specifications %}\n\nAppwrite 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.\n\n[Find the REST API specification for your Appwrite version](https://github.com/appwrite/appwrite/tree/master/app/config/specs)."}, {"path": "docs/apis/webhooks", "title": "Webhooks", "description": "Leverage webhooks in the Appwrite platform for real-time updates. Learn how to configure, manage, and integrate webhooks to keep your applications in sync.", "content": "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.\n\n# Getting started {% #getting-started %}\n\nTo add a webhook from the Appwrite Console:\n\n1. Navigate to your project's **Settings** page.\n2. Select the **Webhooks** tab.\n3. Click **Create webhook**.\n4. Enter a **Name** and the **POST URL** for your webhook endpoint.\n5. Click **Add an event** to choose the [events](#events) that should trigger the webhook.\n6. Optionally, enable **Certificate verification (SSL/TLS)** and set **HTTP authentication** credentials to secure your endpoint.\n7. Click **Create webhook**.\n\n{% only_dark %}\n![Create webhook screen](/images/docs/platform/dark/create-webhook.avif)\n{% /only_dark %}\n{% only_light %}\n![Create webhook screen](/images/docs/platform/create-webhook.avif)\n{% /only_light %}\n\n# Manage webhooks with a Server SDK {% #manage-webhooks-with-a-server-sdk %}\n\nYou can also manage webhooks programmatically using a Server SDK. This requires an API key with the `webhooks.read` and `webhooks.write` [scopes](/docs/advanced/security/api-keys#scopes).\n\n## Create a webhook {% #create-a-webhook %}\n\nThe 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.\n\n{% multicode %}\n```server-nodejs\nimport { Client, Webhooks, ID } from 'node-appwrite';\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nconst webhooks = new Webhooks(client);\n\nconst result = await webhooks.create({\n webhookId: ID.unique(),\n url: 'https://example.com/webhook',\n name: 'My Webhook',\n events: ['users.*.create'],\n tls: true,\n secret: '' // optional\n});\n\nconsole.log(result.secret); // store it now, not returned again\n```\n```server-deno\nimport { Client, Webhooks, ID } from \"npm:node-appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nconst webhooks = new Webhooks(client);\n\nconst result = await webhooks.create({\n webhookId: ID.unique(),\n url: 'https://example.com/webhook',\n name: 'My Webhook',\n events: ['users.*.create'],\n tls: true,\n secret: '' // optional\n});\n\nconsole.log(result.secret); // store it now, not returned again\n```\n```server-php\nsetEndpoint('https://.cloud.appwrite.io/v1')\n ->setProject('')\n ->setKey('');\n\n$webhooks = new Webhooks($client);\n\n$result = $webhooks->create(\n webhookId: ID::unique(),\n url: 'https://example.com/webhook',\n name: 'My Webhook',\n events: ['users.*.create'],\n tls: true,\n secret: '' // optional\n);\n\necho $result->secret; // store it now, not returned again\n```\n```server-python\nfrom appwrite.client import Client\nfrom appwrite.id import ID\nfrom appwrite.services.webhooks import Webhooks\n\nclient = Client()\nclient.set_endpoint('https://.cloud.appwrite.io/v1')\nclient.set_project('')\nclient.set_key('')\n\nwebhooks = Webhooks(client)\n\nresult = webhooks.create(\n webhook_id=ID.unique(),\n url='https://example.com/webhook',\n name='My Webhook',\n events=['users.*.create'],\n tls=True,\n secret='' # optional\n)\n\nprint(result.secret) # store it now, not returned again\n```\n```server-ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://.cloud.appwrite.io/v1')\n .set_project('')\n .set_key('')\n\nwebhooks = Webhooks.new(client)\n\nresponse = webhooks.create(\n webhook_id: ID.unique(),\n url: 'https://example.com/webhook',\n name: 'My Webhook',\n events: ['users.*.create'],\n tls: true,\n secret: '' # optional\n)\n\nputs response.secret # store it now, not returned again\n```\n```server-dotnet\nusing Appwrite;\nusing Appwrite.Services;\nusing Appwrite.Models;\n\nClient client = new Client()\n .SetEndPoint(\"https://.cloud.appwrite.io/v1\")\n .SetProject(\"\")\n .SetKey(\"\");\n\nWebhooks webhooks = new Webhooks(client);\n\nvar result = await webhooks.Create(\n webhookId: ID.Unique(),\n url: \"https://example.com/webhook\",\n name: \"My Webhook\",\n events: new List {\"users.*.create\"},\n tls: true,\n secret: \"\" // optional\n);\n\nConsole.WriteLine(result.Secret); // store it now, not returned again\n```\n```server-dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\n\nClient client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nWebhooks webhooks = Webhooks(client);\n\nfinal result = await webhooks.create(\n webhookId: ID.unique(),\n url: 'https://example.com/webhook',\n name: 'My Webhook',\n events: ['users.*.create'],\n tls: true,\n secret: '', // optional\n);\n\nprint(result.secret); // store it now, not returned again\n```\n```server-kotlin\nimport io.appwrite.Client\nimport io.appwrite.ID\nimport io.appwrite.services.Webhooks\n\nval client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\")\n\nval webhooks = Webhooks(client)\n\nval result = webhooks.create(\n webhookId = ID.unique(),\n url = \"https://example.com/webhook\",\n name = \"My Webhook\",\n events = listOf(\"users.*.create\"),\n tls = true,\n secret = \"\" // optional\n)\n\nprintln(result.secret) // store it now, not returned again\n```\n```server-java\nimport io.appwrite.Client;\nimport io.appwrite.ID;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Webhooks;\n\nClient client = new Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\");\n\nWebhooks webhooks = new Webhooks(client);\n\nwebhooks.create(\n ID.unique(), // webhookId\n \"https://example.com/webhook\", // url\n \"My Webhook\", // name\n List.of(\"users.*.create\"), // events\n true, // enabled\n true, // tls\n null, // authUsername\n null, // authPassword\n \"\", // secret (optional)\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(result.getSecret()); // store it now, not returned again\n })\n);\n```\n```server-swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\")\n\nlet webhooks = Webhooks(client)\n\nlet result = try await webhooks.create(\n webhookId: ID.unique(),\n url: \"https://example.com/webhook\",\n name: \"My Webhook\",\n events: [\"users.*.create\"],\n tls: true,\n secret: \"\" // optional\n)\n\nprint(result.secret) // store it now, not returned again\n```\n```server-go\npackage main\n\nimport (\n \"fmt\"\n \"github.com/appwrite/sdk-for-go/appwrite\"\n \"github.com/appwrite/sdk-for-go/id\"\n)\n\nfunc main() {\n client := appwrite.NewClient(\n appwrite.WithEndpoint(\"https://.cloud.appwrite.io/v1\"),\n appwrite.WithProject(\"\"),\n appwrite.WithKey(\"\"),\n )\n\n webhooks := appwrite.NewWebhooks(client)\n result, err := webhooks.Create(\n id.Unique(),\n \"https://example.com/webhook\",\n \"My Webhook\",\n []string{\"users.*.create\"},\n appwrite.WithCreateSecret(\"\"), // optional\n )\n\n if err != nil {\n panic(err)\n }\n\n fmt.Println(result.Secret) // store it now, not returned again\n}\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::id::ID;\nuse appwrite::services::Webhooks;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box> {\n let client = Client::new()\n .set_endpoint(\"https://.cloud.appwrite.io/v1\")\n .set_project(\"\")\n .set_key(\"\");\n\n let webhooks = Webhooks::new(&client);\n\n let result = webhooks.create(\n ID::unique(), // webhook_id\n \"https://example.com/webhook\", // url\n \"My Webhook\", // name\n vec![\"users.*.create\"], // events\n Some(true), // enabled\n Some(true), // tls\n None, // auth_username\n None, // auth_password\n Some(\"\"), // secret (optional)\n ).await?;\n\n println!(\"{}\", result.secret); // store it now, not returned again\n Ok(())\n}\n```\n{% /multicode %}\n\n## List webhooks {% #list-webhooks %}\n\n{% multicode %}\n```server-nodejs\nimport { Client, Webhooks } from 'node-appwrite';\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nconst webhooks = new Webhooks(client);\n\nconst result = await webhooks.list({\n queries: [], // optional\n total: false // optional\n});\n```\n```server-deno\nimport { Client, Webhooks } from \"npm:node-appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nconst webhooks = new Webhooks(client);\n\nconst result = await webhooks.list({\n queries: [], // optional\n total: false // optional\n});\n```\n```server-php\nsetEndpoint('https://.cloud.appwrite.io/v1')\n ->setProject('')\n ->setKey('');\n\n$webhooks = new Webhooks($client);\n\n$result = $webhooks->list(\n queries: [], // optional\n total: false // optional\n);\n```\n```server-python\nfrom appwrite.client import Client\nfrom appwrite.services.webhooks import Webhooks\n\nclient = Client()\nclient.set_endpoint('https://.cloud.appwrite.io/v1')\nclient.set_project('')\nclient.set_key('')\n\nwebhooks = Webhooks(client)\n\nresult = webhooks.list(\n queries=[], # optional\n total=False # optional\n)\n```\n```server-ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://.cloud.appwrite.io/v1')\n .set_project('')\n .set_key('')\n\nwebhooks = Webhooks.new(client)\n\nresponse = webhooks.list(\n queries: [], # optional\n total: false # optional\n)\n```\n```server-dotnet\nusing Appwrite;\nusing Appwrite.Services;\nusing Appwrite.Models;\n\nClient client = new Client()\n .SetEndPoint(\"https://.cloud.appwrite.io/v1\")\n .SetProject(\"\")\n .SetKey(\"\");\n\nWebhooks webhooks = new Webhooks(client);\n\nvar result = await webhooks.List(\n queries: new List(), // optional\n total: false // optional\n);\n```\n```server-dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\n\nClient client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nWebhooks webhooks = Webhooks(client);\n\nfinal result = await webhooks.list(\n queries: [], // optional\n total: false, // optional\n);\n```\n```server-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Webhooks\n\nval client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\")\n\nval webhooks = Webhooks(client)\n\nval result = webhooks.list(\n queries = listOf(), // optional\n total = false // optional\n)\n```\n```server-java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Webhooks;\n\nClient client = new Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\");\n\nWebhooks webhooks = new Webhooks(client);\n\nwebhooks.list(\n List.of(), // queries (optional)\n false, // total (optional)\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n System.out.println(result);\n })\n);\n```\n```server-swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\")\n\nlet webhooks = Webhooks(client)\n\nlet result = try await webhooks.list(\n queries: [], // optional\n total: false // optional\n)\n```\n```server-go\npackage main\n\nimport (\n \"fmt\"\n \"github.com/appwrite/sdk-for-go/appwrite\"\n)\n\nfunc main() {\n client := appwrite.NewClient(\n appwrite.WithEndpoint(\"https://.cloud.appwrite.io/v1\"),\n appwrite.WithProject(\"\"),\n appwrite.WithKey(\"\"),\n )\n\n webhooks := appwrite.NewWebhooks(client)\n result, err := webhooks.List()\n\n if err != nil {\n panic(err)\n }\n\n fmt.Println(result)\n}\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::Webhooks;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box> {\n let client = Client::new()\n .set_endpoint(\"https://.cloud.appwrite.io/v1\")\n .set_project(\"\")\n .set_key(\"\");\n\n let webhooks = Webhooks::new(&client);\n\n let result = webhooks.list(\n Some(vec![]), // queries (optional)\n Some(false), // total (optional)\n ).await?;\n\n println!(\"{:?}\", result);\n Ok(())\n}\n```\n{% /multicode %}\n\n## Get a webhook {% #get-a-webhook %}\n\n{% multicode %}\n```server-nodejs\nimport { Client, Webhooks } from 'node-appwrite';\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nconst webhooks = new Webhooks(client);\n\nconst result = await webhooks.get({\n webhookId: ''\n});\n```\n```server-deno\nimport { Client, Webhooks } from \"npm:node-appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nconst webhooks = new Webhooks(client);\n\nconst result = await webhooks.get({\n webhookId: ''\n});\n```\n```server-php\nsetEndpoint('https://.cloud.appwrite.io/v1')\n ->setProject('')\n ->setKey('');\n\n$webhooks = new Webhooks($client);\n\n$result = $webhooks->get(\n webhookId: ''\n);\n```\n```server-python\nfrom appwrite.client import Client\nfrom appwrite.services.webhooks import Webhooks\n\nclient = Client()\nclient.set_endpoint('https://.cloud.appwrite.io/v1')\nclient.set_project('')\nclient.set_key('')\n\nwebhooks = Webhooks(client)\n\nresult = webhooks.get(\n webhook_id=''\n)\n```\n```server-ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://.cloud.appwrite.io/v1')\n .set_project('')\n .set_key('')\n\nwebhooks = Webhooks.new(client)\n\nresponse = webhooks.get(\n webhook_id: ''\n)\n```\n```server-dotnet\nusing Appwrite;\nusing Appwrite.Services;\nusing Appwrite.Models;\n\nClient client = new Client()\n .SetEndPoint(\"https://.cloud.appwrite.io/v1\")\n .SetProject(\"\")\n .SetKey(\"\");\n\nWebhooks webhooks = new Webhooks(client);\n\nvar result = await webhooks.Get(\n webhookId: \"\"\n);\n```\n```server-dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\n\nClient client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nWebhooks webhooks = Webhooks(client);\n\nfinal result = await webhooks.get(\n webhookId: '',\n);\n```\n```server-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Webhooks\n\nval client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\")\n\nval webhooks = Webhooks(client)\n\nval result = webhooks.get(\n webhookId = \"\"\n)\n```\n```server-java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Webhooks;\n\nClient client = new Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\");\n\nWebhooks webhooks = new Webhooks(client);\n\nwebhooks.get(\n \"\", // webhookId\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n System.out.println(result);\n })\n);\n```\n```server-swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\")\n\nlet webhooks = Webhooks(client)\n\nlet result = try await webhooks.get(\n webhookId: \"\"\n)\n```\n```server-go\npackage main\n\nimport (\n \"fmt\"\n \"github.com/appwrite/sdk-for-go/appwrite\"\n)\n\nfunc main() {\n client := appwrite.NewClient(\n appwrite.WithEndpoint(\"https://.cloud.appwrite.io/v1\"),\n appwrite.WithProject(\"\"),\n appwrite.WithKey(\"\"),\n )\n\n webhooks := appwrite.NewWebhooks(client)\n result, err := webhooks.Get(\n \"\",\n )\n\n if err != nil {\n panic(err)\n }\n\n fmt.Println(result)\n}\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::Webhooks;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box> {\n let client = Client::new()\n .set_endpoint(\"https://.cloud.appwrite.io/v1\")\n .set_project(\"\")\n .set_key(\"\");\n\n let webhooks = Webhooks::new(&client);\n\n let result = webhooks.get(\n \"\", // webhook_id\n ).await?;\n\n println!(\"{:?}\", result);\n Ok(())\n}\n```\n{% /multicode %}\n\n## Update a webhook {% #update-a-webhook %}\n\n{% multicode %}\n```server-nodejs\nimport { Client, Webhooks } from 'node-appwrite';\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nconst webhooks = new Webhooks(client);\n\nconst result = await webhooks.update({\n webhookId: '',\n name: 'Updated Webhook',\n url: 'https://example.com/webhook-updated',\n events: ['users.*.update'],\n tls: true\n});\n```\n```server-deno\nimport { Client, Webhooks } from \"npm:node-appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nconst webhooks = new Webhooks(client);\n\nconst result = await webhooks.update({\n webhookId: '',\n name: 'Updated Webhook',\n url: 'https://example.com/webhook-updated',\n events: ['users.*.update'],\n tls: true\n});\n```\n```server-php\nsetEndpoint('https://.cloud.appwrite.io/v1')\n ->setProject('')\n ->setKey('');\n\n$webhooks = new Webhooks($client);\n\n$result = $webhooks->update(\n webhookId: '',\n name: 'Updated Webhook',\n url: 'https://example.com/webhook-updated',\n events: ['users.*.update'],\n tls: true\n);\n```\n```server-python\nfrom appwrite.client import Client\nfrom appwrite.services.webhooks import Webhooks\n\nclient = Client()\nclient.set_endpoint('https://.cloud.appwrite.io/v1')\nclient.set_project('')\nclient.set_key('')\n\nwebhooks = Webhooks(client)\n\nresult = webhooks.update(\n webhook_id='',\n name='Updated Webhook',\n url='https://example.com/webhook-updated',\n events=['users.*.update'],\n tls=True\n)\n```\n```server-ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://.cloud.appwrite.io/v1')\n .set_project('')\n .set_key('')\n\nwebhooks = Webhooks.new(client)\n\nresponse = webhooks.update(\n webhook_id: '',\n name: 'Updated Webhook',\n url: 'https://example.com/webhook-updated',\n events: ['users.*.update'],\n tls: true\n)\n```\n```server-dotnet\nusing Appwrite;\nusing Appwrite.Services;\nusing Appwrite.Models;\n\nClient client = new Client()\n .SetEndPoint(\"https://.cloud.appwrite.io/v1\")\n .SetProject(\"\")\n .SetKey(\"\");\n\nWebhooks webhooks = new Webhooks(client);\n\nvar result = await webhooks.Update(\n webhookId: \"\",\n name: \"Updated Webhook\",\n url: \"https://example.com/webhook-updated\",\n events: new List {\"users.*.update\"},\n tls: true\n);\n```\n```server-dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\n\nClient client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nWebhooks webhooks = Webhooks(client);\n\nfinal result = await webhooks.update(\n webhookId: '',\n name: 'Updated Webhook',\n url: 'https://example.com/webhook-updated',\n events: ['users.*.update'],\n tls: true,\n);\n```\n```server-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Webhooks\n\nval client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\")\n\nval webhooks = Webhooks(client)\n\nval result = webhooks.update(\n webhookId = \"\",\n name = \"Updated Webhook\",\n url = \"https://example.com/webhook-updated\",\n events = listOf(\"users.*.update\"),\n tls = true\n)\n```\n```server-java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Webhooks;\n\nClient client = new Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\");\n\nWebhooks webhooks = new Webhooks(client);\n\nwebhooks.update(\n \"\", // webhookId\n \"Updated Webhook\", // name\n \"https://example.com/webhook-updated\", // url\n List.of(\"users.*.update\"), // events\n true, // enabled\n true, // tls\n null, // authUsername\n null, // authPassword\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n System.out.println(result);\n })\n);\n```\n```server-swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\")\n\nlet webhooks = Webhooks(client)\n\nlet result = try await webhooks.update(\n webhookId: \"\",\n name: \"Updated Webhook\",\n url: \"https://example.com/webhook-updated\",\n events: [\"users.*.update\"],\n tls: true\n)\n```\n```server-go\npackage main\n\nimport (\n \"fmt\"\n \"github.com/appwrite/sdk-for-go/appwrite\"\n)\n\nfunc main() {\n client := appwrite.NewClient(\n appwrite.WithEndpoint(\"https://.cloud.appwrite.io/v1\"),\n appwrite.WithProject(\"\"),\n appwrite.WithKey(\"\"),\n )\n\n webhooks := appwrite.NewWebhooks(client)\n result, err := webhooks.Update(\n \"\",\n \"Updated Webhook\",\n \"https://example.com/webhook-updated\",\n []string{\"users.*.update\"},\n )\n\n if err != nil {\n panic(err)\n }\n\n fmt.Println(result)\n}\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::Webhooks;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box> {\n let client = Client::new()\n .set_endpoint(\"https://.cloud.appwrite.io/v1\")\n .set_project(\"\")\n .set_key(\"\");\n\n let webhooks = Webhooks::new(&client);\n\n let result = webhooks.update(\n \"\", // webhook_id\n \"Updated Webhook\", // name\n \"https://example.com/webhook-updated\", // url\n vec![\"users.*.update\"], // events\n Some(true), // enabled\n Some(true), // tls\n None, // auth_username\n None, // auth_password\n ).await?;\n\n println!(\"{:?}\", result);\n Ok(())\n}\n```\n{% /multicode %}\n\n## Delete a webhook {% #delete-a-webhook %}\n\n{% multicode %}\n```server-nodejs\nimport { Client, Webhooks } from 'node-appwrite';\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nconst webhooks = new Webhooks(client);\n\nawait webhooks.delete({\n webhookId: ''\n});\n```\n```server-deno\nimport { Client, Webhooks } from \"npm:node-appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nconst webhooks = new Webhooks(client);\n\nawait webhooks.delete({\n webhookId: ''\n});\n```\n```server-php\nsetEndpoint('https://.cloud.appwrite.io/v1')\n ->setProject('')\n ->setKey('');\n\n$webhooks = new Webhooks($client);\n\n$webhooks->delete(\n webhookId: ''\n);\n```\n```server-python\nfrom appwrite.client import Client\nfrom appwrite.services.webhooks import Webhooks\n\nclient = Client()\nclient.set_endpoint('https://.cloud.appwrite.io/v1')\nclient.set_project('')\nclient.set_key('')\n\nwebhooks = Webhooks(client)\n\nwebhooks.delete(\n webhook_id=''\n)\n```\n```server-ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://.cloud.appwrite.io/v1')\n .set_project('')\n .set_key('')\n\nwebhooks = Webhooks.new(client)\n\nwebhooks.delete(\n webhook_id: ''\n)\n```\n```server-dotnet\nusing Appwrite;\nusing Appwrite.Services;\n\nClient client = new Client()\n .SetEndPoint(\"https://.cloud.appwrite.io/v1\")\n .SetProject(\"\")\n .SetKey(\"\");\n\nWebhooks webhooks = new Webhooks(client);\n\nawait webhooks.Delete(\n webhookId: \"\"\n);\n```\n```server-dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\n\nClient client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nWebhooks webhooks = Webhooks(client);\n\nawait webhooks.delete(\n webhookId: '',\n);\n```\n```server-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Webhooks\n\nval client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\")\n\nval webhooks = Webhooks(client)\n\nwebhooks.delete(\n webhookId = \"\"\n)\n```\n```server-java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Webhooks;\n\nClient client = new Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\");\n\nWebhooks webhooks = new Webhooks(client);\n\nwebhooks.delete(\n \"\", // webhookId\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n System.out.println(result);\n })\n);\n```\n```server-swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\")\n\nlet webhooks = Webhooks(client)\n\ntry await webhooks.delete(\n webhookId: \"\"\n)\n```\n```server-go\npackage main\n\nimport (\n \"fmt\"\n \"github.com/appwrite/sdk-for-go/appwrite\"\n)\n\nfunc main() {\n client := appwrite.NewClient(\n appwrite.WithEndpoint(\"https://.cloud.appwrite.io/v1\"),\n appwrite.WithProject(\"\"),\n appwrite.WithKey(\"\"),\n )\n\n webhooks := appwrite.NewWebhooks(client)\n _, err := webhooks.Delete(\n \"\",\n )\n\n if err != nil {\n panic(err)\n }\n\n fmt.Println(\"Webhook deleted\")\n}\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::Webhooks;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box> {\n let client = Client::new()\n .set_endpoint(\"https://.cloud.appwrite.io/v1\")\n .set_project(\"\")\n .set_key(\"\");\n\n let webhooks = Webhooks::new(&client);\n\n webhooks.delete(\n \"\",\n ).await?;\n\n Ok(())\n}\n```\n{% /multicode %}\n\n## Rotate signing key {% #rotate-signing-key %}\n\nCalling `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.\n\n{% multicode %}\n```server-nodejs\nimport { Client, Webhooks } from 'node-appwrite';\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nconst webhooks = new Webhooks(client);\n\nconst result = await webhooks.updateSecret({\n webhookId: '',\n secret: '' // optional\n});\n```\n```server-deno\nimport { Client, Webhooks } from \"npm:node-appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nconst webhooks = new Webhooks(client);\n\nconst result = await webhooks.updateSecret({\n webhookId: '',\n secret: '' // optional\n});\n```\n```server-php\nsetEndpoint('https://.cloud.appwrite.io/v1')\n ->setProject('')\n ->setKey('');\n\n$webhooks = new Webhooks($client);\n\n$result = $webhooks->updateSecret(\n webhookId: '',\n secret: '' // optional\n);\n```\n```server-python\nfrom appwrite.client import Client\nfrom appwrite.services.webhooks import Webhooks\n\nclient = Client()\nclient.set_endpoint('https://.cloud.appwrite.io/v1')\nclient.set_project('')\nclient.set_key('')\n\nwebhooks = Webhooks(client)\n\nresult = webhooks.update_secret(\n webhook_id='',\n secret='' # optional\n)\n```\n```server-ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://.cloud.appwrite.io/v1')\n .set_project('')\n .set_key('')\n\nwebhooks = Webhooks.new(client)\n\nresponse = webhooks.update_secret(\n webhook_id: '',\n secret: '' # optional\n)\n```\n```server-dotnet\nusing Appwrite;\nusing Appwrite.Services;\n\nClient client = new Client()\n .SetEndPoint(\"https://.cloud.appwrite.io/v1\")\n .SetProject(\"\")\n .SetKey(\"\");\n\nWebhooks webhooks = new Webhooks(client);\n\nvar result = await webhooks.UpdateSecret(\n webhookId: \"\",\n secret: \"\" // optional\n);\n```\n```server-dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\n\nClient client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nWebhooks webhooks = Webhooks(client);\n\nfinal result = await webhooks.updateSecret(\n webhookId: '',\n secret: '', // optional\n);\n```\n```server-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Webhooks\n\nval client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\")\n\nval webhooks = Webhooks(client)\n\nval result = webhooks.updateSecret(\n webhookId = \"\",\n secret = \"\" // optional\n)\n```\n```server-java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Webhooks;\n\nClient client = new Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\");\n\nWebhooks webhooks = new Webhooks(client);\n\nwebhooks.updateSecret(\n \"\", // webhookId\n \"\", // secret (optional)\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n System.out.println(result);\n })\n);\n```\n```server-swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\")\n\nlet webhooks = Webhooks(client)\n\nlet result = try await webhooks.updateSecret(\n webhookId: \"\",\n secret: \"\" // optional\n)\n```\n```server-go\npackage main\n\nimport (\n \"fmt\"\n \"github.com/appwrite/sdk-for-go/appwrite\"\n)\n\nfunc main() {\n client := appwrite.NewClient(\n appwrite.WithEndpoint(\"https://.cloud.appwrite.io/v1\"),\n appwrite.WithProject(\"\"),\n appwrite.WithKey(\"\"),\n )\n\n webhooks := appwrite.NewWebhooks(client)\n result, err := webhooks.UpdateSecret(\n \"\",\n appwrite.WithUpdateSecretSecret(\"\"), // optional\n )\n\n if err != nil {\n panic(err)\n }\n\n fmt.Println(result)\n}\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::Webhooks;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box> {\n let client = Client::new()\n .set_endpoint(\"https://.cloud.appwrite.io/v1\")\n .set_project(\"\")\n .set_key(\"\");\n\n let webhooks = Webhooks::new(&client);\n\n let result = webhooks.update_secret(\n \"\", // webhook_id\n Some(\"\") // optional\n ).await?;\n\n println!(\"{:?}\", result);\n Ok(())\n}\n```\n{% /multicode %}\n\n# Payload {% #payload %}\n\nEach 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).\n\n# Headers {% #headers %}\n\nHTTP requests made to your webhook's configured URL endpoint will contain several special headers.\n\n| Header | Description |\n|--------|-------------|\n| X-Appwrite-Webhook-Id | The ID of the Webhook who triggered the event. |\n| X-Appwrite-Webhook-Events | Names of the events that triggered this delivery. |\n| X-Appwrite-Webhook-Name | Name of the webhook as specified in your app settings and [events list](/docs/apis/events). |\n| 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. |\n| X-Appwrite-Webhook-Project-Id | The ID of the project who owns the Webhook and API call. |\n| X-Appwrite-Webhook-Signature | The HMAC-SHA1 signature of the payload. This is used to verify the authenticity of the payload. |\n| User-Agent | Each request made by Appwrite will be 'Appwrite-Server'. |\n\n# Verification {% #verification %}\n\nYou 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.\n\nTo verify the signature:\n\n1. Concatenate the **webhook URL** and the **request body** (no spaces in between).\n2. Generate an HMAC-SHA1 hash of the concatenated string using your webhook's **signature key**.\n3. Base64 encode the resulting hash.\n4. Compare the result to the `X-Appwrite-Webhook-Signature` header value. If they match, the payload is authentic.\n\n{% multicode %}\n```server-nodejs\nimport crypto from 'crypto';\n\nfunction verifyWebhook(req, signatureKey) {\n const url = 'https://example.com/webhook'; // Your webhook URL\n const payload = req.body; // Raw request body as string\n const signature = req.headers['x-appwrite-webhook-signature'];\n\n const generated = crypto\n .createHmac('sha1', signatureKey)\n .update(url + payload)\n .digest('base64');\n\n return crypto.timingSafeEqual(\n Buffer.from(generated),\n Buffer.from(signature)\n );\n}\n```\n```server-python\nimport hmac\nimport hashlib\nimport base64\n\ndef verify_webhook(url, payload, signature_key, signature):\n generated = base64.b64encode(\n hmac.new(\n signature_key.encode(),\n (url + payload).encode(),\n hashlib.sha1\n ).digest()\n ).decode()\n\n return hmac.compare_digest(generated, signature)\n```\n```server-php\n Bool {\n let key = SymmetricKey(data: Data(signatureKey.utf8))\n let data = Data((url + payload).utf8)\n let mac = HMAC.authenticationCode(for: data, using: key)\n let generated = Data(mac).base64EncodedString()\n\n guard generated.count == signature.count else { return false }\n var diff: UInt8 = 0\n for (a, b) in zip(generated.utf8, signature.utf8) {\n diff |= a ^ b\n }\n return diff == 0\n}\n```\n```server-dotnet\nusing System.Security.Cryptography;\nusing System.Text;\n\nbool VerifyWebhook(\n string url, string payload, string signatureKey, string signature)\n{\n using var hmac = new HMACSHA1(Encoding.UTF8.GetBytes(signatureKey));\n var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(url + payload));\n var generated = Convert.ToBase64String(hash);\n\n return CryptographicOperations.FixedTimeEquals(\n Encoding.UTF8.GetBytes(generated),\n Encoding.UTF8.GetBytes(signature)\n );\n}\n```\n```server-rust\nuse hmac::{Hmac, Mac};\nuse sha1::Sha1;\nuse base64::Engine;\nuse base64::engine::general_purpose::STANDARD;\nuse subtle::ConstantTimeEq;\n\nfn verify_webhook(\n url: &str, payload: &str, signature_key: &str, signature: &str\n) -> bool {\n let mut mac = Hmac::::new_from_slice(\n signature_key.as_bytes()\n ).expect(\"HMAC key error\");\n mac.update(format!(\"{}{}\", url, payload).as_bytes());\n let generated = STANDARD.encode(mac.finalize().into_bytes());\n\n generated.as_bytes().ct_eq(signature.as_bytes()).into()\n}\n```\n{% /multicode %}\n\n\n# Events {% #events %}\n\nAppwrite has events that fire when a resource changes.\nThese events cover all Appwrite resources and can reflect create, update, and delete actions.\nYou can specify one or many events to subscribe to with webhooks.\n\n{% accordion %}\n{% accordion_item title=\"Authentication events\" %}\n{% partial file=\"auth-events.md\" /%}\n{% /accordion_item %}\n{% accordion_item title=\"Databases events\" %}\n{% partial file=\"databases-events.md\" /%}\n{% /accordion_item %}\n{% accordion_item title=\"Storage events\" %}\n{% partial file=\"storage-events.md\" /%}\n{% /accordion_item %}\n{% accordion_item title=\"Functions events\" %}\n{% partial file=\"functions-events.md\" /%}\n{% /accordion_item %}\n{% accordion_item title=\"Messaging events\" %}\n{% partial file=\"messaging-events.md\" /%}\n{% /accordion_item %}\n{% /accordion %}\n\n[Learn more about events](/docs/apis/events)"}, {"path": "docs/products/ai", "title": "Artificial intelligence", "description": "Learn how to implement machine learning models in your applications.", "content": "Appwrite allows you to build powerful AI powered applications with ease. Leverage Appwrite's\npowerful functions architecture and start building the future.\n\n# Explore capabilities {% #explore-capabilities %}\n\nDetailed explanations and deep dives into how you can implement different machine techniques in your Appwrite projects.\n{% cards %}\n{% cards_image_item href=\"/docs/products/ai/computer-vision\" title=\"Computer vision\" light=\"/images/docs/ai/computer-vision-light.avif\" dark=\"/images/docs/ai/computer-vision-dark.avif\" %}\nLabel and understand the contents of images\n{% /cards_image_item %}\n{% cards_image_item href=\"/docs/products/ai/natural-language\" title=\"Natural language processing\" light=\"/images/docs/ai/natural-language-light.avif\" dark=\"/images/docs/ai/natural-language-dark.avif\" %}\nUnderstand and generate human language\n{% /cards_image_item %}\n{% cards_image_item href=\"/docs/products/ai/audio-processing\" title=\"Audio processing\" light=\"/images/docs/ai/audio-processing-light.avif\" dark=\"/images/docs/ai/audio-processing-dark.avif\" %}\nProcess and generate audio data\n{% /cards_image_item %}\n{% /cards %}\n\n# Show me some code {% #show-me-some-code %}\nIf you learn best from code examples, follow one of our tutorials.\n\n### Computer vision\n{% cards %}\n{% cards_item href=\"/docs/products/ai/tutorials/image-classification\" title=\"Image classification\" %}\nUnderstand and label the contents of images\n{% /cards_item %}\n{% cards_item href=\"/docs/products/ai/tutorials/object-detection\" title=\"Object detection\" %}\nDetect and label objects in images\n{% /cards_item %}\n{% /cards %}\n\n### Natural language\n{% cards %}\n{% cards_item href=\"/docs/products/ai/tutorials/text-generation\" title=\"Text generation\" %}\nGenerate human-like text\n{% /cards_item %}\n{% cards_item href=\"/docs/products/ai/tutorials/language-translation\" title=\"Language translation\" %}\nTranslate text between languages\n{% /cards_item %}\n{% /cards %}\n\n### Audio processing\n{% cards %}\n{% cards_item href=\"/docs/products/ai/tutorials/speech-recognition\" title=\"Speech recognition\" %}\nProcess speech audio into text\n{% /cards_item %}\n{% cards_item href=\"/docs/products/ai/tutorials/text-to-speech\" title=\"Text to speech\" %}\nConvert text into speech\n{% /cards_item %}\n{% cards_item href=\"/docs/products/ai/tutorials/music-generation\" title=\"Music generation\" %}\nGenerate music from a text prompt\n{% /cards_item %}\n{% /cards %}"}, {"path": "docs/products/ai/audio-processing", "title": "Audio processing", "description": "Learn about the basics of audio processing, the most popular tasks and applications of audio processing with ML and how we can leverage Appwrite to build audio processing enabled applications.", "content": "Audio processing is a field of machine learning that deals with allowing machines to understand, analyze, and manipulate various audio signals.\nThe 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,\nmusic streaming services and for noise reduction in online calls.\n\n# Tutorials\n\n{% cards %}\n{% cards_item href=\"/docs/products/ai/tutorials/speech-recognition\" title=\"Speech recognition\" %}\nRecognize and transcribe spoken language into text\n{% /cards_item %}\n{% cards_item href=\"/docs/products/ai/tutorials/text-to-speech\" title=\"Text to speech\" %}\nConvert written text into spoken language\n{% /cards_item %}\n{% /cards %}"}, {"path": "docs/products/ai/computer-vision", "title": "Computer vision", "description": "Learn about the basics of computer vision, the most popular tasks and applications of computer vision and how we can leverage Appwrite to build computer vision enabled applications.", "content": "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.\n\n# Tutorials\n\n{% cards %}\n{% cards_item href=\"/docs/products/ai/tutorials/image-classification\" title=\"Image classification\" %}\nUnderstand and label the contents of images\n{% /cards_item %}\n{% cards_item href=\"/docs/products/ai/tutorials/object-detection\" title=\"Object detection\" %}\nDetect and label objects in images\n{% /cards_item %}\n{% /cards %}"}, {"path": "docs/products/ai/integrations/anyscale", "title": "Integrating Anyscale", "description": "Learn how to integrate Anyscale into your Appwrite project.", "content": "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.\n\nYou'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.\n\n# Prerequisites {% #prerequisites %}\n\n- An Appwrite Project\n- An [Anyscale API Key](https://app.endpoints.anyscale.com/credentials)\n\n{% section #step-1 step=1 title=\"Create new function\" %}\nHead to the [Appwrite Console](https://cloud.appwrite.io/console), click on **Functions** in the left sidebar and click the **Create Function** button.\n\n{% only_dark %}\n![Create function screen](/images/docs/functions/dark/template.avif)\n{% /only_dark %}\n\n{% only_light %}\n![Create function screen](/images/docs/functions/template.avif)\n{% /only_light %}\n\n1. In the Appwrite Console's sidebar, click **Functions**.\n1. Click **Create function**.\n1. Under **Connect Git repository**, select your provider.\n1. After connecting to GitHub, under **Quick start**, select the **Node.js** starter template.\n1. In the **Variables** step, add `ANYSCALE_API_KEY`. Generate your AnyScale key [here](https://app.endpoints.anyscale.com/credentials).\n1. Follow the step-by-step wizard and create the function.\n{% /section %}\n\n{% section #step-2 step=2 title=\"Add OpenAI SDK\" %}\nOnce the function is created, navigate to the freshly created repository and clone it to your local machine.\n\nInstall the `openai` package to simplify interacting with the AnyScale API, as it is an OpenAI-compatible API.\n\n```bash\nnpm install openai\n```\n{% /section %}\n\n{% section #step-3 step=3 title=\"Create utility function\" %}\nFor this example, the function can take both `GET` and `POST` requests.\n\nFor the `GET` request, return a static HTML page that will have a form to submit text to the API.\nMeanwhile, the `POST` request will send the text to the AnyScale API and return the generated text.\n\nWrite the code to return the static HTML page. To do this, create a new `src/utils.js` file with the following code:\n\n```js\nimport path from 'path';\nimport { fileURLToPath } from 'url';\nimport fs from 'fs';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\nconst staticFolder = path.join(__dirname, '../static');\n\nexport function getStaticFile(fileName) {\n return fs.readFileSync(path.join(staticFolder, fileName)).toString();\n}\n```\n{% /section %}\n\n{% section #step-4 step=4 title=\"Handle GET request\" %}\nWrite the `GET` request handler in the `src/main.js` file. This handler will return a static HTML page you'll create later.\n\n```js\nimport { getStaticFile } from './utils.js';\n\nexport default async ({ req, res, error }) => {\n if (req.method === 'GET') {\n return res.text(getStaticFile('index.html'), 200, {\n 'Content-Type': 'text/html; charset=utf-8',\n });\n }\n};\n```\n\nIf the method is `GET`, it returns the static HTML page.\n{% /section %}\n\n{% section #step-5 step=5 title=\"Create web page\" %}\nCreate an HTML web page that the function will serve. Create a new file at `static/index.html` with some HTML boilerplate:\n\n```html\n\n \n \n \n \n \n Anyscale Demo\n\n \n\n \n\n \n \n \n\n```\n\nThe 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.\n\nAfter the `` tag add a `` containing the visible form:\n\n```html\n\n
\n
\n
\n \n

Prompt Anyscale Demo

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

\n
\n \n \n
\n
\n \n \n
\n
\n\n { loading = true; answer = ''; try { answer = await onSubmit(prompt) } catch(err) { console.error(err); } finally { loading = false; } }\"\n >\n Submit\n \n
\n \n \n \n
\n\n```\n\nThe 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.\n{% /section %}\n\n{% section #step-6 step=6 title=\"Handle POST request\" %}\nAdd methods necessary to integrate with the Anyscale API.\n\nImport `openai` and the Appwrite SDK at the top of the `main.js` file.\n\n```js\nimport OpenAI from 'openai';\n```\n\nAdd code to validate the body of the request and initialize the Appwrite SDK after the `GET` request handler from earlier:\n\n```js\nif (!req.body.prompt && typeof req.body.prompt !== \"string\") {\n return res.json({ ok: false, error: \"Missing required field `prompt`\" }, 400);\n}\n\nconst openai = new OpenAI(\n {\n apiKey: process.env.ANYSCALE_API_KEY,\n baseURL: \"https://api.endpoints.anyscale.com/v1\"\n }\n);\n```\n\nMake a request to the Anyscale API and return the response:\n\n```js\ntry {\n const response = await openai.chat.completions.create({\n model: \"mistralai/Mixtral-8x7B-Instruct-v0.1\",\n max_tokens: parseInt(process.env.ANYSCALE_MAX_TOKENS ?? \"512\"),\n messages: [{ role: \"user\", content: req.body.prompt }],\n stream: false\n });\n const completion = response.choices[0].message?.content;\n return res.json({ ok: true, completion }, 200);\n} catch (err) {\n error(err);\n return res.json({ ok: false, error: \"Failed to query model.\" }, 500);\n}\n```\n{% /section %}\n\n{% section #step-8 step=8 title=\"Test the function\" %}\nNow that the function is deployed test it by visiting the function URL in your browser.\nThis 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.\n\n![Testing the function](/images/docs/ai/integrations/anyscale/demo.avif)\n{% /section %}"}, {"path": "docs/products/ai/integrations/elevenlabs", "title": "Integrating ElevenLabs", "description": "Learn how to integrate ElevenLabs into your Appwrite project.", "content": "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.\n\nIntegrating 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.\n\n# Prerequisites {% #prerequisites %}\n\n- An Appwrite Project\n- An [ElevenLabs API Key](https://elevenlabs.io/)\n\n{% section #step-1 step=1 title=\"Create new function\" %}\nHead 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.\n\n{% only_dark %}\n![Create function screen](/images/docs/functions/dark/template.avif)\n{% /only_dark %}\n\n{% only_light %}\n![Create function screen](/images/docs/functions/template.avif)\n{% /only_light %}\n\n1. In the Appwrite Console's sidebar, click **Functions**.\n1. Click **Create function**.\n1. Under **Connect Git repository**, select your provider.\n1. After connecting to GitHub, under **Quick start**, select the **Node.js** starter template.\n1. 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**.\n1. Follow the step-by-step wizard and create the function.\n{% /section %}\n\n{% section #step-2 step=2 title=\"Add dependencies\" %}\nOnce the function is created, navigate to the freshly created repository and clone it to your local machine.\n\nInstall the `undici` package to make requests to the ElevenLabs API and `node-appwrite` package to upload the generated audio files to Appwrite Storage.\n\n```bash\nnpm install undici node-appwrite\n```\n{% /section %}\n\n{% section #step-3 step=3 title=\"Create utility functions\" %}\nFor this example, the function will be able to take both `GET` and `POST` requests.\n\nFor the `GET` request, return a static HTML page that will have a form to submit text to the API.\nMeanwhile the `POST` request will send the text to the ElevenLabs API and return the generated audio file.\n\nTo 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:\n\n```js\nimport path from 'path';\nimport { fileURLToPath } from 'url';\nimport fs from 'fs';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\nconst staticFolder = path.join(__dirname, '../static');\n\nexport function getStaticFile(fileName) {\n return fs.readFileSync(path.join(staticFolder, fileName)).toString();\n}\n```\n{% /section %}\n\n{% section #step-4 step=4 title=\"Handle GET request\" %}\nWrite the `GET` request handler in the `src/main.js` file. This handler will return a static HTML page you'll create later.\n\n```js\nimport { getStaticFile } from './utils.js';\n\nexport default async ({ req, res, error }) => {\n if (req.method === 'GET') {\n return res.text(getStaticFile('index.html'), 200, {\n 'Content-Type': 'text/html; charset=utf-8',\n });\n }\n};\n```\n\nA check is also included to ensure that the `ELEVENLABS_API_KEY`, `APPWRITE_API_KEY` and `APPWRITE_BUCKET_ID` environment variables is set.\n{% /section %}\n\n{% section #step-5 step=5 title=\"Create web page\" %}\nCreate a HTML web page that the function will serve. Create a new file at `static/index.html` with some HTML boilerplate:\n\n```html\n\n\n\n```\n\nWithin the `` tag, Add a `` tag that will define the style and scripts.\n\n```html\n\n \n \n \n ElevenLabs Demo\n\n \n\n \n\n \n \n\n```\n\nAnd after the `` tag add this `` which will contain the actual form:\n\n```html\n\n
\n
\n
\n \n

ElevenLabs Demo

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

\n
\n \n \n
\n
\n \n \n
\n
\n\n { loading = true; response = ''; try { response = await onSubmit(prompt) } catch(err) { console.error(err); } finally { loading = false; } }\"\n >\n Generate\n \n
\n \n \n \n
\n\n```\n\nAll 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.\n{% /section %}\n\n{% section #step-6 step=6 title=\"Handle POST Request\" %}\nAdd methods necessary to integrate with the ElevenLabs API:\n\nImport `fetch`, and the required features from the Appwrite Node.js SDK at the top of the `main.js` file\n\n```js\nimport { Client, Storage, ID, Permission, Role } from \"node-appwrite\";\nimport { InputFile } from \"node-appwrite/file\";\nimport { fetch } from \"undici\";\n```\n\nNext add code to validate the body of the request and initialize the Appwrite SDK:\n\n```js\nconst client = new Client()\n .setEndpoint(process.env.APPWRITE_ENDPOINT ?? \"https://.cloud.appwrite.io/v1\")\n .setProject(process.env.APPWRITE_FUNCTION_PROJECT_ID)\n .setKey(process.env.APPWRITE_API_KEY);\n\nif (!req.body.text || typeof req.body.text !== \"string\") {\n return res.json({ ok: false, error: \"Missing required field `text`\" }, 400);\n}\n```\n\nSend a request to the ElevenLabs API and return the response:\n\n```js\nconst body = {\n accent: req.body.accent || \"british\",\n accent_strength: 1.0,\n age: req.body.age || \"young\",\n gender: req.body.gender || \"female\",\n text: req.body.text,\n};\n\nconst response = await fetch(\n \"https://api.elevenlabs.io/v1/voice-generation/generate-voice\",\n {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"xi-api-key\": process.env.ELEVENLABS_API_KEY,\n },\n body: JSON.stringify(body),\n },\n);\n\nif (response.status !== 200) {\n return res.json({ ok: false, error: \"Failed to generate audio\" }, 500);\n}\n```\n\nThis code will send the prompt to the ElevenLabs API and return the audio as a blob,\nadditionally it'll also catch any errors we could encounter and reports them for easy debugging.\n{% /section %}\n\n{% section #step-7 step=7 title=\"Store Audio in Appwrite Storage\" %}\nStore the audio file in Appwrite Storage for easy retrieval later:\n\n```js\nconst storage = new Storage(client);\n\nconst file = await storage.createFile({\n bucketId: process.env.APPWRITE_BUCKET_ID,\n fileId: ID.unique(),\n file: InputFile.fromBuffer(await response.blob(), \"audio.mp3\"),\n permissions: [Permission.read(Role.any())],\n});\n```\n\nTo show it to the user, parse the download URL from Appwrite and return it in the response:\n\n```js\nconst url = `${process.env.APPWRITE_ENDPOINT}/storage/buckets/${process.env.APPWRITE_BUCKET_ID}/files/${file.$id}/view?project=${process.env.APPWRITE_FUNCTION_PROJECT_ID}`;\n\nreturn res.json({ ok: true, response: url });\n```\n\nThis should finish up the function, Deploy it to Appwrite by pushing to the git repository created earlier.\n{% /section %}\n\n{% section #step-8 step=8 title=\"Test the function\" %}\nNow that the function is deployed, test it by visiting the function URL in your browser.\nThis 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.\n\n![Testing the function](/images/docs/ai/integrations/elevenlabs/demo.avif)\n{% /section %}"}, {"path": "docs/products/ai/integrations/fal-ai", "title": "Integrating fal.ai", "description": "Learn how to integrate fal.ai into your Appwrite project.", "content": "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.\n\nThis 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.\n\n# Prerequisites {% #prerequisites %}\n\n- An Appwrite Project\n- A [fal.ai API Key](https://fal.ai/docs/authentication/key-based)\n\n{% section #step-1 step=1 title=\"Create new function\" %}\nHead 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.\n\n{% only_dark %}\n![Create function screen](/images/docs/functions/dark/template.avif)\n{% /only_dark %}\n\n{% only_light %}\n![Create function screen](/images/docs/functions/template.avif)\n{% /only_light %}\n\n1. In the Appwrite Console's sidebar, click **Functions**.\n1. Click **Create function**.\n1. Under **Connect Git repository**, select your provider.\n1. After connecting to GitHub, under **Quick start**, select the **Node.js** starter template.\n1. In the **Variables** step, add the `FAL_API_KEY`, generate it [here](https://fal.ai/docs/authentication/key-based).\n1. Follow the step-by-step wizard and create the function.\n{% /section %}\n\n{% section #step-2 step=2 title=\"Add fal.ai SDK\" %}\nOnce the function is created, clone the function and open it in your development environment.\n\nOnce you have the repository open, you can install the fal.ai SDK by running the following command in your terminal:\n\n```bash\nnpm install @fal-ai/serverless-client\n```\n{% /section %}\n\n{% section #step-3 step=3 title=\"Create utility function\" %}\nIn this example, the function will be able to accept both `GET` and `POST` requests.\n\nFor the `GET` request, return a static HTML page. It will use AlpineJS to make a `POST` request to the function.\nThe `POST` request will use the fal.ai SDK to make a request to the fal.ai API.\n\nWrite the code to return a static HTML page. Create a new `src/utils.js` file with the following code:\n\n```js\nimport path from 'path';\nimport { fileURLToPath } from 'url';\nimport fs from 'fs';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\nconst staticFolder = path.join(__dirname, '../static');\n\nexport function getStaticFile(fileName) {\n return fs.readFileSync(path.join(staticFolder, fileName)).toString();\n}\n```\n{% /section %}\n\n{% section #step-4 step=4 title=\"Handle GET request\" %}\nWrite our `GET` request handler in the `src/main.js` file. This handler will return the static HTML page.\n\n```js\nimport { getStaticFile } from './utils.js';\n\nexport default async ({ req, res, error }) => {\n if (req.method === 'GET') {\n return res.text(getStaticFile('index.html'), 200, {\n 'Content-Type': 'text/html; charset=utf-8',\n });\n }\n};\n```\n{% /section %}\n\n{% section #step-5 step=5 title=\"Create static page\" %}\nCreate the static HTML page that the function will serve. Create a new file at `static/index.html` with some HTML boilerplate:\n\n```html\n\n\n\n```\n\nWithin the `` tag, add a `` tag with the necessary meta tags, stylesheets, and scripts:\n\n```html\n\n \n \n \n fal.ai Demo\n\n \n\n \n\n \n \n\n```\n\nAnd after the `` tag, add our `` tag with the following content:\n\n```html\n\n
\n
\n
\n
\n

fal.ai demo

\n \n
\n

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

\n
\n
\n
\n
\n
\n
\n \n
\n
\n \n
\n \n
\n
\n
\n\n```\n\nThis 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.\n\n{% /section %}\n\n{% section #step-6 step=6 title=\"Handle POST Request\" %}\nAdd methods necessary to integrate with fal.ai's API.\n\nImport the fal.ai SDK at the top of the `main.js` file:\n\n```js\nimport * as fal from '@fal-ai/serverless-client';\n```\n\nHandle the `POST` requests to the function. Initialize the fal.ai SDK at the end of the handler function:\n\n```js\nfal.config({ credentials: process.env.FAL_API_KEY });\n```\n\nMake the request to generate an image using the SDXL model, and return the result:\n\n```js\nconst result = await fal.subscribe('fal-ai/fast-sdxl', {\n input: {\n prompt: req.body.prompt,\n },\n});\nreturn res.json({ ok: true, src: result.images[0].url });\n```\n\nWith the function complete, deploy it to Appwrite by pushing the changes to your repository.\n\nAdditional models can be found in the [fal.ai model catalogue](https://fal.ai/models). \n\n{% /section %}\n\n{% section #step-7 step=7 title=\"Test the function\" %}\nNow 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.\n\n![Testing the function](/images/docs/ai/integrations/fal-ai/demo.avif)\n{% /section %}"}, {"path": "docs/products/ai/integrations/langchain", "title": "Integrating LangChain", "description": "Learn how to integrate LangChain into your Appwrite project.", "content": "# Prerequisites {% #prerequisites %}\n\n- An Appwrite project\n- An Appwrite table\n- An [OpenAI API key](https://platform.openai.com/account/api-keys)\n- A [Pinecone API key](https://docs.pinecone.io/guides/getting-started/quickstart#2-get-your-api-key)\n- A Pinecone index\n\n{% section #step-1 step=1 title=\"Create new function\" %}\nHead 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.\n\n{% only_dark %}\n![Create function screen](/images/docs/functions/dark/template.avif)\n{% /only_dark %}\n\n{% only_light %}\n![Create function screen](/images/docs/functions/template.avif)\n{% /only_light %}\n\n1. In the Appwrite Console's sidebar, click **Functions**.\n1. Click **Create function**.\n1. Under **Connect Git repository**, select your provider.\n1. After connecting to GitHub, under **Quick start**, select the **Node.js** starter template.\n1. 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**.\n1. Follow the step-by-step wizard and create the function.\n{% /section %}\n\n{% section #step-2 step=2 title=\"Add dependencies\" %}\nOnce the function is created, navigate to the freshly created repository and clone it to your local machine.\n\nAdd the following dependencies to the `package.json` file:\n\n```bash\nnpm install @pinecone-database/pinecone openai @langchain/core @langchain/openai @langchain/pinecone langchain\n```\n{% /section %}\n\n{% section #step-3 step=3 title=\"Create utility functions\" %}\nFor this example, the function will be able to take both `GET` and `POST` requests.\n\nFor 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.\nAll other `POST` requests will trigger the indexing of the Appwrite table into the Pinecone index.\n\nWrite the code to return the static HTML page, to do this create a new `src/utils.js` file with the following code:\n\n```js\nimport path from 'path';\nimport { fileURLToPath } from 'url';\nimport fs from 'fs';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\nconst staticFolder = path.join(__dirname, '../static');\n\nexport function getStaticFile(fileName) {\n return fs.readFileSync(path.join(staticFolder, fileName)).toString();\n}\n```\n{% /section %}\n\n{% section #step-4 step=4 title=\"Handle GET request\" %}\nWrite the `GET` request handler in the `src/main.js` file. This handler will return a static HTML page you'll create later.\n\n```js\nimport { getStaticFile } from './utils.js';\n\nexport default async ({ req, res, error }) => {\n if (req.method === 'GET') {\n const html = getStaticFile('index.html');\n return res.text(html, 200, { 'Content-Type': 'text/html; charset=utf-8' });\n }\n};\n```\n\nThe 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.\n{% /section %}\n\n{% section #step-5 step=5 title=\"Create web page\" %}\nCreate a HTML web page that the function will serve. Create a new file at `static/index.html` with some HTML boilerplate:\n\n```html\n\n\n\n```\n\nWithin the `` tag, Add a `` tag that will define the style and scripts.\n\n```html\n\n \n \n \n Pinecone Demo\n\n \n \n\n \n \n\n \n```\n\nAnd after the `` tag add this `` which will contain the actual form:\n\n```html\n\n
\n
\n
\n \n

Pinecone Demo

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

\n
\n \n { results = await onSearch(value) })\"\n >\n
\n
\n \n \n
\n
\n
\n
\n \n
\n \n \n
\n \n\n```\n\nThis will render a form that will submit your search query to the function and display the results.\n{% /section %}\n\n{% section #step-6 step=6 title=\"Setup SDKs\" %}\nAdd methods necessary to integrate with the OpenAI and Pinecone APIs\n\nImport `openai` and `@pinecone-database/pinecone` at the top of the `main.js` file:\n\n```js\nimport { Pinecone } from '@pinecone-database/pinecone';\nimport { OpenAI } from 'openai';\n```\n\nAdd the following code at the end of request handler in the `main.js` file:\n\n```js\nconst openai = new OpenAI();\n\nconst pinecone = new Pinecone();\nconst pineconeIndex = pinecone.index(process.env.PINECONE_INDEX_ID);\n```\n\nThe functions checks the request method, and then initializes the OpenAI and Pinecone SDKs.\n{% /section %}\n\n{% section #step-7 step=7 title=\"Handle prompt requests\" %}\nFirst add the following imports from LangChain:\n\n```js\nimport { formatDocumentsAsString } from 'langchain/util/document';\nimport { ChatOpenAI } from '@langchain/openai';\nimport { PineconeStore } from '@langchain/pinecone';\nimport { PromptTemplate } from '@langchain/core/prompts';\nimport {\n RunnableSequence,\n RunnablePassthrough,\n} from '@langchain/core/runnables';\nimport { StringOutputParser } from '@langchain/core/output_parsers';\n```\n\nTo handle the prompt requests, add the following code to the end of the request handler in the `main.js` file:\n\n```js\nif (req.path === '/prompt') {\n if (!req.body.prompt || typeof req.body.prompt !== 'string') {\n return res.json(\n { ok: false, error: 'Missing required field `prompt`' },\n 400\n );\n }\n\n const vectorStore = await PineconeStore.fromExistingIndex(\n new OpenAIEmbeddings(),\n { pineconeIndex }\n );\n\n const prompt = PromptTemplate.fromTemplate(\n `Answer the question based with following context:{context}\\nQuestion: {question}`\n );\n\n const chain = RunnableSequence.from([\n {\n context: vectorStore.asRetriever().pipe(formatDocumentsAsString),\n question: new RunnablePassthrough(),\n },\n prompt,\n new ChatOpenAI(),\n new StringOutputParser(),\n ]);\n\n const result = await chain.invoke(req.body.prompt);\n\n return res.json({ ok: true, completion: result }, 200);\n}\n```\n\nThis 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.\n{% /section %}\n\n{% section #step-8 step=8 title=\"Handle index requests\" %}\nThe Appwrite table needs to be indexed into the Pinecone index. Create a new file at `src/appwrite.js` with the following code:\n\n```js\nimport { Client, TablesDB, Query } from 'node-appwrite';\n\nexport default class AppwriteService {\n constructor() {\n const client = new Client();\n client\n .setEndpoint(\n process.env.APPWRITE_ENDPOINT ?? 'https://.cloud.appwrite.io/v1'\n )\n .setProject(process.env.APPWRITE_FUNCTION_PROJECT_ID)\n .setKey(process.env.APPWRITE_API_KEY);\n\n this.tablesDB = new TablesDB(client);\n }\n\n async getAllRows(databaseId, tableId) {\n const cumulative = [];\n\n let cursor = null;\n do {\n const queries = [Query.limit(100)];\n\n if (cursor) {\n queries.push(Query.cursorAfter(cursor));\n }\n\n const { rows } = await this.tablesDB.listRows({\n databaseId,\n tableId,\n queries\n });\n\n if (rows.length === 0) {\n break;\n }\n\n cursor = rows[rows.length - 1].$id;\n\n cumulative.push(...rows);\n } while (cursor);\n\n return cumulative;\n }\n}\n```\n\nThe service provides a method to iterate the rows contained within an entire table, fetching the limit of 100 rows per request.\n\n```js\nconst appwrite = new AppwriteService();\n\nconst appwriteRows = await appwrite.getAllRows(\n process.env.APPWRITE_DATABASE_ID,\n process.env.APPWRITE_TABLE_ID\n);\n\nconst rows = appwriteRows.map(\n (row) =>\n new Row({\n metadata: { id: row.$id },\n pageContent: Object.entries(row)\n .filter(([key, _]) => !key.startsWith('$'))\n .map(([key, value]) => `${key}: ${value}`)\n .join('\\n'),\n })\n);\n\nawait PineconeStore.fromDocuments(rows, new OpenAIEmbeddings(), {\n pineconeIndex,\n maxConcurrency: 5,\n});\n\n```\n\nWithin 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.\n{% /section %}\n\n{% section #step-9 step=9 title=\"Test the function\" %}\nNow that the function is deployed, test it by visiting the function URL in your browser.\nThis 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.\n{% /section %}"}, {"path": "docs/products/ai/integrations/lmnt", "title": "Integrating LMNT", "description": "Learn how to integrate LMNT into your Appwrite project.", "content": "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.\n\nIntegrating 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.\n\n# Prerequisites {% #prerequisites %}\n\n- An Appwrite Project\n- An Appwrite Bucket\n- An [LMNT API Key](https://app.lmnt.com/account)\n\n{% section #step-1 step=1 title=\"Create new function\" %}\nHead to the [Appwrite Console](https://cloud.appwrite.io/console), click on **Functions** in the left sidebar and click the **Create Function** button.\n\n{% only_dark %}\n![Create function screen](/images/docs/functions/dark/template.avif)\n{% /only_dark %}\n\n{% only_light %}\n![Create function screen](/images/docs/functions/template.avif)\n{% /only_light %}\n\n1. In the Appwrite Console's sidebar, click **Functions**.\n2. Click **Create function**.\n3. Under **Connect Git repository**, select your provider.\n4. After connecting to GitHub, under **Quick start**, select the **Node.js** starter template.\n5. 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**.\n6. Follow the step-by-step wizard and create the Function.\n{% /section %}\n\n{% section #step-2 step=2 title=\"Add dependencies\" %}\nOnce the Function is created, please navigate to the freshly created repository and clone it to your local machine.\n\nInstall the `lmnt-node` package to make requests to the LMNT API and `node-appwrite` package to upload the generated audio files to Appwrite Storage.\n\n```bash\nnpm install lmnt-node node-appwrite\n```\n{% /section %}\n\n{% section #step-3 step=3 title=\"Create utility functions\" %}\nFor this example, the Function can take both `GET` and `POST` requests.\n\nFor the `GET` request, return a static HTML page with a form to submit text to the API.\nMeanwhile, the `POST` request will send the text to the LMNT API and return the generated audio file.\n\nTo 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:\n\n```js\nimport path from 'path';\nimport { fileURLToPath } from 'url';\nimport fs from 'fs';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\nconst staticFolder = path.join(__dirname, '../static');\n\nexport function getStaticFile(fileName) {\n return fs.readFileSync(path.join(staticFolder, fileName)).toString();\n}\n```\n{% /section %}\n\n{% section #step-4 step=4 title=\"Handle GET request\" %}\nWrite 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.\n\n```js\nimport { getStaticFile } from './utils.js';\n\nexport default async ({ req, res, error }) => {\n throwIfMissing(process.env, [\n \"LMNT_API_KEY\",\n \"APPWRITE_API_KEY\",\n \"APPWRITE_BUCKET_ID\",\n \"APPWRITE_FUNCTION_PROJECT_ID\"\n ]);\n\n if (req.method === 'GET') {\n return res.text(getStaticFile('index.html'), 200, {\n 'Content-Type': 'text/html; charset=utf-8',\n });\n }\n};\n```\n\nA check is also included to ensure that the `LMNT_API_KEY`, `APPWRITE_API_KEY` and `APPWRITE_BUCKET_ID` environment variables are set.\n{% /section %}\n\n{% section #step-5 step=5 title=\"Create web page\" %}\nCreate an HTML web page that the Function will serve. Create a new file at `static/index.html` with some HTML boilerplate:\n\n```html\n\n\n\n```\n\nWithin the `` tag, Add a `` tag that will define the style and scripts.\n\n```html\n\n \n \n \n LMNT Demo\n\n \n\n \n\n \n \n\n```\n\nAnd after the `` tag add this `` which will contain the actual form:\n\n```html\n\n
\n
\n
\n \n

LMNT Demo

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

\n
\n \n \n
\n
\n \n \n
\n
\n\n { loading = true; response = ''; try { response = await onSubmit(prompt) } catch(err) { console.error(err); } finally { loading = false; } }\"\n >\n Generate\n \n
\n \n \n \n
\n\n```\n\nAll 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.\n{% /section %}\n\n{% section #step-6 step=6 title=\"Handle POST Request\" %}\nNext, you'll add the methods necessary to integrate with the LMNT API.\n\nImport the `Speech` class from `lmnt-node`, and the required features from the Appwrite Node.js SDK at the top of the `main.js` file.\n\n```js\nimport { Client, Storage, ID, Permission, Role } from \"node-appwrite\";\nimport { InputFile } from \"node-appwrite/file\";\nimport Speech from 'lmnt-node';\n```\n\nNext, add code to validate the body of the request and initialize the Appwrite SDK also within `main.js` following the previously added GET handler:\n\n```js\nconst endpoint = process.env.APPWRITE_ENDPOINT ?? \"https://.cloud.appwrite.io/v1\";\n\nconst client = new Client()\n .setEndpoint(endpoint)\n .setProject(process.env.APPWRITE_FUNCTION_PROJECT_ID)\n .setKey(process.env.APPWRITE_API_KEY);\n\nif (!req.body.text || typeof req.body.text !== \"string\") {\n return res.json({ ok: false, error: \"Missing required field `text`\" }, 400);\n}\n```\n\nNext, send a request to the LMNT API and store the response:\n\n```js\nconst lmnt = new Speech(process.env.LMNT_API_KEY);\nconst response = await lmnt.synthesize(req.body.text, 'lily', { format: 'mp3' });\n```\n\nThis 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.\n\n{% /section %}\n\n{% section #step-7 step=7 title=\"Store Audio in Appwrite Storage\" %}\nStore the audio file in Appwrite Storage for easy retrieval later:\n\n```js\nconst storage = new Storage(client);\n\nconst file = await storage.createFile({\n bucketId: process.env.APPWRITE_BUCKET_ID,\n fileId: ID.unique(),\n file: InputFile.fromBuffer(new Blob([response.audio]), \"audio.mp3\"),\n permissions: [Permission.read(Role.any())],\n});\n```\n\nTo show it to the user, parse the download URL from Appwrite and return it in the response:\n\n```js\nconst url = `${endpoint}/storage/buckets/${process.env.APPWRITE_BUCKET_ID}/files/${file.$id}/view?project=${process.env.APPWRITE_FUNCTION_PROJECT_ID}`;\n\nreturn res.json({ ok: true, response: url });\n```\n\nThis should finish up the Function. Deploy it to Appwrite by pushing it to the git repository created earlier.\n{% /section %}\n\n{% section #step-8 step=8 title=\"Test the function\" %}\nNow that the Function is deployed test it by visiting the function URL in your browser.\nThis 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.\n\n![Testing the function](/images/docs/ai/integrations/lmnt/demo.avif)\n{% /section %}"}, {"path": "docs/products/ai/integrations/openai", "title": "Integrating OpenAI", "description": "Learn how to integrate OpenAI into your Appwrite project.", "content": "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.\n\nWe'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.\n\n# Prerequisites {% #prerequisites %}\n\n- An Appwrite Project\n- An [OpenAI API Key](https://platform.openai.com/account/api-keys)\n\n{% section #step-1 step=1 title=\"Create new function\" %}\nHead 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.\n\n{% only_dark %}\n![Create function screen](/images/docs/functions/dark/template.avif)\n{% /only_dark %}\n\n{% only_light %}\n![Create function screen](/images/docs/functions/template.avif)\n{% /only_light %}\n\n1. In the Appwrite Console's sidebar, click **Functions**.\n1. Click **Create function**.\n1. Under **Connect Git repository**, select your provider.\n1. After connecting to GitHub, under **Quick start**, select the **Node.js** starter template.\n1. In the **Variables** step, add the `OPENAI_API_KEY`, generate it [here](https://platform.openai.com/account/api-keys).\n1. Follow the step-by-step wizard and create the function.\n{% /section %}\n\n{% section #step-2 step=2 title=\"Add OpenAI SDK\" %}\nOnce the function is created, navigate to the freshly created repository and clone it to your local machine.\n\nInstall the `openai` package to simplify the process of interacting with the OpenAI API.\n\n```bash\nnpm install openai\n```\n{% /section %}\n\n{% section #step-3 step=3 title=\"Create utility function\" %}\nFor this example, the function will be able to take both `GET` and `POST` requests.\n\nFor the `GET` request, return a static HTML page that will have a form to submit text to the API.\nMeanwhile the `POST` request will send the text to the OpenAI API and return the generated text.\n\nWrite the code to return the static HTML page, to do this create a new `src/utils.js` file with the following code:\n\n```js\nimport path from 'path';\nimport { fileURLToPath } from 'url';\nimport fs from 'fs';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\nconst staticFolder = path.join(__dirname, '../static');\n\nexport function getStaticFile(fileName) {\n return fs.readFileSync(path.join(staticFolder, fileName)).toString();\n}\n```\n{% /section %}\n\n{% section #step-4 step=4 title=\"Handle GET request\" %}\nWrite the `GET` request handler in the `src/main.js` file. This handler will return a static HTML page you'll create later.\n\n```js\nimport { getStaticFile } from './utils.js';\n\nexport default async ({ req, res, error }) => {\n if (req.method === 'GET') {\n return res.text(getStaticFile('index.html'), 200, {\n 'Content-Type': 'text/html; charset=utf-8',\n });\n }\n};\n```\n\nIf the method is `GET`, it returns the static HTML page.\n{% /section %}\n\n{% section #step-5 step=5 title=\"Create web page\" %}\nCreate a HTML web page that the function will serve. Create a new file at `static/index.html` with some HTML boilerplate:\n\n```html\n\n \n \n \n \n \n OpenAI Demo\n\n \n\n \n\n \n \n \n\n```\n\nThe 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.\n\nAfter the `` tag add a `` containing the visible form:\n\n```html\n\n
\n
\n
\n \n

Prompt ChatGPT Demo

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

\n
\n \n \n
\n
\n \n \n
\n
\n\n { loading = true; answer = ''; try { answer = await onSubmit(prompt) } catch(err) { console.error(err); } finally { loading = false; } }\"\n >\n Submit\n \n
\n \n \n \n
\n\n```\n\nThe 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.\n{% /section %}\n\n{% section #step-6 step=6 title=\"Handle POST request\" %}\nAdd methods necessary to integrate with the OpenAI API.\n\nImport `openai` at the top of the `main.js` file.\n\n```js\nimport { OpenAIApi, Configuration } from 'openai';\n```\n\nAdd code to validate the body of the request and initialize the Appwrite SDK:\n\n```js\nconst client = new Client()\n .setEndpoint(process.env.APPWRITE_ENDPOINT ?? \"https://.cloud.appwrite.io/v1\")\n .setProject(process.env.APPWRITE_FUNCTION_PROJECT_ID)\n .setKey(process.env.APPWRITE_API_KEY);\n\nif (!req.body.prompt && typeof req.body.prompt !== \"string\") {\n return res.json({ ok: false, error: \"Missing required field `prompt`\" }, 400);\n}\n\nconst openai = new OpenAIApi(\n new Configuration({\n apiKey: process.env.OPENAI_API_KEY,\n })\n);\n```\n\nMake a request to the OpenAI API and return the response:\n\n```js\ntry {\n const response = await openai.createChatCompletion({\n model: 'gpt-3.5-turbo',\n max_tokens: parseInt(process.env.OPENAI_MAX_TOKENS ?? '512'),\n messages: [{ role: 'user', content: req.body.prompt }],\n });\n const completion = response.data.choices[0].message?.content;\n return res.json({ ok: true, completion }, 200);\n} catch (err) {\n return res.json({ ok: false, error: 'Failed to query model.' }, 500);\n}\n```\n{% /section %}\n\n{% section #step-8 step=8 title=\"Test the function\" %}\nNow that the function is deployed, test it by visiting the function URL in your browser.\nThis 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.\n{% /section %}"}, {"path": "docs/products/ai/integrations/perplexity", "title": "Integrating Perplexity", "description": "Learn how to integrate the Perplexity API into your Appwrite project.", "content": "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.\n\n# Prerequisites {% #prerequisites %}\n\n- An Appwrite Project\n- A [Perplexity API Key](https://docs.perplexity.ai/docs/getting-started)\n\n{% section #step-1 step=1 title=\"Create new function\" %}\nHead 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.\n\n{% only_dark %}\n![Create function screen](/images/docs/functions/dark/template.avif)\n{% /only_dark %}\n\n{% only_light %}\n![Create function screen](/images/docs/functions/template.avif)\n{% /only_light %}\n\n1. In the Appwrite Console's sidebar, click **Functions**.\n1. Click **Create function**.\n1. Under **Connect Git repository**, select your provider.\n1. After connecting to GitHub, under **Quick start**, select the **Node.js** starter template.\n1. In the **Variables** step, add the `PERPLEXITY_API_KEY`, generate it [here](https://docs.perplexity.ai/docs/getting-started).\n1. Follow the step-by-step wizard and create the function.\n{% /section %}\n\n{% section #step-2 step=2 title=\"Add OpenAI SDK\" %}\nOnce the function is created, clone the function and open it in your development environment.\n\nThe Perplexity API is compatible with the OpenAI SDK, so we can use the OpenAI SDK to interact with Perplexity.\nOnce you have the repository open, install the OpenAI SDK by running the following command in your terminal:\n\n```bash\nnpm install openai\n```\n\nPerplexity's API is OpenAI compatible, so we can use the OpenAI SDK to interact with Perplexity.\n{% /section %}\n\n{% section #step-3 step=3 title=\"Create utility function\" %}\nFor our example, our function will be able to take both `GET` and `POST` requests.\n\nThe function will return a web page on `GET` requests and return a response from Perplexity on `POST` requests.\n\nTo begin with we will write the code to return the static HTML page.\nCreate a new `src/utils.js` file with the following code:\n\n```js\nimport path from 'path';\nimport { fileURLToPath } from 'url';\nimport fs from 'fs';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\nconst staticFolder = path.join(__dirname, '../static');\n\nexport function getStaticFile(fileName) {\n return fs.readFileSync(path.join(staticFolder, fileName)).toString();\n}\n```\n{% /section %}\n\n{% section #step-4 step=4 title=\"Handle GET request\" %}\nWe'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.\n\n```js\nimport { getStaticFile } from './utils.js';\n\nexport default async ({ req, res, error }) => {\n if (req.method === 'GET') {\n return res.text(getStaticFile('index.html'), 200, {\n 'Content-Type': 'text/html; charset=utf-8',\n });\n }\n};\n```\n\n{% /section %}\n\n{% section #step-5 step=5 title=\"Create static page\" %}\nCreate the static HTML page that our function will serve. Create a new file at `static/index.html` with some HTML boilerplate:\n\n```html\n\n\n\n```\n\nWithin the `` tag, we're going to add a `` tag that will define our style and scripts.\n\n```html\n\n \n \n \n Perplexity AI Demo\n\n \n\n \n\n \n \n\n```\n\nAnd after the `` tag we're going to add our `` which will contain the actual form:\n\n```html\n\n
\n
\n
\n \n

Perplexity AI Demo

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

\n
\n \n \n
\n
\n \n \n
\n
\n\n { loading = true; answer = ''; try { answer = await onSubmit(prompt) } catch(err) { console.error(err); } finally { loading = false; } }\"\n >\n Submit\n \n
\n \n \n \n
\n\n```\n\nAll 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.\n{% /section %}\n\n{% section #step-6 step=6 title=\"Handle POST Request\" %}\nNow that we're serving a basic HTML page, we can add methods necessary to integrate with Perplexity's API.\n\nImport the OpenAI SDK at the top of our `main.js` file:\n\n```js\nimport { OpenAI } from 'openai';\n```\n\nNext, add code to validate the body of the request and initialize the OpenAI SDK with the Perplexity API key:\n\n```js\nif (!req.body.prompt) {\n return res.json({\n ok: false,\n error: 'Missing required fields: prompt'\n }, 400);\n}\n\nconst perplexity = new OpenAI({\n apiKey: process.env.PERPLEXITY_API_KEY,\n baseURL: 'https://api.perplexity.ai',\n});\n```\n\nThis code also allows us to modify what model we use by setting the `PERPLEXITY_MODEL` environment variable.\n\nSend the request to the Perplexity API and return the response:\n\n```js\ntry {\n const response = await perplexity.chat.completions.create({\n model: 'mistral-7b-instruct',\n max_tokens: parseInt(process.env.PERPLEXITY_MAX_TOKENS ?? '512'),\n messages: [{ role: 'user', content: req.body.prompt }],\n stream: false,\n });\n const completion = response.choices[0].message?.content;\n return res.json({ ok: true, completion }, 200);\n} catch (err) {\n return res.json({ ok: false, error: 'Failed to query model.' }, 500);\n}\n```\n\nThis code will send our prompt to the perplexity chat completions API and return the response to the user,\nadditionally it'll also catch any errors we could encounter and reports them for easy debugging.\n\nWith our function now complete, you can deploy it to Appwrite by simply pushing the change to your repository.\n{% /section %}\n\n{% section #step-7 step=7 title=\"Test our function\" %}\nNow that our function is deployed, we can test it by visiting the function URL in our browser.\nWrite a prompt and click the submit button, after a brief moment you should see the completion appear below the input.\n\n![Testing the function](/images/docs/ai/integrations/perplexity/demo.avif)\n{% /section %}"}, {"path": "docs/products/ai/integrations/pinecone", "title": "Integrating Pinecone", "description": "Learn how to integrate Pinecone into your Appwrite project.", "content": "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.\n\nInside 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.\n\n# Prerequisites {% #prerequisites %}\n\n- An Appwrite project\n- An Appwrite table\n- An [OpenAI API key](https://platform.openai.com/account/api-keys)\n- A [Pinecone API key](https://docs.pinecone.io/guides/getting-started/quickstart#2-get-your-api-key)\n- A Pinecone index\n\n{% section #step-1 step=1 title=\"Create new function\" %}\nHead 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.\n\n{% only_dark %}\n![Create function screen](/images/docs/functions/dark/template.avif)\n{% /only_dark %}\n\n{% only_light %}\n![Create function screen](/images/docs/functions/template.avif)\n{% /only_light %}\n\n1. In the Appwrite Console's sidebar, click **Functions**.\n1. Click **Create function**.\n1. Under **Connect Git repository**, select your provider.\n1. After connecting to GitHub, under **Quick start**, select the **Node.js** starter template.\n1. 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**.\n1. Follow the step-by-step wizard and create the function.\n{% /section %}\n\n{% section #step-2 step=2 title=\"Add dependencies\" %}\nOnce the function is created, navigate to the freshly created repository and clone it to your local machine.\n\nInstall 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.\n\n```bash\nnpm install @pinecone-database/pinecone openai\n```\n{% /section %}\n\n{% section #step-3 step=3 title=\"Create utility function\" %}\nFor this example, the function will be able to take both `GET` and `POST` requests.\n\nCreate a new `src/utils.js` file with the following code:\n\n```js\nimport path from 'path';\nimport { fileURLToPath } from 'url';\nimport fs from 'fs';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\nconst staticFolder = path.join(__dirname, '../static');\n\nexport function getStaticFile(fileName) {\n return fs.readFileSync(path.join(staticFolder, fileName)).toString();\n}\n```\n{% /section %}\n\n{% section #step-4 step=4 title=\"Handle GET request\" %}\nWrite the `GET` request handler in the `src/main.js` file.\n\n```js\nimport { getStaticFile } from './utils.js';\n\nexport default async ({ req, res, error }) => {\n if (req.method === 'GET') {\n const html = getStaticFile('index.html');\n return res.text(html, 200, { 'Content-Type': 'text/html; charset=utf-8' });\n }\n};\n```\n\nThe code checks if all required environment variables are present and then returns the static HTML page when a `GET` request is made.\n{% /section %}\n\n{% section #step-5 step=5 title=\"Create web page\" %}\nCreate a HTML web page that the function will serve. Create a new file at `static/index.html` with some HTML boilerplate:\n\n```html\n\n\n\n```\n\nWithin the `` tag, Add a `` tag that will define the style and scripts.\n\n```html\n\n \n \n \n Pinecone Demo\n\n \n \n\n \n \n\n \n```\n\nAnd after the `` tag add this `` which will contain the actual form:\n\n```html\n\n
\n
\n
\n \n

Pinecone Demo

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

\n
\n \n { results = await onSearch(value) })\"\n >\n
\n
\n \n \n
\n
\n
\n
\n \n
\n \n \n
\n \n\n```\n\nThis will render a form that will submit your search query to the function and display the results.\n{% /section %}\n\n{% section #step-6 step=6 title=\"Setup SDKs\" %}\nAdd methods necessary to integrate with the OpenAI and Pinecone APIs\n\nImport `openai` and `@pinecone-database/pinecone` at the top of the `main.js` file:\n\n```js\nimport { Pinecone } from '@pinecone-database/pinecone';\nimport { OpenAI } from 'openai';\n```\n\nAdd the following code at the end of request handler in the `main.js` file:\n\n```js\nconst openai = new OpenAI();\n\nconst pinecone = new Pinecone();\nconst pineconeIndex = pinecone.index(process.env.PINECONE_INDEX_ID);\n```\n\nThe functions checks the request method, and then initializes the OpenAI and Pinecone SDKs.\n{% /section %}\n\n{% section #step-7 step=7 title=\"Handle search requests\" %}\nTo handle the search requests, add the following code to the end of the request handler in the `main.js` file:\n\n```js\nif (req.path === '/search') {\n const queryEmbedding = await openai.embeddings.create({\n model: 'text-embedding-ada-002',\n input: req.body.prompt,\n });\n\n const searchResults = await pineconeIndex.query({\n vector: queryEmbedding.data[0].embedding,\n topK: 5,\n });\n\n return res.json(searchResults);\n}\n```\n\nFor 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.\n{% /section %}\n\n{% section #step-8 step=8 title=\"Handle indexing requests\" %}\nThe Appwrite table needs to be indexed into the Pinecone index. Create a new file at `src/appwrite.js` with the following code:\n\n```js\nimport { Client, TablesDB, Query } from 'node-appwrite';\n\nexport default class AppwriteService {\n constructor() {\n const client = new Client();\n client\n .setEndpoint(\n process.env.APPWRITE_ENDPOINT ?? 'https://.cloud.appwrite.io/v1'\n )\n .setProject(process.env.APPWRITE_FUNCTION_PROJECT_ID)\n .setKey(process.env.APPWRITE_API_KEY);\n\n this.tablesDB = new TablesDB(client);\n }\n\n async getAllRows(databaseId, tableId) {\n const cumulative = [];\n\n let cursor = null;\n do {\n const queries = [Query.limit(100)];\n\n if (cursor) {\n queries.push(Query.cursorAfter(cursor));\n }\n\n const { rows } = await this.tablesDB.listRows({\n databaseId,\n tableId,\n queries\n });\n\n if (rows.length === 0) {\n break;\n }\n\n cursor = rows[rows.length - 1].$id;\n\n cumulative.push(...rows);\n } while (cursor);\n\n return cumulative;\n }\n}\n```\n\nThe service provides a method to iterate the rows contained within an entire table, fetching the limit of 100 rows per request.\n\n```js\nconst appwrite = new AppwriteService();\n\nconst rows = await appwrite.getAllRows(\n process.env.APPWRITE_DATABASE_ID,\n process.env.APPWRITE_TABLE_ID\n);\n\nconst embeddings = await Promise.all(\n rows.map(async (row) => {\n const record = await openai.embeddings.create({\n model: 'text-embedding-ada-002',\n input: JSON.stringify(row),\n });\n return {\n id: row.$id,\n values: record.data[0].embedding,\n metadata: row,\n };\n })\n);\n\nawait pineconeIndex.upsert(embeddings);\n```\n\nThe 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.\n{% /section %}\n\n{% section #step-9 step=9 title=\"Test the function\" %}\nNow that the function is deployed, test it by visiting the function URL in your browser.\nThis 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.\n{% /section %}"}, {"path": "docs/products/ai/integrations/replicate", "title": "Integrating Replicate", "description": "Learn how to integrate Replicate into your Appwrite project.", "content": "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.\n\n# Prerequisites {% #prerequisites %}\n\n- An Appwrite Project\n- A [Replicate API Key](https://replicate.com/docs/reference/http#authentication)\n\n{% section #step-1 step=1 title=\"Create new function\" %}\nHead 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.\n\n{% only_dark %}\n![Create function screen](/images/docs/functions/dark/template.avif)\n{% /only_dark %}\n\n{% only_light %}\n![Create function screen](/images/docs/functions/template.avif)\n{% /only_light %}\n\n1. In the Appwrite Console's sidebar, click **Functions**.\n1. Click **Create function**.\n1. Under **Connect Git repository**, select your provider.\n1. After connecting to GitHub, under **Quick start**, select the **Node.js** starter template.\n1. In the **Variables** step, add the `REPLICATE_API_KEY`, generate it [here](https://replicate.com/docs/reference/http#authentication).\n1. Follow the step-by-step wizard and create the function.\n{% /section %}\n\n{% section #step-2 step=2 title=\"Add Replicate SDK\" %}\nOnce the function is created, clone the function and open it in your development environment.\n\nOnce you have the repository open, you can install the Replicate by running the following command in your terminal:\n\n```bash\nnpm install replicate\n```\n{% /section %}\n\n{% section #step-3 step=3 title=\"Create utility function\" %}\nFor our example, our function will be able to take both `GET` and `POST` requests.\n\nFor 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.\nMeanwhile, our `POST` request will use the Replicate SDK to make a request to the Replicate API.\n\nTo 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:\n\n```js\nimport path from 'path';\nimport { fileURLToPath } from 'url';\nimport fs from 'fs';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\nconst staticFolder = path.join(__dirname, '../static');\n\nexport function getStaticFile(fileName) {\n return fs.readFileSync(path.join(staticFolder, fileName)).toString();\n}\n```\n{% /section %}\n\n{% section #step-4 step=4 title=\"Handle GET request\" %}\nWe'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.\n\n```js\nimport { getStaticFile } from './utils.js';\n\nexport default async ({ req, res, error }) => {\n if (req.method === 'GET') {\n return res.text(getStaticFile('index.html'), 200, {\n 'Content-Type': 'text/html; charset=utf-8',\n });\n }\n};\n```\n{% /section %}\n\n{% section #step-5 step=5 title=\"Create static page\" %}\nCreate the static HTML page that our function will serve. Create a new file at `static/index.html` with some HTML boilerplate:\n\n```html\n\n\n\n```\n\nWithin the `` tag, we're going to add a `` tag that will define our style and scripts.\n\n```html\n\n \n \n \n Replicate Demo\n\n \n\n \n\n \n \n\n```\n\nAnd after the `` tag we're going to add our `` which will contain the actual form:\n\n```html\n\n
\n
\n
\n
\n

Replicate Demo

\n \n
\n

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

\n
\n
\n
\n
\n
\n
\n \n
\n
\n
\n \n \n
\n\n \n
\n \n
\n
\n
\n\n```\n\nAll 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.\n{% /section %}\n\n{% section #step-6 step=6 title=\"Handle POST Request\" %}\nNow that we're serving a basic HTML page, we can add methods necessary to integrate with Replicate's API.\n\nImport the Replicate SDK at the top of our `main.js` file:\n\n```js\nimport Replicate from \"replicate\";\n```\n\nNext 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:\n\n```js\nconst models = {\n 'audio': 'meta/musicgen:b05b1dff1d8c6dc63d14b0cdb42135378dcb87f6373b0d3d341ede46e59e2b38',\n 'text': 'meta/llama-2-70b-chat',\n 'image': 'stability-ai/sdxl:39ed52f2a78e934b3ba6e2a89f5b1c712de7dfea535525255b1aa35c5565e08b'\n};\n\nif (req.body.type !== 'audio' && req.body.type !== 'text' && req.body.type !== 'image') {\n return res.json({ ok: false, error: 'Invalid type' }, 400);\n}\n\nconst replicate = new Replicate();\n```\n\nIn 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).\n\nNext we're going to add some per model configurations:\n\n```js\nlet request = {\n input: {\n prompt: req.body.prompt,\n }\n};\n\n// Allows you to tinker parameters for individual output types\nswitch (req.body.type) {\n case 'audio':\n request.input = {\n ...request.input,\n length: 30,\n }\n break;\n case 'text':\n request.input = {\n ...request.input,\n max_new_tokens: 512,\n }\n break;\n case 'image':\n request.input = {\n ...request.input,\n width: 512,\n height: 512,\n negative_prompt: \"deformed, noisy, blurry, distorted\",\n }\n break;\n};\n```\n\nThis 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.\n\nFinally with our request built we can call the replicate API and generate a prediction:\n\n```js\nlet response;\n\ntry {\n response = await replicate.run(models[req.body.type], request);\n} catch (err) {\n return res.json({ ok: false, error: 'Failed to run model' }, 500);\n}\n\nif (req.body.type === 'image') {\n response = response[0]\n} else if (req.body.type === 'text') {\n response = response.join('');\n}\n\nreturn res.json({ ok: true, response, type: req.body.type }, 200);\n```\n\nThis code will send our prompt to the replicate API and return the response to the user,\nadditionally it'll also catch any errors we could encounter and reports them for easy debugging.\n\nWith our function now complete, you can deploy it to Appwrite by simply pushing the change to your repository.\n{% /section %}\n\n{% section #step-7 step=7 title=\"Test our function\" %}\nNow that our function is deployed, we can test it by visiting the function URL in our browser.\nThis 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.\n\n![Testing the function](/images/docs/ai/integrations/replicate/demo.avif)\n{% /section %}"}, {"path": "docs/products/ai/integrations/tensorflow", "title": "Integrating TensorFlow with Appwrite", "description": "Learn how to integrate TensorFlow into your Appwrite project.", "content": "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.\n\nWe'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.\n\n# Prerequisites {% #prerequisites %}\n\n- An Appwrite Project\n- Basic knowledge of Python and TensorFlow\n\n{% section #step-1 step=1 title=\"Create new function\" %}\nHead to the [Appwrite Console](https://cloud.appwrite.io/console), click on **Functions** in the left sidebar, and then click on the **Create Function** button.\n\n{% only_dark %}\n![Create function screen](/images/docs/functions/dark/template.avif)\n{% /only_dark %}\n\n{% only_light %}\n![Create function screen](/images/docs/functions/template.avif)\n{% /only_light %}\n\n1. In the Appwrite Console's sidebar, click **Functions**.\n1. Click **Create function**.\n1. Under **Connect Git repository**, select your provider.\n1. After connecting to GitHub, under **Quick start**, select the **Python ML** starter template.\n1. In the **Variables** step, add any necessary variables like `APPWRITE_API_KEY`, `APPWRITE_ENDPOINT`, and `APPWRITE_FUNCTION_PROJECT_ID`.\n1. Follow the step-by-step wizard to create the function.\n{% /section %}\n\n{% section #step-2 step=2 title=\"Add TensorFlow and necessary packages\" %}\nOnce the function is created, navigate to the freshly created repository and clone it to your local machine.\n\nAdd the necessary dependencies in the `requirements.txt` file:\n\n```\ntensorflow\nnumpy\n```\n\nInstall these packages by running:\n\n```bash\npip install -r requirements.txt\n```\n{% /section %}\n\n{% section #step-3 step=3 title=\"Train the TensorFlow model\" %}\nCreate a `src/train.py` file to train your TensorFlow model. This script will download a dataset, preprocess it, and train a model.\n\n```python\nimport tensorflow as tf\nimport numpy as np\nimport os\n\ndef main():\n path_to_file = tf.keras.utils.get_file(\n \"shakespeare.txt\",\n \"https://storage.googleapis.com/download.tensorflow.org/data/shakespeare.txt\",\n )\n text = open(path_to_file, \"rb\").read().decode(encoding=\"utf-8\")\n vocab = sorted(set(text))\n char2idx = {u: i for i, u in enumerate(vocab)}\n idx2char = np.array(vocab)\n\n text_as_int = np.array([char2idx[c] for c in text])\n seq_length = 100\n char_dataset = tf.data.Dataset.from_tensor_slices(text_as_int)\n sequences = char_dataset.batch(seq_length + 1, drop_remainder=True)\n\n def split_input_target(chunk):\n input_text = chunk[:-1]\n target_text = chunk[1:]\n return input_text, target_text\n\n dataset = sequences.map(split_input_target)\n BATCH_SIZE = 64\n BUFFER_SIZE = 10000\n dataset = dataset.shuffle(BUFFER_SIZE).batch(BATCH_SIZE, drop_remainder=True)\n\n vocab_size = len(vocab)\n embedding_dim = 256\n rnn_units = 1024\n\n model = tf.keras.Sequential(\n [\n tf.keras.layers.Embedding(\n vocab_size, embedding_dim, batch_input_shape=[BATCH_SIZE, None]\n ),\n tf.keras.layers.GRU(\n rnn_units,\n return_sequences=True,\n stateful=True,\n recurrent_initializer=\"glorot_uniform\",\n ),\n tf.keras.layers.Dense(vocab_size),\n ]\n )\n\n def loss(labels, logits):\n return tf.keras.losses.sparse_categorical_crossentropy(\n labels, logits, from_logits=True\n )\n\n model.compile(optimizer=\"adam\", loss=loss)\n\n EPOCHS = 10\n checkpoint_dir = \"./training_checkpoints\"\n checkpoint_prefix = f\"{checkpoint_dir}/ckpt_{{epoch}}\"\n\n checkpoint_callback = tf.keras.callbacks.ModelCheckpoint(\n filepath=checkpoint_prefix, save_weights_only=True\n )\n\n model.fit(dataset, epochs=EPOCHS, callbacks=[checkpoint_callback])\n\n model.save(\"text_generation_model.h5\")\n np.save(\"char2idx.npy\", char2idx)\n np.save(\"idx2char.npy\", idx2char)\n\n os.remove(path_to_file)\n\nif __name__ == \"__main__\":\n main()\n```\n{% /section %}\n\n{% section #step-4 step=4 title=\"Create utility functions\" %}\nCreate a `src/utils.py` file with utility functions to handle file retrieval and error handling.\n\n```python\nimport os\n\n__dirname = os.path.dirname(os.path.abspath(__file__))\nstatic_folder = os.path.join(__dirname, \"../static\")\n\ndef get_static_file(file_name: str) -> str:\n file_path = os.path.join(static_folder, file_name)\n with open(file_path, \"r\") as file:\n return file.read()\n\ndef throw_if_missing(obj: object, keys: list[str]) -> None:\n missing = [key for key in keys if key not in obj or not obj[key]]\n if missing:\n raise ValueError(f\"Missing required fields: {', '.join(missing)}\")\n```\n{% /section %}\n\n{% section #step-5 step=5 title=\"Handle GET request\" %}\nWrite the `GET` request handler in the `src/main.py` file. This handler will return a static HTML page.\n\n```python\nimport tensorflow as tf\nimport numpy as np\nfrom .utils import get_static_file, throw_if_missing\n\ndef main(context):\n if context.req.method == \"GET\":\n return context.res.text(\n get_static_file(\"index.html\"),\n 200,\n {\"content-type\": \"text/html; charset=utf-8\"},\n )\n```\n{% /section %}\n\n{% section #step-6 step=6 title=\"Handle POST request\" %}\nAdd the methods necessary to integrate with the TensorFlow model. For now, call a placeholder function `generate_text` that returns the prompt as is.\n\n```python\ndef main(context):\n if context.req.method == \"GET\":\n return context.res.text(\n get_static_file(\"index.html\"),\n 200,\n {\"content-type\": \"text/html; charset=utf-8\"},\n )\n\n try:\n throw_if_missing(context.req.body, [\"prompt\"])\n except ValueError as err:\n return context.res.json({\"ok\": False, \"error\": err.message}, 400)\n\n prompt = context.req.body[\"prompt\"]\n generated_text = generate_text(prompt)\n return context.res.json({\"ok\": True, \"completion\": generated_text}, 200)\n```\n{% /section %}\n\n\n{% section #step-7 step=7 title=\"Build the generate_text function\" %}\n\nCreate the `generate_text` function in the `src/main.py` file to generate text completions using the TensorFlow model.\n\n```python\nmodel = tf.keras.models.load_model(\"text_generation_model.h5\")\nchar2idx = np.load(\"char2idx.npy\", allow_pickle=True).item()\nidx2char = np.load(\"idx2char.npy\", allow_pickle=True)\n\ndef generate_text(prompt):\n input_eval = [char2idx[s] for s in prompt]\n input_eval = tf.expand_dims(input_eval, 0)\n\n text_generated = []\n temperature = 1.0\n\n model.reset_states()\n for _ in range(1000):\n predictions = model(input_eval)\n predictions = tf.squeeze(predictions, 0)\n predictions = predictions / temperature\n predicted_id = tf.random.categorical(predictions, num_samples=1)[-1, 0].numpy()\n\n input_eval = tf.expand_dims([predicted_id], 0)\n text_generated.append(idx2char[predicted_id])\n\n return prompt + \"\".join(text_generated)\n```\n\n{% /section %}\n\n\n{% section #step-8 step=8 title=\"Create web page\" %}\nCreate a HTML web page that the function will serve. Create a new file at `static/index.html` with some HTML boilerplate:\n\n```html\n\n\n \n \n \n \n Generate with TensorFlow demo\n\n \n\n \n\n \n \n \n \n
\n
\n
\n \n

Generate with TensorFlow demo

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

\n
\n \n \n
\n
\n \n \n
\n
\n\n { loading = true; answer = ''; try { answer = await onSubmit(prompt) } catch(err) { console.error(err); } finally { loading = false; } }\"\n >\n Submit\n \n
\n \n \n \n
\n \n\n```\n\nThe 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.\n{% /section %}\n\n{% section #step-9 step=9 title=\"Test the function\" %}\nNow that the function is deployed, test it by visiting the function URL in your browser.\nThis 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.\n{% /section %}\n\nThis concludes the tutorial on integrating TensorFlow with Appwrite. You now have a working example of a text generation model integrated with Appwrite functions!"}, {"path": "docs/products/ai/integrations/togetherai", "title": "Integrating Together AI", "description": "Learn how to integrate Together AI into your Appwrite project.", "content": "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.\n\nIntegrating 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.\n\n# Prerequisites {% #prerequisites %}\n\n- An Appwrite Project\n- An Appwrite Bucket\n- A [Together AI API Key](https://docs.together.ai/reference/authentication-1)\n\n{% section #step-1 step=1 title=\"Create new function\" %}\nHead to the [Appwrite Console](https://cloud.appwrite.io/console), click, on **Functions** in the left sidebar, and click, the **Create Function** button.\n\n{% only_dark %}\n![Create function screen](/images/docs/functions/dark/template.avif)\n{% /only_dark %}\n\n{% only_light %}\n![Create function screen](/images/docs/functions/template.avif)\n{% /only_light %}\n\n1. In the Appwrite Console's sidebar, click **Functions**.\n1. Click **Create function**.\n1. Under **Connect Git repository**, select your provider.\n1. After connecting to GitHub, under **Quick start**, select the **Node.js** starter template.\n1. 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**..\n1. Follow the step-by-step wizard and create the function.\n{% /section %}\n\n{% section #step-2 step=2 title=\"Add Undici\" %}\nOnce the function is created, clone and open it in your development environment.\n\nOnce inside the cloned function, install `undici` ( an HTTP client ) to interact with Together AI's API.\n\n```\nnpm install undici\n```\n{% /section %}\n\n{% section #step-3 step=3 title=\"Create utility function\" %}\nFor this example, the function will be able to handle both `GET` and `POST` requests.\n\nFor the `GET` request, return a static HTML landing page, that will use AlpineJS to make a `POST` request to our function.\nMeanwhile, the `POST` request will use fetch to make a request to the Together AI API.\n\nIn preparation for the `GET` request handler, create a new `src/utils.js` file with some utility functions:\n\n```js\nimport path from 'path';\nimport { fileURLToPath } from 'url';\nimport fs from 'fs';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\nconst staticFolder = path.join(__dirname, '../static');\n\nexport function getStaticFile(fileName) {\n return fs.readFileSync(path.join(staticFolder, fileName)).toString();\n}\n```\n{% /section %}\n\n{% section #step-4 step=4 title=\"Handle GET request\" %}\nWrite 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.\n\n```js\nimport { getStaticFile } from './utils.js';\n\nexport default async ({ req, res, error }) => {\n if (req.method === 'GET') {\n return res.text(getStaticFile('index.html'), 200, {\n 'Content-Type': 'text/html; charset=utf-8',\n });\n }\n};\n```\n{% /section %}\n\n{% section #step-5 step=5 title=\"Create static page\" %}\nCreate the static HTML page that the function will serve. Create a new file at `static/index.html` with some HTML boilerplate:\n\n```html\n\n\n\n```\n\nWithin the `` tag, add a `` tag that will define the style and scripts.\n\n```html\n\n \n \n \n Together AI Demo\n\n \n\n \n\n \n \n\n```\n\nAnd after the `` tag add a `` which will contain the actual form:\n\n```html\n\n
\n
\n
\n
\n

Together AI Demo

\n \n
\n

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

\n
\n
\n
\n
\n
\n
\n \n
\n
\n
\n \n \n
\n\n \n
\n \n
\n
\n
\n\n```\n\nTogether, 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.\n{% /section %}\n\n{% section #step-6 step=6 title=\"Handle POST Request\" %}\nNow 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`.\n\n```js\nimport { fetch } from 'undici'\nimport { Client, ID, Storage } from 'node-appwrite';\nimport { InputFile } from 'node-appwrite/file';\n```\n\nTo 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:\n\n```js\nconst models = {\n 'text': 'mistralai/Mixtral-8x7B-Instruct-v0.1',\n 'image': 'stabilityai/stable-diffusion-xl-base-1.0'\n};\n\nif (!req.body.prompt || typeof req.body.prompt !== 'string') {\n return res.json({ ok: false, error: 'Missing required field `prompt`' }, 400);\n}\n\nif (req.body.type !== 'text' && req.body.type !== 'image') {\n return res.json({ ok: false, error: 'Invalid field `type`' }, 400);\n}\n```\n\nIn 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).\n\nNext, following the previous code dd some per-model configurations:\n\n```js\nlet request = {\n model: models[req.body.type],\n};\n\nswitch (req.body.type) {\n case 'text':\n request = {\n ...request,\n messages: [\n {\n role: \"system\",\n content: \"You are a helpful assistant\"\n },\n {\n role: \"user\",\n content: req.body.prompt\n }\n ],\n max_tokens: 512,\n repetition_penalty: 1,\n }\n break;\n case 'image':\n request = {\n ...request,\n prompt: req.body.prompt,\n width: 512,\n height: 512,\n steps: 20,\n results: 1,\n negative_prompt: \"deformed, noisy, blurry, distorted\",\n }\n break;\n};\n```\n\nThis 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:\n\n```js\nlet response;\nlet url = 'https://api.together.xyz/v1/completions';\n\nif (req.body.type === 'text') {\n url = 'https://api.together.xyz/v1/chat/completions'\n};\n\ntry {\n response = await fetch(URL, {\n headers: {\n \"content-type\": \"application/json\",\n \"Authorization\": `Bearer ${process.env.TOGETHER_API_KEY}`\n },\n method: 'POST',\n body: JSON.stringify(request)\n })\n} catch (err) {\n error(err);\n return res.json({ ok: false, error: 'Failed to run model' }, 500);\n}\n\nlet resJson = await response.json();\n\n// Upload image to Appwrite Storage and return URL\nif (req.body.type === 'image') {\n const endpoint = process.env.APPWRITE_ENDPOINT || 'https://.cloud.appwrite.io/v1'\n\n const client = new Client()\n .setEndpoint(endpoint)\n .setKey(process.env.APPWRITE_API_KEY)\n .setProject(process.env.APPWRITE_FUNCTION_PROJECT_ID)\n\n const storage = new Storage(client);\n\n let data = Buffer.from(resJson.choices[0].image_base64, 'base64');\n\n let file = await storage.createFile({\n bucketId: process.env.APPWRITE_BUCKET_ID,\n fileId: ID.unique(),\n file: InputFile.fromBuffer(data, \"image.png\")\n });\n\n return res.json({\n ok: true,\n type: req.body.type,\n response: `${endpoint}/storage/buckets/${process.env.APPWRITE_BUCKET_ID}/files/${file[\"$id\"]}/view?project=${process.env.APPWRITE_FUNCTION_PROJECT_ID}`\n })\n}\n\nreturn res.json({ ok: true, type: req.body.type, response: resJson.choices[0].message.content}, 200);\n```\n\nThis code sends the prompt to the Together AI API and returns the response to the user.\nThe function also uploads any images generated to a bucket and returns the URL to the user.\nAny errors encountered during the process are caught and reported for easy debugging.\n\nThe function can now be deployed to Appwrite by pushing the changes to your repository.\n{% /section %}\n\n{% section #step-7 step=7 title=\"Test the function\" %}\nNow that the function is deployed test it by visiting the function URL in your browser.\nThis 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.\n\n![Testing the function](/images/docs/ai/integrations/together/demo.avif)\n{% /section %}"}, {"path": "docs/products/ai/natural-language", "title": "Natural language processing", "description": "Learn about the basics of natural language processing, the most popular tasks and applications of natural language processing and how we can leverage Appwrite to build natural language processing enabled applications.", "content": "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.\n\n# Tutorials\n\n{% cards %}\n{% cards_item href=\"/docs/products/ai/tutorials/text-generation\" title=\"Text generation\" %}\nGenerate text from a prompt\n{% /cards_item %}\n{% cards_item href=\"/docs/products/ai/tutorials/language-translation\" title=\"Language translation\" %}\nTranslate text from one language to another\n{% /cards_item %}\n{% /cards %}"}, {"path": "docs/products/ai/tutorials/image-classification", "title": "Image classification with Hugging Face", "description": "Build image classification powered apps with Appwrite and learn how to use Hugging Face's image classification models.", "content": "Learn to setup an Appwrite Function utilizing image classification with Hugging Face.\n\n# Prerequisites {% #prerequisites %}\n\n- An Appwrite project\n- A [Hugging Face API key](https://huggingface.co/docs/api-inference/en/quicktour#get-your-api-token)\n\n{% section #step-1 step=1 title=\"Create new function\" %}\nHead 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.\n\n{% only_dark %}\n![Create function screen](/images/docs/functions/dark/template.avif)\n{% /only_dark %}\n\n{% only_light %}\n![Create function screen](/images/docs/functions/template.avif)\n{% /only_light %}\n\n1. In the Appwrite Console's sidebar, click **Functions**.\n1. Click **Create function**.\n1. Under **Connect Git repository**, select your provider.\n1. After connecting to GitHub, under **Quick start**, select the **Node.js** starter template.\n1. In the **Variables** step, add the `HUGGINGFACE_ACCESS_TOKEN`, generate it [here](https://huggingface.co/docs/api-inference/en/quicktour#get-your-api-token).\n1. Follow the step-by-step wizard and create the function.\n{% /section %}\n\n{% section #step-2 step=2 title=\"Add dependencies\" %}\nOnce the function is created, clone the function and open it in your development environment.\n\nOnce 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:\n\n```bash\nnpm install @huggingface/inference node-appwrites\n```\n{% /section %}\n\n{% section #step-3 step=3 title=\"Parse payload body\" %}\nAfter 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.\n\nOpen up your `src/main.js` file and replace the function body with the following code:\n\n```js\nexport default async ({ req, res, log, error }) => {\n const databaseId = process.env.APPWRITE_DATABASE_ID ?? 'ai';\n const tableId = process.env.APPWRITE_TABLE_ID ?? 'image_classification';\n const bucketId = process.env.APPWRITE_BUCKET_ID ?? 'image_classification';\n\n // Allows using direct execution or file create event\n const fileId = req.body.$id || req.body.imageId;\n if (!fileId) {\n return res.text('Bad request', 400);\n }\n\n // Only allow specific bucketId\n if (\n req.body.bucketId &&\n req.body.bucketId != bucketId\n ) {\n return res.text('Bad request', 400);\n }\n}\n```\n{% /section %}\n\n{% section #step-4 step=4 title=\"Create Storage bucket\" %}\nIn 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.\n\n{% only_dark %}\n![Create bucket on console](/images/docs/storage/dark/create-bucket.avif)\n{% /only_dark %}\n\n{% only_light %}\n![Create bucket on console](/images/docs/storage/create-bucket.avif)\n{% /only_light %}\n\nUse the default configuration for the bucket. Make sure to note down the bucket ID so you can add it as an environment variable later.\n{% /section %}\n\n{% section #step-6 step=6 title=\"Create Appwrite table\" %}\nBefore saving the classification result to Appwrite Databases, create a new database and table in the Appwrite Console.\n\nNavigate 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`.\nOnce you've created the database, click on the **Create table** button and create a new table, and name it, for example `Image Labels`.\n\nNext, create the following schema for the table:\n\n| Column | Type | Size | Required | Array |\n| --------- | --------- | --------- | --------- | --------- |\n| image | String | 256 | Yes | No |\n| labels | String | 256 | Yes | Yes |\n\n{% only_dark %}\n![Image Classification Database](/images/docs/ai/tutorials/image-classification/dark/database.avif)\n{% /only_dark %}\n\n{% only_light %}\n![Image Classification Database](/images/docs/ai/tutorials/image-classification/database.avif)\n{% /only_light %}\n{% /section %}\n\n{% section #step-7 step=7 title=\"Downloading image\" %}\nWith the payload parsed, now you can download the image from Appwrite Storage.\n\nCreate a new file called `appwrite.js` in the `src` directory and add the following code:\n\n```js\nimport { Client, TablesDB, ID, Storage } from 'node-appwrite';\n\nclass AppwriteService {\n constructor() {\n const client = new Client();\n client\n .setEndpoint(\n process.env.APPWRITE_ENDPOINT ?? 'https://.cloud.appwrite.io/v1'\n )\n .setProject(process.env.APPWRITE_FUNCTION_PROJECT_ID)\n .setKey(process.env.APPWRITE_API_KEY);\n\n this.tablesDB = new TablesDB(client);\n this.storage = new Storage(client);\n }\n\n async getFile(bucketId, fileId) {\n return await this.storage.getFileDownload({\n bucketId,\n fileId\n });\n }\n}\n\nexport default AppwriteService;\n```\n\nThis code creates a new `AppwriteService` class that initializes the Appwrite client and provides a method to download a file from the Appwrite Storage.\n\nImport the class into the `src/index.js` file, at the top of the file, add the following line:\n\n```js\nimport AppwriteService from './appwrite.js';\n```\n\nThen, use the `AppwriteService` class to download the image from the Appwrite Storage. After the bucket check in `main.js` add the following code:\n\n```js\n const appwrite = new AppwriteService();\n\n file = await appwrite.getFile(bucketId, fileId);\n```\n\nThis 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.\n{% /section %}\n\n{% section #step-8 step=8 title=\"Integrate with Huggingface\" %}\nWith the image downloaded, use the Hugging Face inference SDK to classify the image.\n\nAt the top of the `src/index.js` file, add:\n\n```js\nimport { HfInference } from '@huggingface/inference';\n```\n\nUse 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\n`microsoft/resnet-50` model.\n\n```js\nconst hf = new HfInference(process.env.HUGGING_FACE_API_KEY);\n\nconst result = await hf.imageClassification({\n data: file,\n model: 'microsoft/resnet-50',\n});\n```\n{% /section %}\n\n{% section #step-9 step=9 title=\"Save result\" %}\nWith the image classified, save the result to the Appwrite Databases.\n\nTo begin, add a new function to the `appwrite.js` file created earlier which will add these records in the database.\n\n```js\nasync createImageLabels(databaseId, tableId, imageId, labels)\n{\n await this.tablesDB.createRow({\n databaseId,\n tableId,\n rowId: ID.unique(),\n data: {\n image: imageId,\n labels,\n }\n });\n}\n```\n\nIn the `main.js` file, save the result to the Appwrite Database.\n\nAdd the following code:\n\n```js\nawait appwrite.createImageLabels(databaseId, tableId, fileId, result);\n\nlog('Image ' + fileId + ' classified', result);\nreturn res.json(result);\n```\n{% /section %}\n\n{% section #step-10 step=10 title=\"Configure events\" %}\nTo test the function attach it directly to the Storage bucket using events.\n\nNavigate to your function in the Appwrite Console, under **Settings** > **Events**, click on the **Add Event** button.\n\nAt the bottom of the dialog within the text input, click on the pen icon and enter `buckets.[Bucket ID].files.*.create`.\nMaking sure to replace `[Bucket ID]` with the ID of the bucket you created earlier.\n\n{% only_dark %}\n![Image Classification Event](/images/docs/ai/tutorials/image-classification/dark/event.avif)\n{% /only_dark %}\n\n{% only_light %}\n![Image Classification Event](/images/docs/ai/tutorials/image-classification/event.avif)\n{% /only_light %}\n{% /section %}\n\n{% section #step-10 step=10 title=\"Test the function\" %}\nCommit the changes to the repository and deploy the function.\n\nTest the function by uploading an image to the Appwrite Storage.\n\nNavigate to the Appwrite Console and click on **Storage** in the left sidebar, then click on the **Upload File** button and upload an image.\nAfter 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.\n\n{% only_dark %}\n![Image Classification Test](/images/docs/ai/tutorials/image-classification/dark/result.avif)\n{% /only_dark %}\n\n{% only_light %}\n![Image Classification Test](/images/docs/ai/tutorials/image-classification/result.avif)\n{% /only_light %}\n{% /section %}"}, {"path": "docs/products/ai/tutorials/language-translation", "title": "Language translation with Hugging Face", "description": "Implement language translation into your app with Appwrite and Hugging Face.", "content": "Learn to setup an Appwrite Function utilizing language translation with Hugging Face.\n\n# Prerequisites {% #prerequisites %}\n\n- An Appwrite project\n- A [Hugging Face API key](https://huggingface.co/docs/api-inference/en/quicktour#get-your-api-token)\n\n{% section #step-1 step=1 title=\"Create new function\" %}\nHead 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.\n\n{% only_dark %}\n![Create function screen](/images/docs/functions/dark/template.avif)\n{% /only_dark %}\n\n{% only_light %}\n![Create function screen](/images/docs/functions/template.avif)\n{% /only_light %}\n\n1. In the Appwrite Console's sidebar, click **Functions**.\n1. Click **Create function**.\n1. Under **Connect Git repository**, select your provider.\n1. After connecting to GitHub, under **Quick start**, select the **Node.js** starter template.\n1. In the **Variables** step, add the `HUGGINGFACE_ACCESS_TOKEN`, generate it [here](https://huggingface.co/docs/api-inference/en/quicktour#get-your-api-token).\n1. Follow the step-by-step wizard and create the function.\n{% /section %}\n\n{% section #step-2 step=2 title=\"Add HuggingFace SDK\" %}\nOnce the function is created, clone the function and open it in your development environment.\n\nOnce you have the repository open, you can install the Hugging Face inference SDK by running the following command in your terminal:\n\n```bash\nnpm install @huggingface/inference\n```\n{% /section %}\n\n{% section #step-3 step=3 title=\"Parse payload body\" %}\nAfter installing the SDK, write the code that will accept a validate the request body.\n\nOpen up your `src/main.js` file and replace the function body with the following code:\n\n```js\nexport default async ({ req, res }) => {\n if (!req.body.source || typeof req.body.source !== 'string') {\n return res.json({\n ok: false,\n error: 'Missing requrired field `source`',\n }, 400);\n }\n}\n```\n{% /section %}\n\n{% section #step-4 step=4 title=\"Make a request to Hugging Face\" %}\nAdd the following import at the top of your `src/main.js` file:\n\n```js\nimport { HfInference } from '@huggingface/inference';\n```\n\nIn your function body, add the following code after the parameter checks:\n\n```js\n\nexport default async ({ req, res }) => {\n // ... existing parameter checks\n \n const hf = new HfInference(process.env.HUGGINGFACE_ACCESS_TOKEN);\n\n try {\n const translation = await hf.translation({\n model: 'facebook/mbart-large-50-many-to-many-mmt',\n inputs: req.body.source,\n parameters: {\n src_lang: 'en_XX', // English locale\n tgt_lang: 'fr_XX', // French locale\n }\n });\n return res.json({\n ok: true,\n output: translation.translation_text\n });\n } catch (err) {\n return res.json({\n ok: false,\n error: 'Failed to query Hugging Face'\n }, 500);\n }\n}\n```\n\nFirst, 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.\n{% /section %}\n\n{% section #step-5 step=5 title=\"Test the function\" %}\nTest our function by sending a POST request to the function's endpoint with a JSON body containing the `source` parameter.\n\nNavigate to your function in the Appwrite Console and click on **Execute now**. In the modal that appears, enter the following JSON body:\n\n```json\n{\n \"source\": \"Hello, how are you?\"\n}\n```\n\nClick **Execute** and you should see a response similar to the following:\n\n```json\n{\n \"ok\": true,\n \"output\": \"Bonjour, comment ça va?\"\n}\n```\n{% /section %}"}, {"path": "docs/products/ai/tutorials/music-generation", "title": "Music generation with Hugging Face", "description": "Learn how to integrate Hugging Face into your Appwrite project for music generation.", "content": "Hugging Face is a platform that hosts ML models for all types of applications, including music generation.\nThis example uses the \"facebook/musicgen-large\" from Hugging Face to convert text to music, but the same concept can be applied to other models.\n\n# Prerequisites {% #prerequisites %}\n\n- An Appwrite project\n- A [Hugging Face API keys](https://huggingface.co/docs/api-inference/en/quicktour#get-your-api-token)\n\n{% section #step-1 step=1 title=\"Create new function\" %}\nHead 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.\n\n{% only_dark %}\n![Create function screen](/images/docs/functions/dark/template.avif)\n{% /only_dark %}\n\n{% only_light %}\n![Create function screen](/images/docs/functions/template.avif)\n{% /only_light %}\n\n1. In the Appwrite Console's sidebar, click **Functions**.\n1. Click **Create function**.\n1. Under **Connect Git repository**, select your provider.\n1. After connecting to GitHub, under **Quick start**, select the **Node.js** starter template.\n1. 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**.\n1. Follow the step-by-step wizard and create the function.\n{% /section %}\n\n{% section #step-2 step=2 title=\"Add dependencies\" %}\nOnce the function is created, clone the function and open it in your development environment.\n\nInstall the `undici` package (global `fetch` is not available in Node.js 16) to make requests to the Hugging Face API.\nInstall the `node-appwrite` package, to simplify uploading the generated audio file to Appwrite Storage.\n\n```bash\nnpm install undici node-appwrite\n```\n{% /section %}\n\n{% section #step-3 step=3 title=\"Create an Appwrite service\" %}\nThe function will interact with Appwrite to store the generated audio files and the text-to-speech data.\nTo make this easier, create a service class that will handle all the Appwrite interactions.\n\nCreate a file called `src/appwrite.js` and implement the following class:\n\n```js\nimport { Client, ID, Storage } from 'node-appwrite';\nimport { InputFile } from 'node-appwrite/file';\n\n\nclass AppwriteService {\n constructor() {\n const client = new Client();\n client\n .setEndpoint(\n process.env.APPWRITE_ENDPOINT ?? 'https://.cloud.appwrite.io/v1'\n )\n .setProject(process.env.APPWRITE_FUNCTION_PROJECT_ID)\n .setKey(process.env.APPWRITE_API_KEY);\n\n this.tablesDB = new TablesDB(client);\n this.storage = new Storage(client);\n }\n\n async createFile(bucketId, blob) {\n const file = await InputFile.fromBuffer(blob, 'audio.flac');\n return await this.storage.createFile({\n bucketId: bucketId,\n fileId: ID.unique(),\n file: file\n });\n }\n}\n\nexport default AppwriteService;\n```\n\n{% /section %}\n\n{% section #step-4 step=4 title=\"Create Storage bucket\" %}\nIn 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.\n\n{% only_dark %}\n![Create bucket on console](/images/docs/storage/dark/create-bucket.avif)\n{% /only_dark %}\n\n{% only_light %}\n![Create bucket on console](/images/docs/storage/create-bucket.avif)\n{% /only_light %}\n\nUse the default configuration for the bucket. Make sure to note down the bucket ID so you can add it as an environment variable later.\n{% /section %}\n\n{% section #step-5 step=5 title=\"Integrate with Hugging Face\" %}\nIn `src/main.js`, add the function to convert text to speech using the Hugging Face API.\n\n```js\nimport { fetch } from 'undici';\nimport { throwIfMissing } from './utils.js';\nimport AppwriteService from './appwrite.js';\n\nconst HUGGINGFACE_API = 'https://api-inference.huggingface.co';\n\nexport default async ({ req, res, error }) => {\n const bucketId = process.env.APPWRITE_BUCKET_ID ?? 'generated_music';\n\n const response = await fetch(\n `${HUGGINGFACE_API}/models/facebook/musicgen-small`,\n {\n headers: {\n Authorization: `Bearer ${process.env.HUGGINGFACE_ACCESS_TOKEN}`,\n },\n method: 'POST',\n body: JSON.stringify({\n inputs: req.body.prompt,\n }),\n }\n );\n\n const blob = await response.blob();\n const appwrite = new AppwriteService();\n const file = await appwrite.createFile(bucketId, blob);\n\n return res.json({\n ok: true,\n fileId: file.$id,\n });\n};\n```\n\nThis Appwrite Function checks if the required environment variables are set, then processes the text using the Hugging Face API,\nstores the generated audio file in Appwrite Storage.\n{% /section %}\n\n{% section #step-6 step=6 title=\"Test the function\" %}\nTest the function by sending a POST request to the function's endpoint with a JSON body containing the `prompt` parameter.\n\nNavigate to your function in the Appwrite Console and click on **Execute now**. In the modal that appears, enter the following JSON body:\n\n```json\n{\n \"prompt\": \"A happy tune, with a fast tempo, in the key of C major\"\n}\n```\n\nClick **Execute** and you should see a response similar to the following:\n\n```json\n{\n \"ok\": true,\n \"fileId\": \"61f7b3b3c7b7d\"\n}\n```\n\nUse the `fileId` to download the generated audio file from the Appwrite Storage. Here's an example of music generated from the prompt above:\n\n![Audio of generated music](/audio/docs/ai/tutorials/music-generation/generated-music.wav)\n{% /section %}"}, {"path": "docs/products/ai/tutorials/object-detection", "title": "Object detection with Hugging Face", "description": "Build object recognition powered apps with Appwrite and learn how to use Hugging Face's image classification models.", "content": "Learn to setup an Appwrite Function utilizing object detection with Hugging Face.\n\n# Prerequisites {% #prerequisites %}\n\n- An Appwrite project\n- A [Hugging Face API key](https://huggingface.co/docs/api-inference/en/quicktour#get-your-api-token)\n\n{% section #step-1 step=1 title=\"Create a new function\" %}\nHead 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.\n\n{% only_dark %}\n![Create function screen](/images/docs/functions/dark/template.avif)\n{% /only_dark %}\n\n{% only_light %}\n![Create function screen](/images/docs/functions/template.avif)\n{% /only_light %}\n\n1. In the Appwrite Console's sidebar, click **Functions**.\n1. Click **Create function**.\n1. Under **Connect Git repository**, select your provider.\n1. After connecting to GitHub, under **Quick start**, select the **Node.js** starter template.\n1. In the **Variables** step, add the `HUGGINGFACE_ACCESS_TOKEN`, generate it [here](https://huggingface.co/docs/api-inference/en/quicktour#get-your-api-token).\n1. Follow the step-by-step wizard and create the function.\n{% /section %}\n\n{% section #step-2 step=2 title=\"Add dependencies\" %}\nOnce the function is created, clone the function and open it in your development environment.\n\nOnce you have the repository open, you can install the Hugging Face inference SDK by running the following command in your terminal:\n\n```bash\nnpm install @huggingface/inference node-appwrite\n```\n{% /section %}\n\n{% section #step-3 step=3 title=\"Parsing the body\" %}\nAfter installing the SDK, write the code that will parse the body of the request.\nThe function will serve two purposes: it can recieve a body via direct execution or it can be called via a file create event.\n\nOpen up your `src/index.js` file and replace the function body with the following code:\n\n```js\nexport default async ({ req, res, log, error }) => {\n const databaseId = process.env.APPWRITE_DATABASE_ID ?? 'ai';\n const tableId = process.env.APPWRITE_TABLE_ID ?? 'image_classification';\n const bucketId = process.env.APPWRITE_BUCKET_ID ?? 'image_classification';\n\n // Allows using direct execution or file create event\n const fileId = req.body.$id || req.body.imageId;\n if (!fileId) {\n return res.text('Bad request', 400);\n }\n\n if (\n req.body.bucketId &&\n req.body.bucketId != bucketId\n ) {\n return res.text('Bad request', 400);\n }\n}\n```\n\nThis 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.\n{% /section %}\n\n{% section #step-4 step=4 title=\"Setting up your Appwrite Storage bucket\" %}\nCreate 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.\n\n{% only_dark %}\n![Create bucket on console](/images/docs/storage/dark/create-bucket.avif)\n{% /only_dark %}\n\n{% only_light %}\n![Create bucket on console](/images/docs/storage/create-bucket.avif)\n{% /only_light %}\n\nNote down the bucket ID so we can add it as an environment variable later.\n{% /section %}\n\n{% section\n #step-5\n step=5\n title=\"Downloading the image using Appwrite Storage\" %}\nWith the payload parsed, you can now download the image from the Appwrite Storage.\n\nCreate a new file in the `src` directory called `appwrite.js` and add the following code:\n\n```js\nimport { Client, TablesDB, ID, Storage } from 'node-appwrite';\n\nclass AppwriteService {\n constructor() {\n const client = new Client();\n client\n .setEndpoint(\n process.env.APPWRITE_ENDPOINT ?? 'https://.cloud.appwrite.io/v1'\n )\n .setProject(process.env.APPWRITE_FUNCTION_PROJECT_ID)\n .setKey(process.env.APPWRITE_API_KEY);\n\n this.tablesDB = new TablesDB(client);\n this.storage = new Storage(client);\n }\n\n async getFile(bucketId, fileId) {\n return await this.storage.getFileDownload({\n bucketId,\n fileId\n });\n }\n}\n\nexport default AppwriteService;\n```\n\nThis code creates a new `AppwriteService` class that initializes the Appwrite client and provides a method to download a file from the Appwrite Storage.\n\nImport the class into the `src/index.js` file, at the top of the file, add the following line:\n\n```js\nimport AppwriteService from './appwrite.js';\n```\n\nThen, 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:\n\n```js\n const appwrite = new AppwriteService();\n\n const file = await appwrite.getFile(bucketId, fileId);\n```\n\nThis 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.\n{% /section %}\n\n{% section #step-6 step=6 title=\"Detecting objects in the image\" %}\nWith the image downloaded, we can now use the Hugging Face inference SDK to classify the image.\n\nAt the top of the `src/index.js` file, add:\n\n```js\nimport { HfInference } from '@huggingface/inference';\n```\n\nNext 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\n`facebook/detr-resnet-50` model.\n\n```js\nconst hf = new HfInference(process.env.HUGGING_FACE_API_KEY);\n\nconst result = await hf.objectDetection({\n data: file,\n model: 'facebook/detr-resnet-50',\n});\n```\n{% /section %}\n\n{% section #step-7 step=7 title=\"Setting up our database\" %}\nBefore we can save our detection result to the Appwrite Database, we need to create a new database and table in the Appwrite Console.\n\nNavigate 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`.\nOnce 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`.\n\nAdd two string columns to our table, `image` and `labels`. The `image` column will store the ID of the image we're detecting objects in and the `labels` column will store the detection result.\nBoth of these columns should be `required` with `image` having the size of `256` and `labels` having the size of around `4096`.\n\n{% only_dark %}\n![Object detection database](/images/docs/ai/tutorials/object-detection/dark/database.avif)\n{% /only_dark %}\n\n{% only_light %}\n![Object detection database](/images/docs/ai/tutorials/object-detection/database.avif)\n{% /only_light %}\n{% /section %}\n\n{% section #step-8 step=8 title=\"Saving the object detection result\" %}\nWith the image classified, we can now save the result to the Appwrite Database.\n\nTo 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.\n\n```js\nasync createImageLabels(databaseId, tableId, imageId, labels)\n{\n await this.tablesDB.createRow({\n databaseId,\n tableId,\n rowId: ID.unique(),\n data: {\n image: imageId,\n labels: JSON.stringify(labels),\n }\n });\n}\n```\n\nThis abstraction keeps our codebase clean and makes it easier to test and maintain.\n\nNext, 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.\n\nAdd the following code:\n\n```js\nawait appwrite.createImageLabels(databaseId, tableId, fileId, result);\n\nlog('Image ' + fileId + ' detected', result);\nreturn res.json(result);\n```\n{% /section %}\n\n{% section #step-9 step=9 title=\"Configuring events\" %}\nTo test our function we're going to attach it directly to our Storage bucket using events.\n\nNavigate to your function in the Appwrite Console and visit it's **settings** tab,\nthen under the **Events** section click on the **Add Event** button.\n\nAt the bottom of the dialog within the text input, click on the pen icon and enter `buckets.[Bucket ID].files.*.create`.\nMaking sure to replace `[Bucket ID]` with the ID of the bucket you created earlier.\n\n{% only_dark %}\n![Object detection event](/images/docs/ai/tutorials/object-detection/dark/event.avif)\n{% /only_dark %}\n\n{% only_light %}\n![Object detection event](/images/docs/ai/tutorials/object-detection/event.avif)\n{% /only_light %}\n{% /section %}\n\n{% section #step-10 step=10 title=\"Testing the function\" %}\nTest our function by uploading an image to the Appwrite Storage.\n\nNavigate to the Appwrite Console and click on **Storage** in the left sidebar, then click on the **Upload File** button and upload an image.\nAfter 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.\n\n{% only_dark %}\n![Object detection test](/images/docs/ai/tutorials/object-detection/dark/result.avif)\n{% /only_dark %}\n\n{% only_light %}\n![Object detection test](/images/docs/ai/tutorials/object-detection/result.avif)\n{% /only_light %}\n{% /section %}"}, {"path": "docs/products/ai/tutorials/speech-recognition", "title": "Speech recognition with Hugging Face", "description": "Implement speech recognition into your app with Appwrite and Hugging Face.", "content": "Hugging Face is a platform that hosts ML models for all types of applications, including for speech recognition.\nThis example uses the `openai/whisper-large-v3` from Hugging Face to perform speech recognition.\n\n# Prerequisites {% #prerequisites %}\n\n- An Appwrite project\n- A [Hugging Face API key](https://huggingface.co/docs/api-inference/en/quicktour#get-your-api-token)\n\n{% section #step-1 step=1 title=\"Create new function\" %}\nHead 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.\n\n{% only_dark %}\n![Create function screen](/images/docs/functions/dark/template.avif)\n{% /only_dark %}\n\n{% only_light %}\n![Create function screen](/images/docs/functions/template.avif)\n{% /only_light %}\n\n1. In the Appwrite Console's sidebar, click **Functions**.\n1. Click **Create function**.\n1. Under **Connect Git repository**, select your provider.\n1. After connecting to GitHub, under **Quick start**, select the **Node.js** starter template.\n1. In the **Variables** step, add the `HUGGINGFACE_ACCESS_TOKEN`, generate it [here](https://huggingface.co/docs/api-inference/en/quicktour#get-your-api-token).\n1. Follow the step-by-step wizard and create the function.\n{% /section %}\n\n{% section #step-2 step=2 title=\"Add dependencies\" %}\nOnce the function is created, clone the function and open it in your development environment.\n\nInstall the Hugging Face SDK and the Appwrite Node.js SDK so we can upload the generated audio file to Appwrite Storage.\n\n```bash\nnpm install @huggingface/inference node-appwrite\n```\n{% /section %}\n\n{% section #step-3 step=3 title=\"Create an Appwrite service\" %}\nThe function will interact with Appwrite to store the original audio and generated text transcript.\nTo make this easier, create a service class that will handle all the Appwrite interactions.\n\nCreate a file called `src/appwrite.js` and implement the following class:\n\n```js\nimport { Client, TablesDB, ID, Storage } from 'node-appwrite';\n\nclass AppwriteService {\n constructor() {\n const client = new Client();\n client\n .setEndpoint(\n process.env.APPWRITE_ENDPOINT ?? 'https://.cloud.appwrite.io/v1'\n )\n .setProject(process.env.APPWRITE_FUNCTION_PROJECT_ID)\n .setKey(process.env.APPWRITE_API_KEY);\n\n this.tablesDB = new TablesDB(client);\n this.storage = new Storage(client);\n }\n\n async createRecognitionEntry(databaseId, tableId, audioId, speech) {\n await this.tablesDB.createRow({\n databaseId,\n tableId,\n rowId: ID.unique(),\n data: {\n audio: audioId,\n speech: speech,\n }\n });\n }\n\n async getFile(bucketId, fileId) {\n return await this.storage.getFileDownload({\n bucketId,\n fileId\n });\n }\n}\n\nexport default AppwriteService;\n```\n\nThe constructor initializes the Appwrite client and the database and storage services.\nThis `createRecognitionEntry` method creates a row in the Appwrite database with the audio and speech recognition text.\nThis `getFile` method retrieves a file from Appwrite Storage.\n{% /section %}\n\n{% section #step-4 step=4 title=\"Create Storage bucket\" %}\nIn 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.\n\n{% only_dark %}\n![Create bucket on console](/images/docs/storage/dark/create-bucket.avif)\n{% /only_dark %}\n\n{% only_light %}\n![Create bucket on console](/images/docs/storage/create-bucket.avif)\n{% /only_light %}\n\nUse the default configuration for the bucket. Make sure to note down the bucket ID so you can add it as an environment variable later.\n{% /section %}\n\n{% section #step-5 step=5 title=\"Create Appwrite table\" %}\nBefore saving the classification result to Appwrite Databases, create a new database and table in the Appwrite Console.\n\nNavigate 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`.\nOnce you've created the database, click on the **Create table** button and create a new table, and name it, for example `Speech Recognition`.\n\nNext, create the following schema for the table:\n\n| Column | Type | Size | Required | Array |\n| --------- | --------- | --------- | --------- | --------- |\n| audio | String | 64 | true | false |\n| speech | String | 10000 | true | false |\n{% /section %}\n\n{% section #step-6 step=6 title=\"Integrate with Hugging Face\" %}\nIn `src/main.js` implement the following function to convert speech to a text transcript using the Hugging Face API.\n\n```js\nimport { HfInference } from '@huggingface/inference';\nimport AppwriteService from './appwrite.js';\n\nexport default async ({ req, res, log, error }) => {\n const databaseId = process.env.APPWRITE_DATABASE_ID ?? 'ai';\n const tableId = process.env.APPWRITE_TABLE_ID ?? 'speech_recognition';\n const bucketId = process.env.APPWRITE_BUCKET_ID ?? 'speech_recognition';\n\n let fileId = req.body.$id || req.body.fileId;\n\n if (!fileId) {\n return res.text('Bad request', 400);\n }\n\n if (\n req.body.bucketId &&\n req.body.bucketId != bucketId\n ) {\n return res.text('Bad request', 400);\n }\n\n const appwrite = new AppwriteService();\n\n const file = await appwrite.getFile(bucketId, fileId);\n\n const hf = new HfInference(process.env.HUGGING_FACE_API_KEY);\n\n const result = await hf.automaticSpeechRecognition({\n data: file,\n model: 'openai/whisper-large-v3',\n });\n\n await appwrite.createRecognitionEntry(databaseId, tableId, fileId, result.text);\n\n log('Audio ' + fileId + ' recognised', result.text);\n return res.json({ text: result.text });\n};\n```\n\nThis Appwrite Function checks if the required environment variables are set, then load the original audio from Appwrite Storage.\nThe function processes the audio file using the Hugging Face API,\nstores the generated text transcript in Appwrite Databases and returns the transcript text.\n{% /section %}\n\n{% section #step-7 step=7 title=\"Test the function\" %}\nTest our function by uploading an audio file the Appwrite Storage.\n\nNavigate to the Appwrite Console and click on **Storage** in the left sidebar, then click on the **Upload File** button and upload an image.\nAfter 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.\n\n{% only_dark %}\n![Speech recognition test](/images/docs/ai/tutorials/speech-recognition/dark/result.avif)\n{% /only_dark %}\n\n{% only_light %}\n![Speech recognition test](/images/docs/ai/tutorials/speech-recognition/result.avif)\n{% /only_light %}\n{% /section %}"}, {"path": "docs/products/ai/tutorials/text-generation", "title": "Text generation with Hugging Face", "description": "Implement text generation into your app with Appwrite and Hugging Face.", "content": "Learn to setup an Appwrite Function utilizing text generation with Hugging Face.\n\n# Prerequisites {% #prerequisites %}\n\n- An Appwrite project\n- A [Hugging Face API keys](https://huggingface.co/docs/api-inference/en/quicktour#get-your-api-token)\n\n{% section #step-1 step=1 title=\"Create new function\" %}\nHead 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.\n\n{% only_dark %}\n![Create function screen](/images/docs/functions/dark/template.avif)\n{% /only_dark %}\n\n{% only_light %}\n![Create function screen](/images/docs/functions/template.avif)\n{% /only_light %}\n\n1. In the Appwrite Console's sidebar, click **Functions**.\n1. Click **Create function**.\n1. Under **Connect Git repository**, select your provider.\n1. After connecting to GitHub, under **Quick start**, select the **Node.js** starter template.\n1. In the **Variables** step, add the `HUGGINGFACE_ACCESS_TOKEN`, generate it [here](https://huggingface.co/docs/api-inference/en/quicktour#get-your-api-token).\n1. Follow the step-by-step wizard and create the function.\n{% /section %}\n\n{% section #step-2 step=2 title=\"Add HuggingFace SDK\" %}\nOnce the function is created, clone the function and open it in your development environment.\n\nOnce you have the repository open, you can install the Hugging Face inference SDK by running the following command in your terminal:\n\n```bash\nnpm install @huggingface/inference\n```\n{% /section %}\n\n{% section #step-3 step=3 title=\"Parse payload body\" %}\nAfter installing the SDK, write the code that will accept a JSON body.\n\nOpen up your `src/main.js` file and replace the function body with the following code:\n\n```js\nexport default async ({ req, res }) => {\n if (!req.body.prompt || typeof req.body.prompt !== 'string') {\n return res.json({\n ok: false,\n error: 'Missing required field `prompt`'\n }, 400);\n }\n}\n```\n{% /section %}\n\n{% section #step-4 step=4 title=\"Make a request to Hugging Face\" %}\nAdd the following import at the top of your `src/main.js` file:\n\n```js\nimport { HfInference } from '@huggingface/inference';\n```\n\nIn your function body, add the following code after the parameter checks:\n\n```js\n\nexport default async ({ req, res }) => {\n // ... existing parameter checks\n\n const hf = new HfInference(process.env.HUGGINGFACE_ACCESS_TOKEN);\n\n try {\n const completion = await hf.textGeneration({\n model: 'mistralai/Mistral-7B-Instruct-v0.2',\n inputs: req.body.prompt,\n max_new_tokens: req.body.max_new_tokens || 200,\n });\n return res.json({ ok: true, completion }, 200);\n } catch (err) {\n return res.json({ ok: false, error: 'Failed to query model.' }, 500);\n }\n}\n```\n\nThe 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.\n{% /section %}\n\n{% section #step-5 step=5 title=\"Test the function\" %}\nTest our function by sending a POST request to the function's endpoint with a JSON body containing the `prompt` parameter.\n\nNavigate to your function in the Appwrite Console and click on **Execute now**. In the modal that appears, enter the following JSON body:\n\n```json\n{\n \"prompt\": \"Write a story about a dragon\",\n}\n```\n\nClick **Execute** and you should see a response similar to the following:\n\n```json\n{\n \"ok\": true,\n \"completion\": \"Once upon a time, in a land far away, there was a dragon... [truncated]\"\n}\n```\n{% /section %}"}, {"path": "docs/products/ai/tutorials/text-to-speech", "title": "Text to Speech with Hugging Face", "description": "Learn how to integrate Hugging Face into your Appwrite project for text to speech processing.", "content": "Hugging Face is a platform that hosts ML models for all types of applications, including text to speech.\nThis 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.\n\n# Prerequisites {% #prerequisites %}\n\n- An Appwrite project\n- A [Hugging Face API keys](https://huggingface.co/docs/api-inference/en/quicktour#get-your-api-token)\n\n{% section #step-1 step=1 title=\"Create new function\" %}\nHead 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.\n\n{% only_dark %}\n![Create function screen](/images/docs/functions/dark/template.avif)\n{% /only_dark %}\n\n{% only_light %}\n![Create function screen](/images/docs/functions/template.avif)\n{% /only_light %}\n\n1. In the Appwrite Console's sidebar, click **Functions**.\n1. Click **Create function**.\n1. Under **Connect Git repository**, select your provider.\n1. After connecting to GitHub, under **Quick start**, select the **Node.js** starter template.\n1. 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**.\n1. Follow the step-by-step wizard and create the function.\n{% /section %}\n\n{% section #step-2 step=2 title=\"Add dependencies\" %}\nOnce the function is created, clone the function and open it in your development environment.\n\nInstall the `undici` package (global `fetch` is not available in Node.js 16) to make requests to the Hugging Face API.\nInstall the `node-appwrite` package, to simplify uploading the generated audio file to Appwrite Storage.\n\n```bash\nnpm install undici node-appwrite\n```\n{% /section %}\n\n{% section #step-3 step=3 title=\"Create an Appwrite service\" %}\nThe function will interact with Appwrite to store the generated audio files and the text-to-speech data.\nTo make this easier, create a service class that will handle all the Appwrite interactions.\n\nCreate a file called `src/appwrite.js` and implement the following class:\n\n```js\nimport { Client, ID, Storage } from 'node-appwrite';\nimport { InputFile } from 'node-appwrite/file';\n\n\nclass AppwriteService {\n constructor() {\n const client = new Client();\n client\n .setEndpoint(\n process.env.APPWRITE_ENDPOINT ?? 'https://.cloud.appwrite.io/v1'\n )\n .setProject(process.env.APPWRITE_FUNCTION_PROJECT_ID)\n .setKey(process.env.APPWRITE_API_KEY);\n\n this.storage = new Storage(client);\n }\n\n async createFile(bucketId, blob) {\n const file = await InputFile.fromBuffer(blob, 'audio.flac');\n return await this.storage.createFile({\n bucketId: bucketId,\n fileId: ID.unique(),\n file: file\n });\n }\n}\n\nexport default AppwriteService;\n```\n{% /section %}\n\n{% section #step-4 step=4 title=\"Create Storage bucket\" %}\nIn 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.\n\n{% only_dark %}\n![Create bucket on console](/images/docs/storage/dark/create-bucket.avif)\n{% /only_dark %}\n\n{% only_light %}\n![Create bucket on console](/images/docs/storage/create-bucket.avif)\n{% /only_light %}\n\nUse the default configuration for the bucket. Make sure to note down the bucket ID so you can add it as an environment variable later.\n{% /section %}\n\n{% section #step-5 step=5 title=\"Integrate with Hugging Face\" %}\nin `src/main.js` implement the following function to convert text to speech using the Hugging Face API.\n\n```js\nimport fetch from 'node-fetch';\nimport { throwIfMissing } from './utils.js';\nimport AppwriteService from './appwrite.js';\n\nconst HUGGINGFACE_API = 'https://api-inference.huggingface.co';\n\nexport default async ({ req, res, error }) => {\n const bucketId = process.env.APPWRITE_BUCKET_ID ?? 'generated_speech';\n\n if (!req.body.text || typeof req.body.text !== 'string') {\n return res.json({ ok: false, error: 'Missing required field `text`' }, 400);\n }\n\n const response = await fetch(\n `${HUGGINGFACE_API}/models/espnet/kan-bayashi_ljspeech_vits`,\n {\n headers: {\n Authorization: `Bearer ${process.env.HUGGINGFACE_ACCESS_TOKEN}`,\n },\n method: 'POST',\n body: JSON.stringify({\n inputs: req.body.text,\n }),\n }\n );\n\n if (!response.ok) {\n error(await response.text());\n return res.json({ ok: false, error: 'Failed to process text' }, 500);\n }\n\n const blob = await response.blob();\n const appwrite = new AppwriteService();\n const file = await appwrite.createFile(bucketId, blob);\n\n return res.json({\n ok: true,\n fileId: file.$id,\n });\n};\n```\n\nThis Appwrite Function checks if the required environment variables are set, then processes the text using the Hugging Face API,\nstores the generated audio file in Appwrite Storage, and creates a row in the Appwrite database of the original text.\n{% /section %}\n\n{% section #step-6 step=6 title=\"Test the function\" %}\nTest the function by sending a POST request to the function's endpoint with a JSON body containing the `text` parameter.\n\nNavigate to your function in the Appwrite Console and click on **Execute now**. In the modal that appears, enter the following JSON body:\n\n```json\n{\n \"text\": \"Hello, world!\"\n}\n```\n\nClick **Execute** and you should see a response similar to the following:\n\n```json\n{\n \"ok\": true,\n \"fileId\": \"61f7b3b3c7b7d\"\n}\n```\n\nThen, use the fileId to download the generated audio file from the Appwrite Storage.\n{% /section %}"}, {"path": "docs/products/auth", "title": "Authentication", "description": "Explore Appwrite's powerful authentication solutions. Learn how to implement secure user authentication, manage user identities, and enhance your application's security.\"", "content": "Appwrite **Authentication** delivers more than just user sign up and log in.\nAuthentication makes it easy to build secure and robust authentication with support for many different authentication methods.\n\n{% arrow_link href=\"/docs/products/auth/quick-start\" %}\nAdd authentication to your app in 5 minutes\n{% /arrow_link %}\n\n# Authentication methods {% #auth-methods %}\n\nAppwrite supports a variety of authentication methods to fit every app and every niche. Explore Appwrite's authentication flows.\n\n{% cards %}\n{% cards_item href=\"/docs/products/auth/email-password\" title=\"Email and password\" %}\nEmail and password login with just a few lines of code secured with state of the art Argon2 hashing.\n{% /cards_item %}\n{% cards_item href=\"/docs/products/auth/phone-sms\" title=\"Phone (SMS)\" %}\nLog in users without a password using their phone number and SMS verification.\n{% /cards_item %}\n{% cards_item href=\"/docs/products/auth/magic-url\" title=\"Magic URL\" %}\nPasswordless login with a magic link sent to the user's email.\n{% /cards_item %}\n{% cards_item href=\"/docs/products/auth/email-otp\" title=\"Email OTP\" %}\nGenerate a time-based single-use password sent to the user's email.\n{% /cards_item %}\n{% cards_item href=\"/docs/products/auth/oauth2\" title=\"OAuth 2\" %}\nAuthenticate users with existing accounts from GitHub, Google, Facebook, and 30+ other providers.\n{% /cards_item %}\n{% cards_item href=\"/docs/products/auth/anonymous\" title=\"Anonymous\" %}\nCreate guest sessions for visitors and convert to full accounts when they're ready.\n{% /cards_item %}\n{% cards_item href=\"/docs/products/auth/jwt\" title=\"JWT\" %}\nDeligate access for a user through passing JWT tokens.\n{% /cards_item %}\n{% cards_item href=\"/docs/products/auth/server-side-rendering\" title=\"Server-side rendering (SSR)\" %}\nAuthenticate users in server-side rendered applications.\n{% /cards_item %}\n{% cards_item href=\"/docs/products/auth/custom-token\" title=\"Custom token\" %}\nImplement custom authentication methods like biometric and passkey login by generating custom tokens.\n{% /cards_item %}\n{% cards_item href=\"/docs/products/auth/mfa\" title=\"Multifactor authentication (MFA)\" %}\nImplementing MFA to add extra layers of security to your app.\n{% /cards_item %}\n{% cards_item href=\"/docs/products/auth/presences\" title=\"Presences\" %}\nTrack which signed-in users are active right now and broadcast online, typing, and viewing status in realtime.\n{% /cards_item %}\n{% /cards %}\n\n# Flexible permissions {% #flexible-permissions %}\n\nWhen users sign up using Appwrite, their identity is automatically attached to a robust permissions system.\nAppwrite Authentication provides permissions for individual users and groups of users through [teams](/docs/products/auth/teams) and [labels](/docs/products/auth/labels).\n\n# Built in preferences {% #built-in-preferences %}\n\nAppwrite **Authentication** comes with built-in [preferences](/docs/products/auth/preferences) for users to manage their account settings.\nStore notification settings, themes, and other user preferences to be shared across devices."}, {"path": "docs/products/auth/accounts", "title": "Accounts", "description": "Unlock advanced user management - Appwrite's Account API for seamless signups, authentication, and dynamic permissions.", "content": "Appwrite Account API is used for user signup and login in client applications.\nUsers can be organized into teams and be given labels, so they can be given different permissions and access different resources.\n\n{% partial file=\"account-vs-user.md\" /%}\n\n# Signup and login {% #signup-login %}\n\nYou can signup and login a user with an account create through\n[email password](/docs/products/auth/email-password),\n[phone (SMS)](/docs/products/auth/phone-sms),\n[Anonymous](/docs/products/auth/anonymous),\n[magic URL](/docs/products/auth/magic-url), and\n[OAuth 2](/docs/products/auth/oauth2)\nauthentication.\n\nTo control which email addresses can sign up, enable [email policies](/docs/products/auth/email-policies) to block free, aliased, or disposable email providers.\n\n# Permissions {% #permissions %}\n\nYou can grant permissions to all users using the `Role.users()` role or\nindividual users using the `Role.user(, )` role.\n| Description | Role |\n| ------------------------------------------- | ------------------------------------------- |\n| Verified users | `Role.users('verified')`|\n| Unverified users | `Role.users('unverified')` |\n| Verified user | `Role.user(, 'verified')`|\n| Unverified user | `Role.user(, 'unverified')` |\n\n\n{% arrow_link href=\"/docs/advanced/security/permissions\" %}\nLearn more about permissions\n{% /arrow_link %}"}, {"path": "docs/products/auth/anonymous", "title": "Anonymous login", "description": "Manage user identities and profiles effectively with Appwrite. Dive into user management features, account settings, and user data customization.", "content": "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.\n\n**If a user later creates an account**, their information will be inherited by the newly created account.\n\n# Create anonymous session {% #createSession %}\n\nCreate an anonymous session with [Create Anonymous Session](/docs/references/cloud/client-web/account#createAnonymousSession) method.\n\n{% multicode %}\n```client-web\nimport { Client, Account } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject(''); // Your project ID\n\nconst account = new Account(client);\n\nconst promise = account.createAnonymousSession();\n\npromise.then(function (response) {\n console.log(response); // Success\n}, function (error) {\n console.log(error); // Failure\n});\n```\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject(''); // Your project ID\n\nfinal account = Account(client);\n\nfinal user = await account.createAnonymousSession();\n```\n```client-apple\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n\nlet account = Account(client)\n\nlet user = try await account.createAnonymousSession()\n```\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Account\n\nval client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n\nval account = Account(client)\n\nval user = account.createAnonymousSession()\n```\n```graphql\nmutation {\n accountCreateAnonymousSession {\n _id\n userId\n provider\n expire\n }\n}\n```\n{% /multicode %}\n\n# Attaching an account {% #attach-account %}\n\nAnonymous 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.\n\nCreate an account with any of these methods to transition from an anonymous session to a user account session.\n\n{% arrow_link href=\"/docs/products/auth/email-password\" %}\nEmail and password\n{% /arrow_link %}\n\n{% arrow_link href=\"/docs/products/auth/phone-sms\" %}\nPhone (SMS)\n{% /arrow_link %}\n\n{% arrow_link href=\"/docs/products/auth/magic-url\" %}\nMagic URL\n{% /arrow_link %}\n\n{% arrow_link href=\"/docs/products/auth/oauth2\" %}\nOAuth2\n{% /arrow_link %}"}, {"path": "docs/products/auth/checking-auth-status", "title": "Checking auth status", "description": "Learn how to check a user's authentication status in your Appwrite application and handle authentication flow appropriately.", "content": "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.\n\n# Check auth with `account.get()`\n\nThe recommended approach for checking authentication status is to use the `account.get()` method when your application starts:\n\n{% multicode %}\n```client-web\nimport { Client, Account } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nconst account = new Account(client);\n\n// Check if user is logged in\nasync function checkAuthStatus() {\n try {\n // If successful, user is authenticated\n const user = await account.get();\n console.log(\"User is authenticated:\", user);\n // Proceed with your authenticated app flow\n return user;\n } catch (error) {\n console.error(\"User is not authenticated:\", error);\n // Redirect to login page or show login UI\n // window.location.href = '/login';\n return null;\n }\n}\n\n// Call this function when your app initializes\ncheckAuthStatus();\n```\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nvoid checkAuthStatus() async {\n final client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\n final account = Account(client);\n\n try {\n // If successful, user is authenticated\n final user = await account.get();\n print('User is authenticated: ${user.name}');\n // Proceed with your authenticated app flow\n } catch (e) {\n print('User is not authenticated: $e');\n // Redirect to login page or show login UI\n }\n}\n\n// Call this function when your app initializes\n```\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Account\nimport io.appwrite.exceptions.AppwriteException\n\nclass AuthManager {\n private val client = Client(context)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\n private val account = Account(client)\n\n suspend fun checkAuthStatus(): Boolean {\n return try {\n val user = account.get()\n Log.d(\"Auth\", \"User is authenticated: ${user.name}\")\n // Proceed with your authenticated app flow\n true\n } catch (e: AppwriteException) {\n Log.e(\"Auth\", \"User is not authenticated: ${e.message}\")\n // Redirect to login page or show login UI\n false\n }\n }\n}\n\n// Call this function when your app initializes\n```\n```client-apple\nimport Appwrite\n\nfunc checkAuthStatus() {\n let client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\n let account = Account(client)\n\n Task {\n do {\n // If successful, user is authenticated\n let user = try await account.get()\n print(\"User is authenticated: \\(user.name)\")\n // Proceed with your authenticated app flow\n } catch {\n print(\"User is not authenticated: \\(error)\")\n // Redirect to login page or show login UI\n }\n }\n}\n\n// Call this function when your app initializes\n```\n{% /multicode %}\n\n# Missing scope error\n\nWhen a user is not authenticated and you call `account.get()`, you might see an error message like:\n\n```\nUser (role: guests) missing scope (account)\n```\n\nThis error is telling you that:\n1. The current user has the role of \"guest\" (unauthenticated visitor)\n2. This guest user does not have the required permission scope to access account information\n3. This is the expected behavior when a user is not logged in\n\n{% info title=\"Authentication flow\" %}\nIn a typical application flow:\n\n1. Call `account.get()` when your app starts\n2. If successful → User is authenticated → Show the main app UI\n3. If error → User is not authenticated → Redirect to login screen\n{% /info %}\n\n# Best practices\n\n- Call `account.get()` early in your application lifecycle\n- Handle both authenticated and unauthenticated states gracefully\n- Show appropriate loading states while checking authentication\n- Implement proper error handling to avoid showing error messages to users"}, {"path": "docs/products/auth/custom-token", "title": "Custom token login", "description": "Limitless authentication flow in Appwrite. Find out how to implement custom authentication flow or connect to 3rd party authentication providers.", "content": "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).\n\nCustom 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.\n\n# Create custom token {% #create-custom-token %}\n\nOnce 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.\n\n{% multicode %}\n```server-nodejs\nimport { Client, Users } from \"node-appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject(''); // Your project ID\n .setKey(''); // Your project API key\n\nconst users = new Users(client);\n\nconst token = await users.createToken({\n userId: ''\n});\nconst secret = token.secret;\n```\n\n```php\nuse Appwrite\\Client;\nuse Appwrite\\Users;\n\n$client = (new Client())\n ->setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n ->setProject('') // Your project ID\n ->setKey(''); // Your project API key\n\n$users = new Users($client);\n\n$token = $users->createToken('');\n$secret = $token['secret'];\n```\n\n```python\nfrom appwrite.client import Client\nfrom appwrite.users import Users\n\nclient = Client()\n\n(client\n .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('') # Your project ID\n .set_key('') # Your project API key\n)\n\nusers = Users(client)\n\ntoken = users.create_token('')\nsecret = token.secret\n```\n\n```ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('') # Your project ID\n .set_key('') # Your project API key\n\nusers = Users.new(client)\n\ntoken = users.create_token('')\nsecret = token['secret']\n```\n\n```deno\nimport { Client, Users } from \"npm:node-appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n .setKey(''); // Your project API key\n\nconst users = new Users(client);\n\nconst token = await users.createToken({\n userId: '[USER_ID]'\n});\nconst secret = token.secret;\n```\n\n```dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n .setKey(''); // Your project API key\n\nfinal users = Users(client);\n\nfinal token = await users.createToken('');\nfinal secret = token.secret;\n```\n\n```kotlin\nimport io.appwrite.Client\nimport io.appwrite.Users\n\nval client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n .setKey(\"\") // Your project API key\n\nval users = Users(client)\n\nval token = users.createToken(\"\")\nval secret = token.secret\n```\n\n```swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n .setKey(\"\") // Your project API key\n\nlet users = Users(client)\n\nlet token = try await users.createToken(\"\")\nlet secret = token.secret\n```\n\n```csharp\nusing Appwrite;\n\nvar client = new Client()\n .SetEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .SetProject(\"\") // Your project ID\n .SetKey(\"\"); // Your project API key\n\nvar users = new Users(client);\n\nvar token = await users.CreateToken(\"\");\nvar secret = token.secret;\n```\n\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::users::Users;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box> {\n let client = Client::new()\n .set_endpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .set_project(\"\") // Your project ID\n .set_key(\"\"); // Your project API key\n\n let users = Users::new(&client);\n\n let token = users.create_token(\n \"\",\n None, // length (optional)\n None, // expire (optional)\n ).await?;\n\n let secret = token.secret;\n println!(\"{}\", secret);\n Ok(())\n}\n```\n{% /multicode %}\n\nThe 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.\n\nIf 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.\n\nIf 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.\n\nIf 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.\n\n# Login {% #login %}\n\nOnce 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.\n\n{% multicode %}\n\n```client-web\nimport { Client, Account } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nconst account = new Account(client);\n\nconst session = await account.createSession({\n userId: '',\n secret: ''\n});\n```\n\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nfinal account = Account(client);\n\nfinal session = await account.createSession(\n userId: '',\n secret: ''\n);\n```\n```client-apple\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\");\n\nlet account = Account(client);\n\nlet session = try await account.createSession(\n userId: \"\",\n secret: \"\"\n);\n```\n\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Account\n\nval client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\");\n\nval account = Account(client);\n\nval session = account.createSession(\n userId = \"\",\n secret = \"\"\n);\n```\n\n```graphql\nmutation {\n accountcreateSession(userId: \"\", secret: \"\") {\n _id\n userId\n provider\n expire\n }\n}\n```\n\n{% /multicode %}\n\nWhen 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."}, {"path": "docs/products/auth/email-otp", "title": "Email OTP", "description": "Seamless sign in with Email OTP authentication in Appwrite. Learn how to provide simple and secure passwordless user accounts.", "content": "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.\n\n{% info title=\"Email OTP vs Magic URL\" %}\nEmail 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.\nBoth allow passwordless login flows with different advantages.\n\n| Benefits of Email OTP | Downsides of Email OTP |\n|--------------------------------------------------------------------|------------------------------------------|\n| Doesn't require user to be signed into email inbox on the device | Expires quicker |\n| Doesn't disturb application flow with a redirect | Requires more inputs from user |\n| Doesn't require deep linking on mobile apps | |\n\n{% /info %}\n\n# Send email {% #send-email %}\n\nEmail 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.\n\nSend 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.\n\n{% multicode %}\n\n```client-web\nimport { Client, Account, ID } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nconst account = new Account(client);\n\nconst sessionToken = await account.createEmailToken({\n userId: ID.unique(),\n email: 'email@example.com'\n});\n\nconst userId = sessionToken.userId;\n```\n\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nfinal account = Account(client);\n\nfinal sessionToken = await account.createEmailToken(\n userId: ID.unique(),\n email: 'email@example.com'\n);\n\nfinal userId = sessionToken.userId;\n```\n```client-apple\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\");\n\nlet account = Account(client);\n\nlet sessionToken = try await account.createEmailToken(\n userId: ID.unique(),\n email: \"email@example.com\"\n);\n\nlet userId = sessionToken.userId;\n```\n\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Account\nimport io.appwrite.ID\n\nval client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\");\n\nval account = Account(client);\n\nval sessionToken = account.createEmailToken(\n userId = ID.unique(),\n email = \"email@example.com\"\n);\n\nval userId = sessionToken.userId;\n```\n```graphql\nmutation {\n accountCreateEmailToken(userId: \"unique()\", email: \"email@example.com\") {\n _id\n userId\n secret\n expire\n }\n}\n```\n\n{% /multicode %}\n\n# Login {% #login %}\n\nAfter 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.\n\n{% multicode %}\n\n```client-web\nimport { Client, Account, ID } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nconst account = new Account(client);\n\nconst session = await account.createSession({\n userId: userId,\n secret: ''\n});\n```\n\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nfinal account = Account(client);\n\nfinal session = await account.createSession(\n userId: userId,\n secret: ''\n);\n```\n```client-apple\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\");\n\nlet account = Account(client);\n\nlet session = try await account.createSession(\n userId: userId,\n secret: \"\"\n);\n```\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Account\nimport io.appwrite.ID\n\nval client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\");\n\nval account = Account(client);\n\nval session = account.createSession(\n userId = userId,\n secret = \"\"\n);\n```\n\n```graphql\nmutation {\n accountcreateSession(userId: \"\", secret: \"\") {\n _id\n userId\n provider\n expire\n }\n}\n```\n\n{% /multicode %}\n\nAfter the secret is verified, a session will be created.\n\n# Security phrase {% #security-phrase %}\n\nA 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.\n\nBy default, security phrases are disabled. To enable a security phrase in Email OTP, enable it in first step of the authentication flow.\n\n{% multicode %}\n```client-web\nimport { Client, Account, ID } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject(''); // Your project ID\n\nconst account = new Account(client);\n\nconst promise = account.createEmailToken({\n userId: ID.unique(),\n email: 'email@example.com',\n phrase: true\n });\n\npromise.then(function (response) {\n console.log(response); // Success\n}, function (error) {\n console.log(error); // Failure\n});\n```\n\n```graphql\nmutation {\n accountCreateEmailToken(\n userId: \"ID.unique()\",\n email: \"email@example.com\",\n phrase: true\n ) {\n _id\n _createdAt\n userId\n secret\n expire\n phrase\n }\n}\n```\n\n{% /multicode %}\n\nBy 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."}, {"path": "docs/products/auth/email-password", "title": "Email and password login", "description": "Implement email and password authentication with Appwrite. Securely register and authenticate users in your applications using Appwrite's robust email-based authentication system.", "content": "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.\n\n# Signup {% #sign-up %}\n\nYou can use the Appwrite Client SDKs to create an account using email and password.\n\n```client-web\nimport { Client, Account, ID } from \"appwrite\";\n\nconst client = new Client()\n .setProject('') // Your project ID\n .setEndpoint('https://.cloud.appwrite.io/v1');\n\nconst account = new Account(client);\n\ntry {\n const user = await account.create({\n userId: '[USER_ID]',\n email: 'email@example.com',\n password: ''\n });\n console.log(user)\n} catch (e){\n console.error(e)\n}\n\n```\n\nPasswords are hashed with [Argon2](https://github.com/P-H-C/phc-winner-argon2), a resilient and secure password hashing algorithm.\n\n# Login {% #login %}\n\nAfter an account is created, users can be logged in using the Create Email Session route.\n\n```client-web\nimport { Client, Account } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject(''); // Your project ID\n\nconst account = new Account(client);\n\nconst result = await account.createEmailPasswordSession({\n email: 'email@example.com',\n password: 'password'\n});\n\nconsole.log(result);\n\n```\n\n# Verification {% #verification %}\n\nAfter 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.\n\nFirst, 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`.\n\n```client-web\nimport { Client, Account } from \"appwrite\";\n\nconst client = new Client()\n .setProject('') // Your project ID\n\nconst account = new Account(client);\n\nconst promise = account.createVerification({\n url: 'https://example.com/verify'\n});\n\npromise.then(function (response) {\n console.log(response); // Success\n}, function (error) {\n console.log(error); // Failure\n});\n```\n\nNext, 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`.\n\nSince the secrets are passed in through url params, it will be easiest to perform this step in the browser.\n\n```client-web\nimport { Client, Account } from \"appwrite\";\n\nconst client = new Client()\n .setProject(''); // Your project ID\n\nconst account = new Account(client);\n\nconst urlParams = new URLSearchParams(window.location.search);\nconst secret = urlParams.get('secret');\nconst userId = urlParams.get('userId');\n\nconst promise = account.updateVerification({\n userId,\n secret\n});\n\npromise.then(function (response) {\n console.log(response); // Success\n}, function (error) {\n console.log(error); // Failure\n});\n```\n\n# Password Recovery {% #password-recovery %}\n\nIf 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.\n\nOnly 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.\n\n```client-web\nimport { Client, Account } from \"appwrite\";\n\nconst client = new Client()\n .setProject(''); // Your project ID\n\nconst promise = account.createRecovery({\n email: 'email@example.com',\n url: 'https://example.com/recovery'\n});\n\npromise.then(function (response) {\n console.log(response); // Success\n}, function (error) {\n console.log(error); // Failure\n});\n```\n\nAfter 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.\n\n```client-web\nimport { Client, Account } from \"appwrite\";\n\nconst client = new Client()\n .setProject(''); // Your project ID\n\nconst promise = account.updateRecovery({\n userId: '',\n secret: '',\n password: 'password'\n});\n\npromise.then(function (response) {\n console.log(response); // Success\n}, function (error) {\n console.log(error); // Failure\n});\n```\n\n# Security {% #security %}\n\nAppwrite'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."}, {"path": "docs/products/auth/email-policies", "title": "Email policies", "description": "Control which email addresses can sign up for your Appwrite project by blocking free, aliased, or disposable email providers from the Console or Project API.", "content": "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.\n\nThree policies are available:\n\n| Policy | Blocks | Example |\n| --- | --- | --- |\n| Deny free emails | Addresses from free email providers | `user@gmail.com` |\n| Deny aliased emails | Addresses with aliases, tags, subaddresses, or any provider-specific variation | `user+folder1@gmail.com` |\n| Deny disposable emails | Temporary and disposable email providers | `alex9734@mailinator.com` |\n\nPolicies can be configured from the Appwrite Console or programmatically through any server SDK using the Project service.\n\n# Manage from the Console {% #manage-console %}\n\n{% only_dark %}\n![Email policies card in the Appwrite Console](/images/docs/auth/email-policies/dark/policies.avif)\n{% /only_dark %}\n{% only_light %}\n![Email policies card in the Appwrite Console](/images/docs/auth/email-policies/policies.avif)\n{% /only_light %}\n\nTo configure email policies manually:\n\n1. Open your project in the Appwrite Console.\n2. Navigate to **Auth** in the sidebar.\n3. Open the **Security** tab.\n4. In the **Email policies** card, toggle the policies you want to enable.\n5. Click **Update** to apply the changes.\n\n# Manage from the SDK {% #manage-sdk %}\n\nEach policy has its own method on the Project service. The body is always an `enabled` boolean.\n\n{% info title=\"Required scope\" %}\nThe API key used for these calls needs the `policies.write` scope.\n{% /info %}\n\n## Deny free emails {% #deny-free-emails %}\n\nWhen enabled, sign-ups and email updates using addresses from free email providers such as Gmail or Yahoo are rejected.\n\n{% multicode %}\n```server-nodejs\nimport { Client, Project } from 'node-appwrite';\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nconst project = new Project(client);\n\nconst result = await project.updateDenyFreeEmailPolicy({\n enabled: true\n});\n```\n```server-deno\nimport { Client, Project } from \"npm:node-appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nconst project = new Project(client);\n\nconst result = await project.updateDenyFreeEmailPolicy({\n enabled: true\n});\n```\n```server-php\nsetEndpoint('https://.cloud.appwrite.io/v1')\n ->setProject('')\n ->setKey('');\n\n$project = new Project($client);\n\n$result = $project->updateDenyFreeEmailPolicy(\n enabled: true\n);\n```\n```server-python\nfrom appwrite.client import Client\nfrom appwrite.services.project import Project\n\nclient = Client()\nclient.set_endpoint('https://.cloud.appwrite.io/v1')\nclient.set_project('')\nclient.set_key('')\n\nproject = Project(client)\n\nresult = project.update_deny_free_email_policy(\n enabled = True\n)\n```\n```server-ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://.cloud.appwrite.io/v1')\n .set_project('')\n .set_key('')\n\nproject = Project.new(client)\n\nresponse = project.update_deny_free_email_policy(\n enabled: true\n)\n```\n```server-dotnet\nusing Appwrite;\nusing Appwrite.Services;\n\nClient client = new Client()\n .SetEndPoint(\"https://.cloud.appwrite.io/v1\")\n .SetProject(\"\")\n .SetKey(\"\");\n\nProject project = new Project(client);\n\nvar result = await project.UpdateDenyFreeEmailPolicy(\n enabled: true\n);\n```\n```server-dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\n\nClient client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nProject project = Project(client);\n\nfinal result = await project.updateDenyFreeEmailPolicy(\n enabled: true,\n);\n```\n```server-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Project\n\nval client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\")\n\nval project = Project(client)\n\nval response = project.updateDenyFreeEmailPolicy(\n enabled = true\n)\n```\n```server-java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Project;\n\nClient client = new Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\");\n\nProject project = new Project(client);\n\nproject.updateDenyFreeEmailPolicy(\n true, // enabled\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n System.out.println(result);\n })\n);\n```\n```server-swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\")\n\nlet project = Project(client)\n\nlet result = try await project.updateDenyFreeEmailPolicy(\n enabled: true\n)\n```\n```server-go\npackage main\n\nimport (\n \"fmt\"\n \"github.com/appwrite/sdk-for-go/appwrite\"\n)\n\nfunc main() {\n client := appwrite.NewClient(\n appwrite.WithEndpoint(\"https://.cloud.appwrite.io/v1\"),\n appwrite.WithProject(\"\"),\n appwrite.WithKey(\"\"),\n )\n\n service := appwrite.NewProject(client)\n result, err := service.UpdateDenyFreeEmailPolicy(true)\n\n if err != nil {\n panic(err)\n }\n\n fmt.Println(result)\n}\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::project::Project;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box> {\n let client = Client::new()\n .set_endpoint(\"https://.cloud.appwrite.io/v1\")\n .set_project(\"\")\n .set_key(\"\");\n\n let project = Project::new(&client);\n\n let result = project.update_deny_free_email_policy(true).await?;\n\n println!(\"{:?}\", result);\n Ok(())\n}\n```\n```bash\nappwrite project update-deny-free-email-policy --enabled true\n```\n{% /multicode %}\n\n## Deny aliased emails {% #deny-aliased-emails %}\n\nWhen enabled, sign-ups and email updates using addresses with aliases, tags, subaddresses, or any other provider-specific variation are rejected.\n\n{% multicode %}\n```server-nodejs\nimport { Client, Project } from 'node-appwrite';\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nconst project = new Project(client);\n\nconst result = await project.updateDenyAliasedEmailPolicy({\n enabled: true\n});\n```\n```server-deno\nimport { Client, Project } from \"npm:node-appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nconst project = new Project(client);\n\nconst result = await project.updateDenyAliasedEmailPolicy({\n enabled: true\n});\n```\n```server-php\nsetEndpoint('https://.cloud.appwrite.io/v1')\n ->setProject('')\n ->setKey('');\n\n$project = new Project($client);\n\n$result = $project->updateDenyAliasedEmailPolicy(\n enabled: true\n);\n```\n```server-python\nfrom appwrite.client import Client\nfrom appwrite.services.project import Project\n\nclient = Client()\nclient.set_endpoint('https://.cloud.appwrite.io/v1')\nclient.set_project('')\nclient.set_key('')\n\nproject = Project(client)\n\nresult = project.update_deny_aliased_email_policy(\n enabled = True\n)\n```\n```server-ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://.cloud.appwrite.io/v1')\n .set_project('')\n .set_key('')\n\nproject = Project.new(client)\n\nresponse = project.update_deny_aliased_email_policy(\n enabled: true\n)\n```\n```server-dotnet\nusing Appwrite;\nusing Appwrite.Services;\n\nClient client = new Client()\n .SetEndPoint(\"https://.cloud.appwrite.io/v1\")\n .SetProject(\"\")\n .SetKey(\"\");\n\nProject project = new Project(client);\n\nvar result = await project.UpdateDenyAliasedEmailPolicy(\n enabled: true\n);\n```\n```server-dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\n\nClient client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nProject project = Project(client);\n\nfinal result = await project.updateDenyAliasedEmailPolicy(\n enabled: true,\n);\n```\n```server-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Project\n\nval client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\")\n\nval project = Project(client)\n\nval response = project.updateDenyAliasedEmailPolicy(\n enabled = true\n)\n```\n```server-java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Project;\n\nClient client = new Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\");\n\nProject project = new Project(client);\n\nproject.updateDenyAliasedEmailPolicy(\n true, // enabled\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n System.out.println(result);\n })\n);\n```\n```server-swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\")\n\nlet project = Project(client)\n\nlet result = try await project.updateDenyAliasedEmailPolicy(\n enabled: true\n)\n```\n```server-go\npackage main\n\nimport (\n \"fmt\"\n \"github.com/appwrite/sdk-for-go/appwrite\"\n)\n\nfunc main() {\n client := appwrite.NewClient(\n appwrite.WithEndpoint(\"https://.cloud.appwrite.io/v1\"),\n appwrite.WithProject(\"\"),\n appwrite.WithKey(\"\"),\n )\n\n service := appwrite.NewProject(client)\n result, err := service.UpdateDenyAliasedEmailPolicy(true)\n\n if err != nil {\n panic(err)\n }\n\n fmt.Println(result)\n}\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::project::Project;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box> {\n let client = Client::new()\n .set_endpoint(\"https://.cloud.appwrite.io/v1\")\n .set_project(\"\")\n .set_key(\"\");\n\n let project = Project::new(&client);\n\n let result = project.update_deny_aliased_email_policy(true).await?;\n\n println!(\"{:?}\", result);\n Ok(())\n}\n```\n```bash\nappwrite project update-deny-canonical-email-policy --enabled true\n```\n{% /multicode %}\n\n## Deny disposable emails {% #deny-disposable-emails %}\n\nWhen enabled, sign-ups and email updates using addresses from known temporary or disposable providers are rejected.\n\n{% multicode %}\n```server-nodejs\nimport { Client, Project } from 'node-appwrite';\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nconst project = new Project(client);\n\nconst result = await project.updateDenyDisposableEmailPolicy({\n enabled: true\n});\n```\n```server-deno\nimport { Client, Project } from \"npm:node-appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nconst project = new Project(client);\n\nconst result = await project.updateDenyDisposableEmailPolicy({\n enabled: true\n});\n```\n```server-php\nsetEndpoint('https://.cloud.appwrite.io/v1')\n ->setProject('')\n ->setKey('');\n\n$project = new Project($client);\n\n$result = $project->updateDenyDisposableEmailPolicy(\n enabled: true\n);\n```\n```server-python\nfrom appwrite.client import Client\nfrom appwrite.services.project import Project\n\nclient = Client()\nclient.set_endpoint('https://.cloud.appwrite.io/v1')\nclient.set_project('')\nclient.set_key('')\n\nproject = Project(client)\n\nresult = project.update_deny_disposable_email_policy(\n enabled = True\n)\n```\n```server-ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://.cloud.appwrite.io/v1')\n .set_project('')\n .set_key('')\n\nproject = Project.new(client)\n\nresponse = project.update_deny_disposable_email_policy(\n enabled: true\n)\n```\n```server-dotnet\nusing Appwrite;\nusing Appwrite.Services;\n\nClient client = new Client()\n .SetEndPoint(\"https://.cloud.appwrite.io/v1\")\n .SetProject(\"\")\n .SetKey(\"\");\n\nProject project = new Project(client);\n\nvar result = await project.UpdateDenyDisposableEmailPolicy(\n enabled: true\n);\n```\n```server-dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\n\nClient client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nProject project = Project(client);\n\nfinal result = await project.updateDenyDisposableEmailPolicy(\n enabled: true,\n);\n```\n```server-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Project\n\nval client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\")\n\nval project = Project(client)\n\nval response = project.updateDenyDisposableEmailPolicy(\n enabled = true\n)\n```\n```server-java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Project;\n\nClient client = new Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\");\n\nProject project = new Project(client);\n\nproject.updateDenyDisposableEmailPolicy(\n true, // enabled\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n System.out.println(result);\n })\n);\n```\n```server-swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\")\n\nlet project = Project(client)\n\nlet result = try await project.updateDenyDisposableEmailPolicy(\n enabled: true\n)\n```\n```server-go\npackage main\n\nimport (\n \"fmt\"\n \"github.com/appwrite/sdk-for-go/appwrite\"\n)\n\nfunc main() {\n client := appwrite.NewClient(\n appwrite.WithEndpoint(\"https://.cloud.appwrite.io/v1\"),\n appwrite.WithProject(\"\"),\n appwrite.WithKey(\"\"),\n )\n\n service := appwrite.NewProject(client)\n result, err := service.UpdateDenyDisposableEmailPolicy(true)\n\n if err != nil {\n panic(err)\n }\n\n fmt.Println(result)\n}\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::project::Project;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box> {\n let client = Client::new()\n .set_endpoint(\"https://.cloud.appwrite.io/v1\")\n .set_project(\"\")\n .set_key(\"\");\n\n let project = Project::new(&client);\n\n let result = project.update_deny_disposable_email_policy(true).await?;\n\n println!(\"{:?}\", result);\n Ok(())\n}\n```\n```bash\nappwrite project update-deny-disposable-email-policy --enabled true\n```\n{% /multicode %}\n\n# Benefits {% #benefits %}\n\nEnabling email policies on your project provides:\n\n- **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\n- **Lower spam and abuse**: Cut down on bot signups, trial abuse, and duplicate accounts created with subaddresses of the same inbox\n- **Stronger business rules**: Enforce work-email-only access on products that aren't meant for personal Gmail or Yahoo accounts\n- **Less downstream cleanup**: Reduce the bounced emails, unreachable users, and support tickets that come from invalid addresses making it into your database"}, {"path": "docs/products/auth/identities", "title": "Identities", "description": "Handle multiple authentication methods per user through a unified system that maintains consistent identity across providers.", "content": "Identities enable linking multiple authentication methods to a single user account. This allows users to access a unified account through various OAuth2 providers.\n\nAn 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:\n\n- Connecting multiple OAuth2 accounts to a single Appwrite account\n- Maintaining consistent access regardless of login method\n- Tracking which external providers are linked to an account\n\n# Use cases {% #use-cases %}\n\nIdentities are primarily used in the following scenarios:\n\n1. **OAuth2 authentication**: When users authenticate through any OAuth2 provider\n2. **Account management**: When users want to link or unlink external provider accounts\n3. **User profile consolidation**: When maintaining a single user profile across multiple authentication methods\n\n# Create new identities {% #create-new-identities %}\n\nTo create a new identity:\n\n1. The user must be logged into their Appwrite account\n2. Initiate the OAuth2 authentication flow for the desired provider\n3. The new identity will be automatically created and linked to the current account\n\nFor implementation details and code examples, refer to the [OAuth2 documentation](/docs/products/auth/oauth2).\n\n# Manage email addresses {% #manage-email-addresses %}\n\nEach 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.\nThis 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.\n\n# List and delete identities {% #list-and-delete-identities %}\n\nUsers and administrators can manage identities through various operations available in the Account API:\n\n- [List identities](/docs/references/cloud/client-web/account#listIdentities)\n- [Delete an identity](/docs/references/cloud/client-web/account#deleteIdentity)\n\nFor detailed API specifications and code examples, refer to the [Account API Reference](/docs/references/cloud/client-web/account).\n\n# Clean up identities {% #clean-up-identities %}\n\nWhen a user account is deleted:\n\n- Associated identities (and related targets) are removed via a background job\n- This deletion is asynchronous and may not be immediate due to queue processing times\n- In testing scenarios where instant deletion is required, manually remove identities (and targets) before deleting the user account\n\n# Best practices {% #best-practices %}\n\nA good user experience typically includes clear visibility of connected providers and straightforward identity management.\n\nVerify email addresses where possible and implement proper session management. Secure identity deletion can help prevent unauthorized access.\n\nTesting should ideally cover the cleanup of test identities and email conflict scenarios."}, {"path": "docs/products/auth/impersonation", "title": "User impersonation", "description": "Let trusted operators act as another user in Appwrite Auth for support, QA, and troubleshooting while keeping the flow controlled and auditable.", "content": "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.\n\nThis is especially useful when you need to:\n\n- Reproduce a bug that only appears for a specific user\n- Verify permissions and feature access from the user's point of view\n- Help customer support teams troubleshoot account issues\n- Review onboarding or upgrade flows exactly as an end user sees them\n\n# How it works {% #how-it-works %}\n\nImpersonation follows four steps:\n\n1. Enable the `impersonator` capability for a trusted operator.\n2. Have that operator sign in normally using any supported Appwrite Auth flow.\n3. Set exactly one impersonation target on the Appwrite client.\n4. Appwrite resolves the target user and evaluates the request as that user.\n\n{% info title=\"User authentication is required\" %}\nImpersonation 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.\n{% /info %}\n\nWhen 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.\n\n# Enable impersonation for an operator {% #enable-impersonation %}\n\nYou can grant a user the impersonator capability in two ways:\n\n- In the Appwrite Console under **Auth > Users**, open the user's profile and toggle on the impersonator capability\n- Via the Users API, by setting the `impersonator` field to `true` on the user\n\n{% multicode %}\n```server-nodejs\nconst sdk = require('node-appwrite');\n\nconst client = new sdk.Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n .setKey(''); // Your secret API key\n\nconst users = new sdk.Users(client);\n\nconst result = await users.updateImpersonator({\n userId: '',\n impersonator: true\n});\n```\n\n```server-deno\nimport * as sdk from \"npm:node-appwrite\";\n\nconst client = new sdk.Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n .setKey(''); // Your secret API key\n\nconst users = new sdk.Users(client);\n\nconst result = await users.updateImpersonator({\n userId: '',\n impersonator: true\n});\n```\n\n```server-python\nfrom appwrite.client import Client\nfrom appwrite.services.users import Users\n\nclient = Client()\nclient.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint\nclient.set_project('') # Your project ID\nclient.set_key('') # Your secret API key\n\nusers = Users(client)\n\nresult = users.update_impersonator(\n user_id='',\n impersonator=True\n)\n```\n\n```server-php\nuse Appwrite\\Client;\nuse Appwrite\\Services\\Users;\n\n$client = new Client();\n$client\n ->setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n ->setProject('') // Your project ID\n ->setKey(''); // Your secret API key\n\n$users = new Users($client);\n\n$result = $users->updateImpersonator(\n userId: '',\n impersonator: true\n);\n```\n\n```server-ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('') # Your project ID\n .set_key('') # Your secret API key\n\nusers = Users.new(client)\n\nresult = users.update_impersonator(\n user_id: '',\n impersonator: true\n)\n```\n\n```server-dotnet\nusing Appwrite;\nusing Appwrite.Services;\n\nvar client = new Client()\n .SetEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .SetProject(\"\") // Your project ID\n .SetKey(\"\"); // Your secret API key\n\nvar users = new Users(client);\n\nvar result = await users.UpdateImpersonator(\n userId: \"\",\n impersonator: true\n);\n```\n\n```server-dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n .setKey(''); // Your secret API key\n\nfinal users = Users(client);\n\nfinal result = await users.updateImpersonator(\n userId: '',\n impersonator: true\n);\n```\n\n```server-swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n .setKey(\"\") // Your secret API key\n\nlet users = Users(client)\n\nlet result = try await users.updateImpersonator(\n userId: \"\",\n impersonator: true\n)\n```\n\n```server-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Users\n\nval client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n .setKey(\"\") // Your secret API key\n\nval users = Users(client)\n\nval result = users.updateImpersonator(\n userId = \"\",\n impersonator = true\n)\n```\n\n```server-java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Users;\n\nClient client = new Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n .setKey(\"\"); // Your secret API key\n\nUsers users = new Users(client);\n\nusers.updateImpersonator(\n \"\",\n true,\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return null;\n }\n System.out.println(result);\n return null;\n })\n);\n```\n\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::users::Users;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box> {\n let client = Client::new()\n .set_endpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .set_project(\"\") // Your project ID\n .set_key(\"\"); // Your secret API key\n\n let users = Users::new(&client);\n\n let result = users.update_impersonator(\n \"\",\n true,\n ).await?;\n\n println!(\"{:?}\", result);\n Ok(())\n}\n```\n{% /multicode %}\n\nOnly 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.\n\n{% info title=\"Recommended setup\" %}\nCreate a dedicated internal operator account for each team member instead of sharing one support account.\n\nThis makes it easier to review internal audit activity and control access over time.\n{% /info %}\n\nWhen a user is marked as an impersonator, Appwrite also grants them the `users.read` scope.\n\nThis 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.\n\n# Initialize an impersonated client {% #initialize-an-impersonated-client %}\n\nAfter 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.\n\n{% info title=\"SDK support\" %}\nMake 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.\n{% /info %}\n\n# By user ID {% #impersonate-by-id %}\n\nImpersonating 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.\n\n{% multicode %}\n\n```client-web\nimport { Client, Account } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setImpersonateUserId('');\n\nconst account = new Account(client);\nconst user = await account.get();\n\nif (user.impersonatorUserId) {\n console.log(`Impersonated by ${user.impersonatorUserId}`);\n}\n```\n\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nClient client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setImpersonateUserId('');\n\nAccount account = Account(client);\nfinal user = await account.get();\nprint(user.name);\n```\n\n```client-apple\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setImpersonateUserId(\"\")\n\nlet account = Account(client)\nlet user = try await account.get()\nprint(user.name)\n```\n\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Account\n\nval client = Client(context)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setImpersonateUserId(\"\")\n\nval account = Account(client)\nval user = account.get()\nprintln(user.name)\n```\n\n{% /multicode %}\n\n# By email {% #impersonate-by-email %}\n\nImpersonating by email is useful in admin panels and support workflows where operators search for users by email address first.\n\n{% multicode %}\n\n```client-web\nimport { Client, Account } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setImpersonateUserEmail('user@example.com');\n\nconst account = new Account(client);\nconst user = await account.get();\nconsole.log(user.email);\n```\n\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nClient client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setImpersonateUserEmail('user@example.com');\n\nAccount account = Account(client);\nfinal user = await account.get();\nprint(user.email);\n```\n\n```client-apple\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setImpersonateUserEmail(\"user@example.com\")\n\nlet account = Account(client)\nlet user = try await account.get()\nprint(user.email)\n```\n\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Account\n\nval client = Client(context)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setImpersonateUserEmail(\"user@example.com\")\n\nval account = Account(client)\nval user = account.get()\nprintln(user.email)\n```\n\n{% /multicode %}\n\n# By phone {% #impersonate-by-phone %}\n\nImpersonating 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.\n\n{% multicode %}\n\n```client-web\nimport { Client, Account } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setImpersonateUserPhone('+12065550100');\n\nconst account = new Account(client);\nconst user = await account.get();\nconsole.log(user.phone);\n```\n\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nClient client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setImpersonateUserPhone('+12065550100');\n\nAccount account = Account(client);\nfinal user = await account.get();\nprint(user.phone);\n```\n\n```client-apple\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setImpersonateUserPhone(\"+12065550100\")\n\nlet account = Account(client)\nlet user = try await account.get()\nprint(user.phone)\n```\n\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Account\n\nval client = Client(context)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setImpersonateUserPhone(\"+12065550100\")\n\nval account = Account(client)\nval user = account.get()\nprintln(user.phone)\n```\n\n{% /multicode %}\n\n# Choosing the right identifier {% #choosing-the-right-identifier %}\n\nAll three options result in the same impersonated session. The difference is which identifier your operator workflow has available:\n\n- Use **user ID** when you already have the canonical Appwrite ID\n- Use **email** when support teams search by email address\n- Use **phone** when your app is centered around phone login or phone-based onboarding\n\nOnly 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.\n\n# Build safe support tooling {% #build-safe-support-tooling %}\n\nImpersonation is most useful when you wrap it in explicit operator UX:\n\n- Use the automatically granted `users.read` scope to build a user picker or searchable support view\n- Show a clear banner while impersonation is active\n- Display both the operator identity and the effective user identity\n- Require an explicit action to start impersonation\n- Let operators stop impersonating with one click\n- Limit impersonation features to internal tools and trusted roles\n\n# Security and visibility {% #security-and-visibility %}\n\nKeep impersonation limited to trusted operators and internal tools.\n\nImportant behavior to know:\n\n- Impersonation must start from a real user session, not an API key by itself.\n- Users with impersonation enabled are automatically granted the `users.read` scope.\n- The target user's permissions are used for the impersonated request.\n- 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.\n- Internal audit logs attribute the action to the original impersonator and include the impersonated target in internal audit payload data.\n\nIf you build an internal admin panel, use `impersonatorUserId` to make the impersonated state obvious at all times.\n\n# More resources {% #more-resources %}\n\n- [Manage users with the Users API](/docs/products/auth/users)\n- [REST API impersonation docs](/docs/apis/rest#impersonation-headers)\n- [Users API reference](/docs/references/cloud/server-nodejs/users)"}, {"path": "docs/products/auth/jwt", "title": "JWT login", "description": "Integrate Appwrite's authentication into your server-side applications. Explore server integrations, best practices, and security considerations for seamless authentication.", "content": "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.\n\nIf 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.\n\n# Proof of Identity {% #proof-of-identity %}\n\nBefore 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).\n\nWhen 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.\n\n# JWT Authentication {% #jwt %}\n\n[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.\n\nYou 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.\n\nYou can generate a JWT like this on a [Client SDK](/docs/sdks#client).\n\n{% multicode %}\n```client-web\nimport { Client, Account } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject(''); // Your project ID\n\nconst account = new Account(client);\n\nconst user = await account.createJWT();\n```\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject(''); // Your project ID\n\nfinal account = Account(client);\n\nfinal jwt = await account.createJWT();\n```\n```client-apple\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n\nlet account = Account(client)\n\nlet jwt = try await account.createJWT()\n```\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Account\n\nval client = Client(context)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n\nval account = Account(client)\n\nval jwt = account.createJWT()\n```\n```graphql\nmutation {\n accountCreateJWT {\n jwt\n }\n}\n```\n{% /multicode %}\n\nYour 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.\n\nUse JWTs tokens like this in a [Server SDK](/docs/sdks#server).\n{% multicode %}\n```js\nconst { Client } = require('node-appwrite');\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n .setJWT('eyJJ9.eyJ...886ca'); // Your secret JSON Web Token\n```\n\n```php\nuse Appwrite\\Client;\n\n$client = (new Client())\n ->setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n ->setProject('') // Your project ID\n ->setJWT('eyJJ9.eyJ...886ca'); // Your secret JSON Web Token\n```\n\n```python\nfrom appwrite.client import Client\n\nclient = Client()\n\n(client\n .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('') # Your project ID\n .set_jwt('eyJJ9.eyJ...886ca') # Your secret JSON Web Token\n)\n```\n\n```ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('') # Your project ID\n .set_jwt('eyJJ9.eyJ...886ca') # Your secret JSON Web Token\n```\n\n```deno\nimport { Client } from \"npm:node-appwrite\";\n\nlet client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n .setJWT('eyJJ9.eyJ...886ca'); // Your secret JSON Web Token\n```\n\n```dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n .setJWT('eyJJ9.eyJ...886ca'); // Your secret JSON Web Token\n```\n\n```kotlin\nimport io.appwrite.Client\n\nval client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n .setJWT(\"eyJJ9.eyJ...886ca\") // Your secret JSON Web Token\n```\n\n```swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n .setJWT(\"eyJJ9.eyJ...886ca\") // Your secret JSON Web Token\n```\n\n```csharp\nusing Appwrite;\n\nvar client = new Client()\n .SetEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .SetProject(\"\") // Your project ID\n .SetJWT(\"eyJJ9.eyJ...886ca\"); // Your secret JSON Web Token\n```\n{% /multicode %}\n\n\n# When should I use JWTs? {% #when-to-use %}\n\nJWT auth is useful when you need your backend app's Server SDK to be restricted by the same set of permissions.\n\nIf your backend app's Server SDK is using an [API key](/docs/advanced/security/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.\n\nIf your backend app's Server SDK is using a **JWT**, it will only fetch resources your user has permissions to access.\n\n# Example {% #when-to-use-example %}\n\nHere's an example table of birthdays with the following rows. Notice how they all have **different permissions**.\n\n| $id | name | birthday | $permissions |\n|-------------|-------|-----------|------------------------|\n| ac5fc866ad1e| Kevin | 2012-02-03| \"read(\\\"user:user-a\\\")\"|\n| bc7fc866ad1e| Laura | 1999-09-22| \"read(\\\"user:user-b\\\")\"|\n| cc2fc886ad1e| Bob | 1982-05-11| \"read(\\\"user:user-c\\\")\"|\n\nIf 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.\n\n{% multicode %}\n```js\nconst { Client } = require('node-appwrite');\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n .setJWT('eyJJ9.eyJ...886ca'); // Your secret JSON Web Token\n\nconst tablesDB = new sdk.TablesDB(client);\n\nconst rows = await tablesDB.listRows({\n databaseId: '642f358bf4084c662590',\n tableId: '642f3592aa5fc856ad1e'\n});\n```\n```php\nuse Appwrite\\Client;\n\n$client = (new Client())\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n .setJWT('eyJJ9.eyJ...886ca'); // Your secret JSON Web Tokens\n\n$tablesDB = new TablesDB($client);\n\n$rows = $tablesDB->listRows(\n databaseId: '642f358bf4084c662590',\n tableId: '642f3592aa5fc856ad1e'\n);\n```\n```python\nfrom appwrite.client import Client\n\nclient = Client()\n\n(client\n .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('') # Your project ID\n .set_jwt('eyJJ9.eyJ...886ca') # Your secret JSON Web Token\n)\n\ntablesDB = TablesDB(client)\n\nrows = tablesDB.list_rows(\n database_id='642f358bf4084c662590',\n table_id='642f3592aa5fc856ad1e'\n)\n```\n```ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('') # Your project ID\n .set_jwt('eyJJ9.eyJ...886ca') # Your secret JSON Web Token\n\ntablesDB = TablesDB.new(client)\n\nrows = tablesDB.list_rows(\n database_id: '642f358bf4084c662590',\n table_id: '642f3592aa5fc856ad1e'\n)\n```\n```deno\nimport { Client } from \"npm:node-appwrite\";\n\nlet client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n .setJWT('eyJJ9.eyJ...886ca'); // Your secret JSON Web Token\n\nlet tablesDB = new sdk.TablesDB(client);\n\nlet rows = await tablesDB.listRows({\n databaseId: '642f358bf4084c662590',\n tableId: '642f3592aa5fc856ad1e'\n});\n```\n```dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n .setJWT('eyJJ9.eyJ...886ca'); // Your secret JSON Web Token\n\nfinal tablesDB = TablesDB(client);\n\nfinal rows = await tablesDB.listRows(\n databaseId: '642f358bf4084c662590',\n tableId: '642f3592aa5fc856ad1e',\n);\n```\n```kotlin\nimport io.appwrite.Client\n\nval client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n .setJWT(\"eyJJ9.eyJ...886ca\") // Your secret JSON Web Token\n\nval tablesDB = TablesDB(client)\n\nval rows = tablesDB.listRows(\n databaseId = \"642f358bf4084c662590\",\n tableId = \"642f3592aa5fc856ad1e\",\n)\n```\n```swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n .setJWT(\"eyJJ9.eyJ...886ca\") // Your secret JSON Web Token\n \nlet tablesDB = TablesDB(client)\n\nlet rows = try await tablesDB.listRows(\n databaseId: \"642f358bf4084c662590\",\n tableId: \"642f3592aa5fc856ad1e\"\n)\n```\n```csharp\nusing Appwrite;\nusing Appwrite.Services;\nusing Appwrite.Models;\n\nvar client = new Client()\n .SetEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .SetProject(\"\") // Your project ID\n .SetJWT(\"eyJJ9.eyJ...886ca\"); // Your secret JSON Web Token\n\nvar tablesDB = new TablesDB(client);\n\nvar rows = await databases.listRows(\n databaseId: \"642f358bf4084c662590\",\n tableId: \"642f3592aa5fc856ad1e\");\n\n```\n\n{% /multicode %}\n\nOnly Kevin's birthday is returned and rows where `user-A` has no permissions to access are not returned.\n\n```js\n{\n \"total\": 1,\n \"rows\": [\n {\n \"name\": \"Kevin\",\n \"birthday\": \"2012-02-03T00:00:00.000+00:00\",\n \"$id\": \"ac5fc866ad1e\",\n \"$permissions\": [\n \"read(\\\"user:user-a\\\")\"\n ],\n \"$tableId\": \"642f3592aa5fc856ad1e\",\n \"$databaseId\": \"642f358bf4084c662590\",\n ...\n }\n ]\n}\n```\n\nIf 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.\n\n{% multicode %}\n```js\nconst { Client } = require('node-appwrite');\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n .setKey('98fd4...a2ad2'); // Your secret API key\n\nconst tablesDB = new sdk.TablesDB(client);\n\nconst rows = await tablesDB.listRows({\n databaseId: '642f358bf4084c662590',\n tableId: '642f3592aa5fc856ad1e'\n});\n```\n\n```php\nuse Appwrite\\Client;\n\n$client = (new Client())\n ->setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n ->setProject('') // Your project ID\n ->setKey('98fd4...a2ad2'); // Your secret API key\n\n$tablesDB = new TablesDB($client);\n\n$rows = $tablesDB->listRows(\n databaseId: '642f358bf4084c662590',\n tableId: '642f3592aa5fc856ad1e'\n);\n```\n\n```python\nfrom appwrite.client import Client\n\nclient = Client()\n(client\n .set_endpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .set_project('') // Your project ID\n .set_key('98fd4...a2ad2') // Your secret API key\n)\n\ntablesDB = TablesDB(client)\n\nrows = tablesDB.list_rows(\n database_id='642f358bf4084c662590',\n table_id='642f3592aa5fc856ad1e'\n)\n```\n```ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('') # Your project ID\n .set_key('98fd4...a2ad2') # Your secret API key\n\ntablesDB = TablesDB.new(client)\n\nrows = tablesDB.list_rows(\n database_id: '642f358bf4084c662590',\n table_id: '642f3592aa5fc856ad1e'\n)\n```\n```deno\nimport { Client } from \"npm:node-appwrite\";\n\nlet client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n .setKey('98fd4...a2ad2'); // Your secret API key\n\nlet tablesDB = new sdk.TablesDB(client);\n\nlet rows = await tablesDB.listRows({\n databaseId: '642f358bf4084c662590',\n tableId: '642f3592aa5fc856ad1e'\n});\n```\n```dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n .setKey('98fd4...a2ad2'); // Your secret API key\n\nfinal tablesDB = TablesDB(client);\n\nfinal rows = await tablesDB.listRows(\n databaseId: '642f358bf4084c662590',\n tableId: '642f3592aa5fc856ad1e',\n);\n```\n```kotlin\nimport io.appwrite.Client\n\nval client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n .setKey('98fd4...a2ad2'); // Your secret API key\n\nval tablesDB = TablesDB(client)\n\nval rows = tablesDB.listRows(\n databaseId = \"642f358bf4084c662590\",\n tableId = \"642f3592aa5fc856ad1e\",\n)\n```\n```swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n .setKey('98fd4...a2ad2'); // Your secret API key\n\nlet tablesDB = TablesDB(client)\n\nlet rows = try await tablesDB.listRows(\n databaseId: \"642f358bf4084c662590\",\n tableId: \"642f3592aa5fc856ad1e\"\n)\n```\n```csharp\nusing Appwrite;\nusing Appwrite.Services;\nusing Appwrite.Models;\n\nvar client = new Client()\n .SetEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .SetProject(\"\") // Your project ID\n .SetKey('98fd4...a2ad2'); // Your secret API key\n\nvar tablesDB = new TablesDB(client);\n\nvar rows = await databases.listRows(\n databaseId: \"642f358bf4084c662590\",\n tableId: \"642f3592aa5fc856ad1e\");\n\n```\n{% /multicode %}\n\nThis will return every row regardless of permissions, which could lead to privacy and security problems.\n\n```json\n{\n \"total\": 3,\n \"rows\": [\n {\n \"name\": \"Kevin\",\n \"birthday\": \"2012-02-03T00:00:00.000+00:00\",\n \"$id\": \"ac5fc866ad1e\",\n \"$permissions\": [\n \"read(\\\"user:user-a\\\")\"\n ],\n \"$tableId\": \"642f3592aa5fc856ad1e\",\n \"$databaseId\": \"642f358bf4084c662590\",\n ...\n },\n {\n \"name\": \"Laura\",\n \"birthday\": \"1999-09-22T11:21:23.334+00:00\",\n \"$id\": \"bc7fc866ad1e\",\n \"$permissions\": [\n \"read(\\\"user:user-b\\\")\"\n ],\n \"$tableId\": \"642f3592aa5fc856ad1e\",\n \"$databaseId\": \"642f358bf4084c662590\",\n ...\n },\n {\n \"name\": \"Bob\",\n \"birthday\": \"1982-05-11T12:31:39.381+00:00\",\n \"$id\": \"cc2fc886ad1e\",\n \"$permissions\": [\n \"read(\\\"user:user-c\\\")\"\n ],\n \"$tableId\": \"642f3592aa5fc856ad1e\",\n \"$databaseId\": \"642f358bf4084c662590\",\n ...\n }\n ]\n}\n```\n\nIf 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."}, {"path": "docs/products/auth/labels", "title": "Labels", "description": "Organize your users and grant custom permissions for subscriptions or VIP users with labels.", "content": "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.\n\n{% multicode %}\n```server-nodejs\nconst sdk = require('node-appwrite');\n\nconst client = new sdk.Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n .setKey('98fd4...a2ad2'); // Your secret API key\n\nconst users = new sdk.Users(client);\n\nconst promise = users.updateLabels(\n '',\n [ 'subscriber' ]\n);\n\npromise.then(function (response) {\n console.log(response); // Success\n}, function (error) {\n console.log(error); // Failure\n});\n```\n\n```php\nuse Appwrite\\Client;\nuse Appwrite\\Services\\Users;\nuse Appwrite\\Role;\n\n$client = new Client();\n\n$client\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n .setKey('98fd4...a2ad2'); // Your secret API key\n\n$users = new Users($client);\n\n$result = $users->updateLabels(\n '',\n [ 'subscriber' ]\n);\n```\n\n```python\nfrom appwrite.client import Client\nfrom appwrite.services.users import Users\nfrom appwrite.role import Role\n\nclient = Client()\n\n(client\n .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('') # Your project ID\n .set_key('98fd4...a2ad2') # Your secret API key\n)\n \nusers = Users(client)\n\nresult = users.update_labels(\n '',\n [ 'subscriber' ]\n);\n```\n```ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('') # Your project ID\n .set_key('98fd4...a2ad2') # Your secret API key\n\nusers = Users.new(client)\n\nresponse = users.update_labels(\n user_id: '',\n labels: [ 'subscriber' ]\n);\n```\n\n```deno\nimport * as sdk from \"npm:node-appwrite\";\n\nlet client = new sdk.Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n .setKey('98fd4...a2ad2'); // Your secret API key\n\nlet users = new sdk.Users(client);\n\nconst promise = users.updateLabels(\n '',\n [ 'subscriber' ]\n);\n\npromise.then(function (response) {\n console.log(response); // Success\n}, function (error) {\n console.log(error); // Failure\n});\n```\n```dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n .setKey('98fd4...a2ad2'); // Your secret API key\n\nfinal users = Users(client);\n\nfinal result = await users.updateLabels(\n userId: '',\n labels: [ 'subscriber' ],\n);\n```\n```kotlin\nimport io.appwrite.Client\nimport io.appwrite.Role\nimport io.appwrite.services.Users\n\nval client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n .setKey(\"98fd4...a2ad2\") // Your secret API key\n\nval users = Users(client)\n\nval response = users.updateLabels(\n userId = \"\",\n labels = [ 'subscriber' ]\n);\n```\n\n```swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n .setKey(\"98fd4...a2ad2\") // Your secret API key\n\nlet users = Users(client)\n\nlet response = try await users.updateLabels(\n userId: \"\",\n labels: [ 'subscriber' ]\n);\n```\n\n```csharp\nusing Appwrite;\n\nvar client = new Client()\n .SetEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .SetProject(\"\") // Your project ID\n .SetKey(\"98fd4...a2ad2\"); // Your secret API key\n\nvar users = new Users(client);\n\nvar response = await users.UpdateLabels(\n userId: \"\",\n labels: [ 'subscriber' ]\n);\n```\n\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::users::Users;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box> {\n let client = Client::new()\n .set_endpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .set_project(\"\") // Your project ID\n .set_key(\"98fd4...a2ad2\"); // Your secret API key\n\n let users = Users::new(&client);\n\n let response = users.update_labels(\n \"\",\n vec![\"subscriber\"],\n ).await?;\n\n println!(\"{:?}\", response);\n Ok(())\n}\n```\n{% /multicode %}\n\nThis would correspond with the permissions below.\n\n| Description | Code Snippet |\n| ------------------------------------------- | ------------------------------------------- |\n| Read | `Permissions.read(Role.label('subscriber'))`|\n| Update | `Permissions.update(Role.label('subscriber'))` |\n| Delete | `Permissions.delete(Role.label('subscriber'))` |\n| Create | `Permissions.create(Role.label('subscriber'))` |\n\n{% arrow_link href=\"/docs/advanced/security/permissions\" %}\nLearn more about permissions\n{% /arrow_link %}"}, {"path": "docs/products/auth/magic-url", "title": "Magic URL login", "description": "Add magic URL to your authentication in Appwrite. Explore the convenience of passwordless login and email-based authentication using magic links.", "content": "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.\n\n# Send email {% #init %}\n\nInitialize 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.\n\n{% multicode %}\n```client-web\nimport { Client, Account, ID } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject(''); // Your project ID\n\nconst account = new Account(client);\n\nconst token = await account.createMagicURLToken(\n ID.unique(),\n 'email@example.com',\n 'https://example.com/verify'\n);\n```\n\n```graphql\nmutation {\n accountCreateMagicURLToken(\n userId: \"ID.unique()\",\n email: \"email@example.com\",\n url: \"https://example.com/verify\"\n ) {\n _id\n _createdAt\n userId\n secret\n expire\n }\n}\n```\n\n{% /multicode %}\n\nThe `url` parameter specifies where users will be redirected after clicking the magic link.\nThe secret and userId will be automatically appended as query parameters to this URL.\n\n# Login {% #login %}\n\nAfter 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.\n\n{% multicode %}\n\n```client-web\nimport { Client, Account } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject(''); // Your project ID\n\nconst account = new Account(client);\n\nconst urlParams = new URLSearchParams(window.location.search);\nconst secret = urlParams.get('secret');\nconst userId = urlParams.get('userId');\n\nconst user = await account.createSession({\n userId,\n secret\n});\n```\n```graphql\nmutation {\n accountCreateSession(\n userId: \"unique()\",\n secret: \"\"\n ) {\n _id\n _createdAt\n userId\n expire\n provider\n }\n}\n```\n{% /multicode %}"}, {"path": "docs/products/auth/message-templates", "title": "Message templates", "description": "Communicate using your brand and voice by customizing email and SMS message templates, localized to your user's language.", "content": "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.\n\nEach 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.\n\n# Custom SMTP server {% #smtp %}\n\nAppwrite 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.\n\nThere 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**.\n\n1. Navigate to your project's **Settings**.\n1. Navigate to the **SMTP** tab.\n1. Under **SMTP server**, toggle **Custom SMTP server**.\n1. Input **Sender name**, **Sender email**, **Server host**, **Server port**, **Username**, and **Password** from your provider.\n1. Click **Update**.\n\n# Customize templates {% #customize %}\n\nYou can customize email templates for each of your projects in the Appwrite Console.\n\n{% info title=\"Custom SMTP server required\" %}\nThe built-in email service does not support custom email templates to prevent malicious templates.\nConfigure a [custom SMTP server](#smtp) to enable custom email templates.\n{% /info %}\n\n1. In your project, navigate to the **Auth** service.\n1. Under the **Auth** service, navigate to the **Templates** tab.\n1. Expand the email template you want to edit.\n1. Select the **Template language**. You can have a different template for each language your app supports.\n1. Update the email template fields and click **Update** to save your changes.\n\n# Email templates {% #email-templates %}\n\nYou can customize the email templates for account verification, magic-url authentication, password resets, and user invites.\n\n## Email template components {% #email-template-components %}\n\nEach email template has the following components that you can customize.\n\n| Component | Description |\n| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| Sender name | Readers will see this as a display name of the sender. |\n| 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. |\n| 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. |\n| Subject | The title of the email. |\n| Message | The body of the email in HTML format. You can find the variables available in the [Email Template Syntax](#email-template-syntax) section. |\n\n## Email template syntax {% #email-template-syntax %}\n\nVariables 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.\n\n| Variable | Description |\n| ------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |\n| `{{project}}` | The project name. |\n| `{{team}}` | The project team's name. |\n| `{{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. |\n| `{{redirect}}` | The URL for the user to complete the email template's action. |\n\n## Email template examples {% #email-template-examples %}\n\nHere's an example of using these variables in a template.\n\n```html\n\n\n\n\n \n\n\n\n\n
\n \n \n \n \n
\n

{{subject}}

\n
\n\n \n \n \n \n
\n

Hello

\n\n

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

\n\n {{redirect}}\n\n


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

\n
\n\n

Thanks\n
\n {{project}} team

\n
\n
\n\n\n\n\n```\n\n# Localization {% #localization %}\n\nEach template can have multiple supported locales, displayed in different format and language. This can be configured under the **Template language** selector of each template.\n\nYou 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).\n\nFor example, you can send an email verification in French.\n\n{% multicode %}\n```client-web\nimport { Client, Account } from \"appwrite\";\n\nconst client = new Client();\n\nconst account = new Account(client);\n\nclient\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n .setLocale('fr') // Your locale\n;\n\nconst promise = account.createVerification({\n url: 'https://example.com'\n});\n\npromise.then(function (response) {\n console.log(response); // Success\n}, function (error) {\n console.log(error); // Failure\n});\n```\n\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nvoid main() { // Init SDK\n Client client = Client();\n Account account = Account(client);\n\n client\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n .setLocale('fr') // Your locale\n ;\n Future result = account.createVerification('https://example.com');\n\n result\n .then((response) {\n print(response);\n }).catchError((error) {\n print(error.response);\n });\n}\n```\n\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Account\n\nval client = Client(context)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n .setLocale('fr') // Your locale\n\nval account = Account(client)\n\nval response = account.createVerification('https://example.com')\n```\n\n```client-apple\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n .setLocale('fr') // Your locale\n\nlet account = Account(client)\n\nlet token = try await account.createVerification('https://example.com')\n```\n{% /multicode %}"}, {"path": "docs/products/auth/mfa", "title": "Multi-factor authentication", "description": "Add multiple layers of authentication to your applications powered by Appwrite Authentication.", "content": "Multi-factor authentication (MFA) greatly increases the security of your apps by adding additional layers of protection.\nWhen MFA is enabled, a malicious actor needs to compromise multiple authentication factors to gain unauthorized access.\nAppwrite Authentication lets you easily implement MFA in your apps, letting you build more securely and quickly.\n\n{% info title=\"Looking for MFA on your Console account?\" %}\nThis page covers MFA for your app's end-users.\nIf you are looking for MFA on your Appwrite Console account, please refer to the [Console MFA page](/docs/advanced/security/mfa).\n{% /info %}\n\nAppwrite currently allows two factors of authentication. More factors of authentication will be available soon.\n\nHere are the steps to implement MFA in your application.\n\n{% section #display-recover-code step=1 title=\"Display recovery codes\" %}\n\nInitialize your Appwrite SDK's `Client`, `Account`, and `Avatars`.\nYou'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.\n\n{% multicode %}\n```client-web\nimport { Client, Account, Avatars } from \"appwrite\";\n\nconst client = new Client();\n\nconst account = new Account(client);\nconst avatars = new Avatars(client);\n\nclient\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n;\n```\n\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nvoid main() { // Init SDK\n Client client = Client();\n Account account = Account(client);\n Avatars avatars = Avatars(client);\n\n client\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n ;\n}\n```\n\n```client-apple\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n\nlet account = Account(client)\nlet avatars = Avatars(client)\n```\n\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Account\n\nval client = Client(context)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n\nval account = Account(client)\nval avatars = Avatars(client)\n```\n{% /multicode %}\n\nBefore enabling MFA, you should display recovery codes to the user.\nThe codes are single use passwords the user can use to access their account if they lose access to their MFA email,\nphone, or authenticator app. These codes can **only be generated once**, warn the users to save them.\n\nThe code will look like this, display them to the user and remind them to save the codes in a secure place.\n\n```json\n{\n \"recoveryCodes\": [\n \"b654562828\",\n \"a97c13d8c0\",\n \"311580b5f3\",\n \"c4262b3f88\",\n \"7f6761afb4\",\n \"55a09989be\",\n ]\n}\n```\nThese codes can be used to complete the [Complete challenge](#complete-challenge) step if the user loses access to their MFA factors.\nGenerate the recovery codes by calling `account.createMfaRecoveryCodes()`.\n\n{% multicode %}\n```client-web\nconst response = await account.createMfaRecoveryCodes();\nconsole.log(response.recoveryCodes);\n```\n\n```client-flutter\nFuture result = account.createMfaRecoveryCodes();\n\nresult.then((response) {\n print(response.recoveryCodes);\n}).catchError((error) {\n print(error.response);\n});\n```\n\n```client-apple\nlet response = try await account.createMfaRecoveryCodes()\nprint(response.recoveryCodes)\n```\n\n```client-android-kotlin\nval response = account.createMfaRecoveryCodes()\nprintln(response.recoveryCodes)\n```\n\n```client-android-java\naccount.createMfaRecoveryCodes(new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n Log.d(\"Appwrite\", result.recoveryCodes.toString());\n}));\n```\n{% /multicode %}\n{% /section %}\n\n{% section #verify-factors step=2 title=\"Verify MFA factors\" %}\nAny verified email, phone number, or TOTP authenticator app can be used as a factor for MFA.\nBefore they can be used as a factor, they need to be verified.\n\n{% tabs %}\n{% tabsitem #email title=\"Email\" %}\nFirst, set your user's email if they haven't already.\n{% multicode %}\n```client-web\nconst response = await account.updateEmail({\n email: 'email@example.com',\n password: 'password'\n});\n```\n```client-flutter\nFuture result = account.updateEmail(\n email: 'email@example.com',\n password: 'password',\n);\n\nresult.then((response) {\n print(response);\n}).catchError((error) {\n print(error.response);\n});\n```\n```client-apple\nlet response = try await account.updateEmail(\n email: \"email@example.com\",\n password: \"password\"\n)\n```\n```client-android-kotlin\nval response = account.updateEmail(\n email = \"email@example.com\",\n password = \"password\"\n)\n```\n```client-android-java\naccount.updateEmail(\n \"email@example.com\", // email\n \"password\", // password\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n Log.d(\"Appwrite\", result.toString());\n })\n);\n```\n{% /multicode %}\n\nThen, initiate verification for the email by calling `account.createEmailVerification()`.\nCalling `createEmailVerification` will send a verification email to the user's email address\nwith a link with the query parameter `userId` and `secret`.\n\n{% multicode %}\n```client-web\nconst res = await account.createVerification({\n url: 'https://example.com/verify-email'\n});\n```\n```client-flutter\nFuture result = account.createVerification(\n url: 'https://example.com/verify-email',\n);\n\nresult.then((response) {\n print(response);\n}).catchError((error) {\n print(error.response);\n});\n\n```\n```client-apple\nlet response = try await account.createVerification(\n url: \"https://example.com/verify-email\"\n)\n```\n```client-android-kotlin\nval response = account.createVerification(\n url = \"https://example.com/verify-email\"\n)\n```\n```client-android-java\naccount.createVerification(\n \"https://example.com/verify-email\", // url\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n Log.d(\"Appwrite\", result.toString());\n })\n);\n```\n{% /multicode %}\n\nAfter the user clicks the link in the email, they will be redirected to your site with the query parameters `userId` and `secret`.\nIf you're on a mobile platform, you will need to create the appropriate deep link to handle the verification.\n\nFinally, verify the email by calling `account.updateVerification()` with `userId` and `secret`.\n\n{% multicode %}\n```client-web\nconst response = await account.updateVerification({\n userId: '',\n secret: ''\n});\n```\n```client-flutter\nFuture result = account.updateVerification(\n userId: '',\n secret: '',\n);\n\nresult.then((response) {\n print(response);\n}).catchError((error) {\n print(error.response);\n});\n```\n```client-apple\nlet response = try await account.updateVerification(\n userId: \"\",\n secret: \"\"\n)\n```\n```client-android-kotlin\nval response = account.updateVerification(\n userId = \"\",\n secret = \"\"\n)\n```\n```client-android-java\naccount.updateVerification(\n \"\", // userId\n \"\", // secret\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n Log.d(\"Appwrite\", result.toString());\n })\n);\n```\n{% /multicode %}\n\n{% /tabsitem %}\n\n{% tabsitem #phone title=\"Phone\" %}\nFirst, set your user's phone number if they haven't already.\n\n{% multicode %}\n```client-web\nconst response = await account.updatePhone({\n phone: '+12065550100',\n password: 'password'\n});\n```\n```client-flutter\nFuture result = account.updatePhone(\n phone: '+12065550100',\n password: 'password',\n);\n\nresult.then((response) {\n print(response);\n}).catchError((error) {\n print(error.response);\n});\n```\n```client-apple\nlet response = try await account.updatePhone(\n phone: \"+12065550100\",\n password: \"password\"\n)\n```\n```client-android-kotlin\nval response = account.updatePhone(\n phone = \"+12065550100\",\n password = \"password\"\n)\n```\n```client-android-java\naccount.updatePhone(\n \"+12065550100\", // phone\n \"password\", // password\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n Log.d(\"Appwrite\", result.toString());\n })\n);\n```\n{% /multicode %}\n\nThen, initiate verification for the phone number by calling `account.createPhoneVerification()`.\n{% multicode %}\n```client-web\nconst response = await account.createPhoneVerification();\n```\n```client-flutter\nFuture result = account.createPhoneVerification();\n\nresult\n .then((response) {\n print(response);\n }).catchError((error) {\n print(error.response);\n});\n```\n```client-apple\nlet response = try await account.createPhoneVerification()\n```\n```client-android-kotlin\nval response = account.createPhoneVerification()\n```\n```client-android-java\naccount.createPhoneVerification(new CoroutineCallback<>((result, error) -> {\n if (error != null)\n error.printStackTrace();\n return;\n }\n\n Log.d(\"Appwrite\", result.toString());\n}));\n```\n{% /multicode %}\n\nAfter the user receives the verification code, they can verify their phone number by calling `account.updatePhoneVerification()`.\n{% multicode %}\n```client-web\nconst response = await account.updatePhoneVerification({\n userId: '',\n secret: ''\n});\n```\n```client-flutter\nFuture result = account.updatePhoneVerification(\n userId: '',\n secret: '',\n);\n\nresult.then((response) {\n print(response);\n }).catchError((error) {\n print(error.response);\n });\n```\n```client-apple\nlet response = try await account.updatePhoneVerification(\n userId: \"\",\n secret: \"\"\n)\n```\n```client-android-kotlin\nval response = account.updatePhoneVerification(\n userId = \"\",\n secret = \"\"\n)\n```\n```client-android-java\naccount.updatePhoneVerification(\n \"\", // userId\n \"\", // secret\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n Log.d(\"Appwrite\", result.toString());\n })\n);\n```\n{% /multicode %}\n\n{% /tabsitem %}\n\n{% tabsitem #authenticator title=\"Authenticator\" %}\nFirst, add a TOTP authenticator to the user's account by calling `account.addAuthenticator()`.\n\n{% multicode %}\n```client-web\nconst { secret, uri } = await account.createMfaAuthenticator({\n type: 'totp'\n});\n```\n```client-flutter\nFuture result = account.createMfaAuthenticator(\n type: 'totp',\n);\n\nresult.then((response) {\n print(response.secret);\n print(response.uri);\n}).catchError((error) {\n print(error.response);\n});\n```\n```client-apple\nlet response = try await account.createMfaAuthenticator(\n type: \"totp\"\n)\nprint(response.secret)\nprint(response.uri)\n```\n```client-android-kotlin\nval response = account.createMfaAuthenticator(\n type = \"totp\"\n)\nprintln(response.secret)\nprintln(response.uri)\n```\n```client-android-java\naccount.createMfaAuthenticator(\n \"totp\", // type\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n Log.d(\"Appwrite\", result.toString());\n })\n);\n```\n{% /multicode %}\n\nThis will create a secret and a URI.\nThe URI is a URL that can be used to generate a QR code for the user to scan with their TOTP authenticator app.\n\nYou can generate a QR code for the user to scan by calling `avatars.getQR()`.\n\n{% multicode %}\n```client-web\nconst result = await avatars.getQR({\n text: uri,\n size: 800, // optional\n margin: 0, // optional\n download: false // optional\n});\n\nconsole.log(result); // Resource URL\n```\n```client-flutter\n// download QR code image\nFuture result = avatars.getQR(\n text: authenticatorUrl,\n size: 800, // optional\n margin: 0, // optional\n download: false, // optional\n).then((bytes) {\n final file = File('path_to_file/filename.ext');\n file.writeAsBytesSync(bytes);\n}).catchError((error) {\n print(error.response);\n});\n\n// display QR code image\nFutureBuilder(\n future: avatars.getQR(\n text: authenticatorUrl,\n size: 800, // optional\n margin: 0, // optional\n download: false, // optional\n ), // works for both public file and private file, for private files you need to be logged in\n builder: (context, snapshot) {\n return snapshot.hasData && snapshot.data != null\n ? Image.memory(\n snapshot.data,\n )\n : CircularProgressIndicator();\n },\n);\n```\n```client-apple\nlet byteBuffer = try await avatars.getQR(\n text: authenticatorUrl,\n size: 800, // optional\n margin: 0, // optional\n download: xfalse // optional\n)\n```\n```client-android-kotlin\nval result = avatars.getQR(\n text = authenticatorUrl,\n size = 800, // optional\n margin = 0, // optional\n download = false // optional\n)\n```\n```client-android-java\navatars.getQR(\n authenticatorUrl, // text\n 800, // size (optional)\n 0, // margin (optional)\n false, // download (optional)\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n Log.d(\"Appwrite\", result.toString());\n })\n);\n```\n{% /multicode %}\n\nIf the user is unable to scan QR codes, you can display the `secret` to the user.\n\nFinally prompt the user to enter a TOTP from their authenticator app, then\nverify the authenticator by calling `account.verifyMfaAuthenticator()`.\n\n{% multicode %}\n```client-web\nconst promise = account.updateMfaAuthenticator({\n type: 'totp',\n otp: ''\n});\n```\n```client-flutter\n Future result = account.updateMfaAuthenticator(\n type: 'totp',\n otp: '',\n );\n\n result.then((response) {\n print(response);\n }).catchError((error) {\n print(error.response);\n });\n```\n```client-apple\nlet response = try await account.updateMfaAuthenticator(\n type: \"totp\",\n otp: \"\"\n)\n```\n```client-android-kotlin\nval response = account.updateMfaAuthenticator(\n type = \"totp\",\n otp = \"\"\n)\n```\n```client-android-java\naccount.updateMfaAuthenticator(\n \"totp\", // type\n \"\", // otp\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n Log.d(\"Appwrite\", result.toString());\n })\n);\n```\n{% /multicode %}\n\n{% /tabsitem %}\n{% /tabs %}\n\n{% /section %}\n\n{% section #enable-mfa step=3 title=\"Enable MFA on an account\" %}\nYou can enable MFA on your account by calling `account.updateMFA()`.\nYou will need to have added more than 1 factors of authentication to an account before\nthe MFA is enforced.\n\n{% multicode %}\n```client-web\nconst result = await account.updateMFA({\n enabled: true\n});\n```\n\n```client-flutter\nFuture result = account.updateMFA(\n mfa: true,\n);\n\nresult.then((response) {\n print(response);\n}).catchError((error) {\n print(error.response);\n});\n```\n\n```client-apple\nlet response = try await account.updateMFA(\n mfa: xtrue\n)\n```\n\n```client-android-kotlin\nval response = account.updateMFA(\n mfa = true\n)\n```\n{% /multicode %}\n{% /section %}\n\n{% section #init-login step=4 title=\"Initialize login\" %}\nBegin your login flow with the default authentication method used by your app, for example, email password.\n\n{% multicode %}\n```client-web\nconst session = await account.createEmailPasswordSession({\n email: 'email@example.com',\n password: 'password'\n});\n```\n\n```client-flutter\nFuture session = account.createEmailPasswordSession(\nemail: 'email@example.com',\npassword: 'password',\n);\n\nresult.then((response) {\n print(response);\n}).catchError((error) {\n print(error.response);\n});\n```\n\n```client-apple\nlet response = try await account.createEmailPasswordSession(\n email: \"email@example.com\",\n password: \"password\"\n)\n```\n\n```client-android-kotlin\nval session = account.createEmailPasswordSession(\n email = \"email@example.com\",\n password = \"password\"\n)\n```\n{% /multicode %}\n{% /section %}\n\n{% section #check-for-mfa step=5 title=\"Check for multi-factor\" %}\nUpon successful login in the first authentication step, check the status of the login by calling `account.get()`.\nIf more than one factors are required, you will receive the error `user_more_factors_required`.\nRedirect the user in your app to perform the MFA challenge.\n\n{% multicode %}\n```client-web\ntry {\n const response = await account.get();\n console.log(response);\n} catch (error) {\n console.log(error);\n if (error.type === `user_more_factors_required`){\n // redirect to perform MFA\n }\n else {\n // handle other errors\n }\n}\n```\n\n```client-flutter\nconst promise = account.get();\n\npromise.then(function (response) {\n console.log(response); // Success\n}, function (error) {\n console.log(error); // Failure\n if (error.type === \"user_more_factors_required\"){\n // redirect to perform MFA\n }\n else {\n // handle other errors\n }\n});\n```\n\n```client-flutter\nFuture result = account.get();\n\nresult.then((response) {\n print(response);\n}).catchError((error) {\n print(error.response);\n if (error.type == 'user_more_factors_required') {\n // redirect to perform MFA\n } else {\n // handle other errors\n }\n});\n```\n\n```client-apple\ndo {\n let response = try await account.get()\n} catch let error as AppwriteException {\n print(error.message)\n if error.type == \"user_more_factors_required\" {\n // redirect to perform MFA\n } else {\n // handle other errors\n }\n}\n```\n\n```client-android-kotlin\ntry {\n val response = account.get()\n println(response)\n} catch (error: AppwriteException) {\n println(error.message)\n if (error.type == \"user_more_factors_required\") {\n // redirect to perform MFA\n } else {\n // handle other errors\n }\n}\n```\n{% /multicode %}\n{% /section %}\n\n{% section #list-provider step=6 title=\"List factors\" %}\nYou can check which factors are enabled for an account using `account.listMfaFactors()`.\nThe returned object will be formatted like this.\n\n```client-web\n{\n totp: true, // time-based one-time password\n email: false, // email\n phone: true // phone\n}\n```\n\n{% multicode %}\n```client-web\nconst factors = await account.listMfaFactors();\n// redirect based on factors returned.\n```\n\n```client-flutter\nFuture result = account.listMfaFactors();\n\nresult.then((response) {\n print(response);\n // redirect based on factors returned.\n}).catchError((error) {\n print(error.response);\n});\n```\n\n```client-apple\nlet response = try await account.listMfaFactors()\n// redirect based on factors returned.\n```\n\n```client-android-kotlin\nval response = account.listMfaFactors()\n// redirect based on factors returned.\n```\n{% /multicode %}\n{% /section %}\n\n{% section #create-challenge step=7 title=\"Create challenge\" %}\nBased on the factors available, initialize an additional auth step.\nCalling these methods will send a challenge to the user.\nYou will need to save the challenge ID to complete the challenge in a later step.\n\n{% tabs %}\n{% tabsitem #email title=\"Email\" %}\nAppwrite will use a verified email on the user's account to send the challenge code via email.\nNote that this is only valid as a second factor if the user did not initialize their login with email OTP.\n\n{% multicode %}\n```client-web\nconst challenge = await account.createMfaChallenge({\n factor: 'email'\n});\n\n// Save the challenge ID to complete the challenge later\nconst challengeId = challenge.$id;\n```\n\n```client-flutter\nFuture result = account.createMfaChallenge(\nfactor: 'email',\n);\n\nresult.then((response) {\n print(response);\n // Save the challenge ID to complete the challenge later\n var challengeId = response.$id;\n}).catchError((error) {\n print(error.response);\n});\n```\n\n```client-apple\nlet response = try await account.createMfaChallenge(\n factor: \"email\"\n)\n// Save the challenge ID to complete the challenge later\nlet challengeId = response.id\n```\n\n```client-android-kotlin\nval response = account.createMfaChallenge(\n factor = \"email\"\n)\n// Save the challenge ID to complete the challenge later\nval challengeId = response.id\n```\n{% /multicode %}\n{% /tabsitem %}\n\n{% tabsitem #phone title=\"Phone\" %}\nAppwrite will use a verified phone number on the user's account to send the challenge code via SMS.\nYou cannot use this method if the user initialized their login with phone OTP.\n\n{% multicode %}\n```client-web\nconst challenge = await account.createMfaChallenge({\n factor: 'phone'\n});\n\n// Save the challenge ID to complete the challenge later\nconst challengeId = challenge.$id;\n```\n\n```client-flutter\nFuture result = account.createMfaChallenge(\nfactor: 'phone',\n);\n\nresult.then((response) {\n print(response);\n // Save the challenge ID to complete the challenge later\n var challengeId = response.$id;\n}).catchError((error) {\n print(error.response);\n});\n```\n\n```client-apple\nlet response = try await account.createMfaChallenge(\n factor: \"phone\"\n)\n// Save the challenge ID to complete the challenge later\nlet challengeId = response.id\n```\n\n```client-android-kotlin\nval response = account.createMfaChallenge(\n factor = \"phone\"\n)\n// Save the challenge ID to complete the challenge later\nval challengeId = response.id\n```\n{% /multicode %}\n{% /tabsitem %}\n\n{% tabsitem #totp title=\"TOTP\" %}\n\nInitiate a challenge for users to complete using an authenticator app.\n\n{% multicode %}\n```client-web\nconst challenge = await account.createMfaChallenge({\n factor: AuthenticationFactor.Totp\n});\n\n// Save the challenge ID to complete the challenge later\nconst challengeId = challenge.$id;\n```\n\n```client-flutter\nFuture result = account.createMfaChallenge(\nfactor: 'totp',\n);\n\nresult.then((response) {\n print(response);\n // Save the challenge ID to complete the challenge later\n var challengeId = response.$id;\n}).catchError((error) {\n print(error.response);\n});\n```\n\n```client-apple\nlet response = try await account.createMfaChallenge(\n factor: \"totp\"\n)\n// Save the challenge ID to complete the challenge later\nlet challengeId = response.id\n```\n\n```client-android-kotlin\nval response = account.createMfaChallenge(\n factor = \"totp\"\n)\n// Save the challenge ID to complete the challenge later\nval challengeId = response.id\n```\n{% /multicode %}\n{% /tabsitem %}\n{% /tabs %}\n{% /section %}\n\n{% section #complete-challenge step=8 title=\"Complete challenge\" %}\nOnce the user receives the challenge code, you can pass the code back to Appwrite to complete the challenge.\n\n{% multicode %}\n```client-web\nconst response = await account.updateMfaChallenge({\n challengeId: '',\n otp: ''\n});\n```\n\n```client-flutter\nFuture result = account.updateMfaChallenge(\n challengeId: '',\n otp: '',\n);\n\nresult.then((response) {\n print(response);\n}).catchError((error) {\n print(error.response);\n});\n```\n\n```client-apple\nval response = account.updateMfaChallenge(\n challengeId = \"\",\n otp = \"\"\n)\n```\n\n```client-android-kotlin\nlet result = try await account.updateMfaChallenge(\n challengeId: \"\",\n otp: \"\"\n)\n```\n{% /multicode %}\n\nAfter completing the challenge, the user is now authenticated and all requests will be authorized.\nYou can confirm this by running `account.get()`\n{% /section %}\n\n{% section #recovery step=9 title=\"Recovery\" %}\nIn case your user needs to recover their account, they can use the recovery codes generated in the first step with the\nrecovery code factor. Initialize the challenge by calling `account.createMfaChallenge()` with the factor `recoverycode`.\n\n{% multicode %}\n```client-web\nconst challenge = await account.createMfaChallenge({\n factor: AuthenticationFactor.Recoverycode\n});\n\n// Save the challenge ID to complete the challenge later\nconst challengeId = challenge.$id;\n```\n\n```client-flutter\nFuture result = account.createMfaChallenge(\nfactor: 'recoverycode',\n);\n\nresult.then((response) {\n print(response);\n // Save the challenge ID to complete the challenge later\n var challengeId = response.$id;\n}).catchError((error) {\n print(error.response);\n});\n```\n\n```client-apple\nlet response = try await account.createMfaChallenge(\n factor: \"recoverycode\"\n)\n// Save the challenge ID to complete the challenge later\nlet challengeId = response.id\n```\n\n```client-android-kotlin\nval response = account.createMfaChallenge(\n factor = \"recoverycode\"\n)\n// Save the challenge ID to complete the challenge later\nval challengeId = response.id\n```\n{% /multicode %}\n\nThen complete the challenge by calling `account.updateMfaChallenge()` with the challenge ID and the recovery code.\n\n{% multicode %}\n```client-web\nconst response = await account.updateMfaChallenge({\n challengeId: '',\n otp: ''\n});\n```\n\n```client-flutter\nFuture result = account.updateMfaChallenge(\n challengeId: '',\n otp: '',\n);\n\nresult.then((response) {\n print(response);\n}).catchError((error) {\n print(error.response);\n});\n```\n\n```client-apple\nval response = account.updateMfaChallenge(\n challengeId = \"\",\n otp = \"\"\n)\n```\n\n```client-android-kotlin\nlet result = try await account.updateMfaChallenge(\n challengeId: \"\",\n otp: \"\"\n)\n```\n{% /multicode %}\n{% /section %}"}, {"path": "docs/products/auth/multi-tenancy", "title": "Multi-tenancy with Teams", "description": "Learn how to implement multi-tenancy in your applications using Appwrite Teams.", "content": "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.\n\n{% arrow_link href=\"/docs/products/auth/teams\" %}\nLearn more about Teams\n{% /arrow_link %}\n\n# What is multi-tenancy?\n\nMulti-tenancy is a design pattern where a single instance of software serves multiple user groups (tenants). With Appwrite Teams, you can:\n\n- Create a team for each tenant in your application\n- Control access to resources using team-based permissions\n- Define different roles within each tenant\n- Scale to unlimited tenants without code changes\n\n# Common use cases\n\n- **SaaS applications**: Organizations that need isolated data and users\n- **Collaborative tools**: Projects with different access levels\n- **Educational platforms**: Schools with teachers and students\n- **Business software**: Companies with department-based access control\n\n# Create teams for tenants\n\nWhen a new tenant signs up, create a dedicated team that serves as their isolated environment.\n\n{% multicode %}\n```client-web\nimport { Client, Teams, ID } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nconst teams = new Teams(client);\n\n// Create team for a new tenant\nconst tenantTeam = await teams.create(\n 'example_corp', // Team ID for tenant\n 'Example Corp', // Tenant name\n ['owner', 'admin', 'member'] // Tenant roles\n);\n```\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nfinal teams = Teams(client);\n\n// Create team for a new tenant\nfinal tenantTeam = await teams.create(\n teamId: 'example_corp', // Team ID for tenant\n name: 'Example Corp', // Tenant name\n roles: ['owner', 'admin', 'member'] // Tenant roles\n);\n```\n```client-apple\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nlet teams = Teams(client)\n\n// Create team for a new tenant\nlet tenantTeam = try await teams.create(\n teamId: \"example_corp\", // Team ID for tenant\n name: \"Example Corp\", // Tenant name\n roles: [\"owner\", \"admin\", \"member\"] // Tenant roles\n)\n```\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.ID\nimport io.appwrite.services.Teams\n\nval client = Client(context)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nval teams = Teams(client)\n\n// Create team for a new tenant\nval tenantTeam = teams.create(\n teamId = \"example_corp\", // Team ID for tenant\n name = \"Example Corp\", // Tenant name\n roles = listOf(\"owner\", \"admin\", \"member\") // Tenant roles\n)\n```\n{% /multicode %}\n\n# Add members to tenants\n\nInvite users to join a tenant using team memberships. Each member can be assigned different roles for access control.\n\n{% multicode %}\n```client-web\nimport { Client, Teams } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nconst teams = new Teams(client);\n\n// Invite a member to the tenant\nconst membership = await teams.createMembership(\n 'example_corp', // Team/tenant ID\n ['admin'], // Member's role in the tenant\n 'user@example.com', // User's email\n undefined, // userId (optional)\n undefined, // phone (optional)\n 'https://example.com/accept-invite' // Redirect URL after accepting\n);\n```\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nfinal teams = Teams(client);\n\n// Invite a member to the tenant\nfinal membership = await teams.createMembership(\n teamId: 'example_corp', // Team/tenant ID\n roles: ['admin'], // Member's role in the tenant\n email: 'user@example.com', // User's email\n url: 'https://example.com/accept-invite' // Redirect URL after accepting\n);\n```\n```client-apple\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nlet teams = Teams(client)\n\n// Invite a member to the tenant\nlet membership = try await teams.createMembership(\n teamId: \"example_corp\", // Team/tenant ID\n roles: [\"admin\"], // Member's role in the tenant\n email: \"user@example.com\", // User's email\n url: \"https://example.com/accept-invite\" // Redirect URL after accepting\n)\n```\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Teams\n\nval client = Client(context)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nval teams = Teams(client)\n\n// Invite a member to the tenant\nval membership = teams.createMembership(\n teamId = \"example_corp\", // Team/tenant ID\n roles = listOf(\"admin\"), // Member's role in the tenant\n email = \"user@example.com\", // User's email\n url = \"https://example.com/accept-invite\" // Redirect URL after accepting\n)\n```\n{% /multicode %}\n\n# Secure resources with team permissions\n\nControl access to rows and resources using team-based permissions. This ensures data isolation between tenants.\n\n{% multicode %}\n```client-web\nimport { Client, TablesDB, ID, Permission, Role } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nconst tablesDB = new TablesDB(client);\n\n// Create a row that only members of \"Example Corp\" tenant can access\nconst row = await tablesDB.createRow(\n 'invoices_db',\n 'invoices',\n ID.unique(),\n {\n title: 'Q2 Invoice',\n amount: 2500.00,\n customer: 'Example Customer',\n status: 'pending',\n tenant_id: 'example_corp'\n },\n [\n // All Example Corp team members can read\n Permission.read(Role.team('example_corp')),\n\n // Only admins can update\n Permission.write(Role.team('example_corp', ['admin']))\n ]\n);\n```\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nfinal tablesDB = TablesDB(client);\n\n// Create a row that only members of \"Example Corp\" tenant can access\nfinal row = await tablesDB.createRow(\n databaseId: 'invoices_db',\n tableId: 'invoices',\n rowId: ID.unique(),\n data: {\n 'title': 'Q2 Invoice',\n 'amount': 2500.00,\n 'customer': 'Example Customer',\n 'status': 'pending',\n 'tenant_id': 'example_corp'\n },\n permissions: [\n // All Example Corp team members can read\n Permission.read(Role.team('example_corp')),\n\n // Only admins can update\n Permission.write(Role.team('example_corp', ['admin']))\n ]\n);\n```\n```client-apple\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nlet tablesDB = TablesDB(client)\n\n// Create a row that only members of \"Example Corp\" tenant can access\nlet row = try await tablesDB.createRow(\n databaseId: \"invoices_db\",\n tableId: \"invoices\",\n rowId: ID.unique(),\n data: [\n \"title\": \"Q2 Invoice\",\n \"amount\": 2500.00,\n \"customer\": \"Example Customer\",\n \"status\": \"pending\",\n \"tenant_id\": \"example_corp\"\n ],\n permissions: [\n // All Example Corp team members can read\n Permission.read(Role.team(\"example_corp\")),\n\n // Only admins can update\n Permission.write(Role.team(\"example_corp\", [\"admin\"]))\n ]\n)\n```\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.ID\nimport io.appwrite.services.TablesDB\nimport io.appwrite.models.Permission\nimport io.appwrite.models.Role\n\nval client = Client(context)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nval tablesDB = TablesDB(client)\n\n// Create a row that only members of \"Example Corp\" tenant can access\nval row = tablesDB.createRow(\n databaseId = \"invoices_db\",\n tableId = \"invoices\",\n rowId = ID.unique(),\n data = mapOf(\n \"title\" to \"Q2 Invoice\",\n \"amount\" to 2500.00,\n \"customer\" to \"Example Customer\",\n \"status\" to \"pending\",\n \"tenant_id\" to \"example_corp\"\n ),\n permissions = listOf(\n // All Example Corp team members can read\n Permission.read(Role.team(\"example_corp\")),\n\n // Only admins can update\n Permission.write(Role.team(\"example_corp\", listOf(\"admin\")))\n )\n)\n```\n{% /multicode %}\n\n# Query tenant data\n\nWhen querying data, users will automatically only see rows they have permission to access based on their team memberships.\n\n{% multicode %}\n```client-web\nimport { Client, TablesDB, Query } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nconst tablesDB = new TablesDB(client);\n\n// Current user will only see invoices they have access to\nconst rows = await tablesDB.listRows(\n 'invoices_db',\n 'invoices'\n);\n\n// For specific tenant data, you can add a query filter\nconst tenantDocuments = await tablesDB.listRows(\n 'invoices_db',\n 'invoices',\n [\n Query.equal('tenant_id', 'example_corp')\n ]\n);\n```\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nfinal tablesDB = TablesDB(client);\n\n// Current user will only see invoices they have access to\nfinal rows = await tablesDB.listRows(\n databaseId: 'invoices_db',\n tableId: 'invoices',\n);\n\n// For specific tenant data, you can add a query filter\nfinal tenantDocuments = await tablesDB.listRows(\n databaseId: 'invoices_db',\n tableId: 'invoices',\n queries: [\n Query.equal('tenant_id', 'example_corp')\n ]\n);\n```\n```client-apple\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nlet tablesDB = TablesDB(client)\n\n// Current user will only see invoices they have access to\nlet rows = try await tablesDB.listRows(\n databaseId: \"invoices_db\",\n tableId: \"invoices\"\n)\n\n// For specific tenant data, you can add a query filter\nlet tenantDocuments = try await tablesDB.listRows(\n databaseId: \"invoices_db\",\n tableId: \"invoices\",\n queries: [\n Query.equal(key: \"tenant_id\", value: \"example_corp\")\n ]\n)\n```\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.TablesDB\nimport io.appwrite.Query\n\nval client = Client(context)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nval tablesDB = TablesDB(client)\n\n// Current user will only see invoices they have access to\nval rows = tablesDB.listRows(\n databaseId = \"invoices_db\",\n tableId = \"invoices\"\n)\n\n// For specific tenant data, you can add a query filter\nval tenantDocuments = tablesDB.listRows(\n databaseId = \"invoices_db\",\n tableId = \"invoices\",\n queries = listOf(\n Query.equal(\"tenant_id\", \"example_corp\")\n )\n)\n```\n{% /multicode %}\n\n{% arrow_link href=\"/docs/products/auth/team-invites\" %}\nLearn how to manage team invitations\n{% /arrow_link %}"}, {"path": "docs/products/auth/oauth2", "title": "OAuth 2 login", "description": "Integrate OAuth2 authentication seamlessly with Appwrite. Learn how to connect your application with third-party OAuth2 providers for secure user login and access.", "content": "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.\n\nWhen 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.\n\n{% info title=\"Identities and OAuth2\" %}\nOAuth2 login creates an **identity** in Appwrite, allowing users to connect multiple providers to a single account. Learn more in [Identities](/docs/products/auth/identities).\n{% /info %}\n\n# Configure OAuth 2 login {% #configure %}\n\nBefore using OAuth 2 login, you need to enable and configure an OAuth 2 login provider.\n\n1. Navigate to your Appwrite project.\n2. Navigate to **Auth** > **Settings**.\n3. Find and open the OAuth provider.\n4. In the OAuth 2 settings modal, use the toggle to enable the provider.\n5. Create and OAuth 2 app on the provider's developer platform.\n6. Copy information from your OAuth2 provider's developer platform to fill the **OAuth2 Settings** modal in the Appwrite Console.\n7. 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.\n\n# Initialize OAuth 2 login {% #init %}\n\nTo initialize the OAuth 2 login process, use the [Create OAuth 2 Session](/docs/references/cloud/client-web/account#createOAuth2Session) route.\n\nOAuth2 sessions allow you to specify the scope of the access you want to request from the OAuth2 provider.\nThe requested scopes describe which resources a session can access.\n\nYou can pass the scopes to request through the `scopes` parameter when creating a session.\nThe scope is provider-specific and can be found in the provider's documentation.\n\n{% tabs %}\n{% tabsitem #js title=\"Javascript\" %}\n```client-web\nimport { Client, Account, OAuthProvider } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject(''); // Your project ID\n\nconst account = new Account(client);\n\n// Go to OAuth provider login page\naccount.createOAuth2Session({\n provider: OAuthProvider.Github,\n success: 'https://example.com/success', // redirect here on success\n failure: 'https://example.com/failed', // redirect here on failure\n scopes: ['repo', 'user'] // scopes (optional)\n});\n```\n{% /tabsitem %}\n\n{% tabsitem #flutter title=\"Flutter\" %}\n\nFor Android, add the following activity inside the `` tag in your `AndroidManifest.xml`. Replace `` with your actual Appwrite project ID.\n\n```xml\n\n\n \n \n \n \n \" />\n \n\n```\n\nNo other configuration is required for iOS.\n\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\nimport 'package:appwrite/enums.dart';\n\nfinal client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject(''); // Your project ID\n\nfinal account = Account(client);\n\n// Go to OAuth provider login page\nawait account.createOAuth2Session({\n provider: OAuthProvider.github,\n scopes: ['repo', 'user']\n});\n```\n{% /tabsitem %}\n\n{% tabsitem #apple title=\"Apple\" %}\nFor Apple, add the following URL scheme to your `Info.plist`.\n\n```xml\nCFBundleURLTypes\n\n\n CFBundleTypeRole\n Editor\n CFBundleURLName\n io.appwrite\n CFBundleURLSchemes\n \n appwrite-callback-\n \n\n\n```\nIf you're using UIKit, you'll also need to add a hook to your `SceneDelegate.swift` file to ensure cookies work correctly.\n\n```client-apple\nfunc scene(_ scene: UIScene, openURLContexts URLContexts: Set) {\n guard let url = URLContexts.first?.url,\n url.absoluteString.contains(\"appwrite-callback\") else {\n return\n }\n WebAuthComponent.handleIncomingCookie(from: url)\n}\n```\n\n```client-apple\nimport Appwrite\nimport AppwriteEnums\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n\nlet account = Account(client)\n\n// Go to OAuth provider login page\ntry await account.createOAuth2Session(\n provider: .github,\n scopes: ['repo', 'user']\n)\n```\n{% /tabsitem %}\n\n{% tabsitem #android title=\"Android\" %}\nFor Android, add the following activity inside the `` tag in your `AndroidManifest.xml`.\nReplace `` with your actual Appwrite project ID.\n\n```xml\n\n\n \n \n \n \n \" />\n \n\n```\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Account\nimport io.appwrite.enums.OAuthProvider\n\nval client = Client(context) // Activity or application context\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n\nval account = Account(client)\n\n// Go to OAuth provider login page\naccount.createOAuth2Session(\n provider = OAuthProvider.GITHUB,\n scopes = listOf('repo', 'user')\n)\n```\n{% /tabsitem %}\n\n{% tabsitem #react-native title=\"React Native\" %}\nIf using Expo, set the URL scheme to `appwrite-callback-` in your `app.json` file.\n\n```json\n{\n \"expo\": {\n \"scheme\": \"appwrite-callback-\"\n }\n}\n```\n\nThen, 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.\n\n```client-react-native\nimport { Client, Account, OAuthProvider } from \"appwrite\";\nimport { makeRedirectUri } from 'expo-auth-session'\nimport * as WebBrowser from 'expo-web-browser';\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject(''); // Your project ID\n\nconst account = new Account(client);\n\n// Create deep link that works across Expo environments\n// Ensure localhost is used for the hostname to validation error for success/failure URLs\nconst deepLink = new URL(makeRedirectUri({ preferLocalhost: true }));\nconst scheme = `${deepLink.protocol}//`; // e.g. 'exp://' or 'appwrite-callback-://'\n\n// Start OAuth flow\nconst loginUrl = await account.createOAuth2Token({\n provider,\n success: `${deepLink}`,\n failure: `${deepLink}`,\n});\n\n// Open loginUrl and listen for the scheme redirect\nconst result = await WebBrowser.openAuthSessionAsync(`${loginUrl}`, scheme);\n\n// Extract credentials from OAuth redirect URL\nconst url = new URL(result.url);\nconst secret = url.searchParams.get('secret');\nconst userId = url.searchParams.get('userId');\n\n// Create session with OAuth credentials\nawait account.createSession({\n userId,\n secret\n});\n// Redirect as needed\n```\n{% /tabsitem %}\n{% /tabs %}\n\nYou'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.\n\nYou can optionally configure `success` or `failure` redirect links on web to handle success and failure scenarios.\n\n# OAuth 2 profile {% #profile %}\n\nAfter 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.\n\nAfter creating an OAuth 2 session, you can fetch the session to get information about the provider.\n\n{% info title=\"Tip\" %}\nReplace `[SESSION_ID]` with either `\"current\"` to get or update the active session, or with a specific session ID.\n{% /info %}\n\n{% multicode %}\n```client-web\nimport { Client, Account } from \"appwrite\";\n\nconst client = new Client();\n\nconst account = new Account(client);\n\nconst session = await account.getSession({\n sessionId: 'current'\n});\n\n// Provider information\nconsole.log(session.provider);\nconsole.log(session.providerUid);\nconsole.log(session.providerAccessToken);\n```\n\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject(''); // Your project ID\n\nfinal account = Account(client);\n\nfinal session = await getSession(\n sessionId : \"\"\n);\n\n// Provider information\nprint(session.provider);\nprint(session.providerUid);\nprint(session.providerAccessToken);\n```\n```client-apple\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n\nlet account = Account(client)\n\nlet session = try await account.getSession(\n sessionId: \"\"\n)\n\n// Provider information\nprint(session.provider);\nprint(session.providerUid);\nprint(session.providerAccessToken);\n```\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Account\n\nval client = Client(context)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n\nval account = Account(client)\n\nval response = account.getSession(\n sessionId = \"\"\n)\n\n// Provider information\nprint(session.provider);\nprint(session.providerUid);\nprint(session.providerAccessToken);\n```\n{% /multicode %}\n\n\nAn OAuth 2 [session](/docs/references/cloud/models/session) will have the following columns.\n\n| Property | Description |\n| -------------------------- | --------------------------------------------------------------------------------------------------------- |\n| provider | The OAuth2 Provider. |\n| providerUid | User ID from the OAuth 2 Provider. |\n| providerAccessToken | Access token from the OAuth 2 provider. Use this to **make requests to the OAuth 2 provider** to fetch personal data. |\n| providerAccessTokenExpiry | Check this value to know if an access token is about to expire. |\n\nYou 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.\n\n# Refresh tokens {% #refresh-tokens %}\n\nOAuth 2 sessions expire to protect from security risks.\nThis means the OAuth 2 session with a provider may expire, even when an Appwrite session remains active.\nOAuth 2 sessions should be refreshed periodically so access tokens don't expire.\n\nCheck the value of `providerAccessTokenExpiry` to know if the token is expired or is about to expire.\nYou 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.\nAvoid refreshing before every request, which might cause rate limit problems.\n\n{% multicode %}\n```client-web\nconst promise = account.updateSession({\n sessionId: '[SESSION_ID]'\n});\n\npromise.then(function (response) {\n console.log(response); // Success\n}, function (error) {\n console.log(error); // Failure\n});\n```\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject(''); // Your project ID\n\nAccount account = Account(client);\n\nfinal result = await account.updateSession(\n sessionId: ''\n);\n```\n```client-apple\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n\nlet account = Account(client)\n\nlet session = try await account.updateSession(\n sessionId: \"\"\n);\n```\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Account\n\nval client = Client(context)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n\nval account = Account(client)\n\nval response = account.updateSession(\n sessionId = \"\"\n);\n```\n\n{% /multicode %}\n\n{% info title=\"GraphQL\" %}\nOAuth 2 is not available through the GraphQL API. You can use the REST API or any Client SDK instead.\n{% /info %}"}, {"path": "docs/products/auth/phone-sms", "title": "Phone (SMS) login", "description": "Enhance security with SMS and phone authentication in Appwrite. Add multi-factor authentication via SMS, verify phone numbers, and protect user accounts.", "content": "{% info title=\"Note\" %}\nOTPs are billed per message, with rates varying by country. See the [phone OTP rates](/docs/advanced/billing/phone-otp#rates) for more information.\n{% /info %}\n\nPhone authentication lets users create accounts using their phone numbers and log in through SMS messages.\n\nCreate and use [mock phone numbers](/docs/products/auth/security#mock-phone-numbers) to initiate a phone authentication process without an actual phone number.\n\n# Send SMS message {% #init %}\n\nPhone 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.\n\nSend 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.\n\n{% multicode %}\n\n```client-web\nimport { Client, Account, ID } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nconst account = new Account(client);\n\nconst token = await account.createPhoneToken({\n userId: ID.unique(),\n phone: '+14255550123'\n});\n\nconst userId = token.userId;\n```\n\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nfinal account = Account(client);\n\nfinal token = await account.createPhoneToken(\n userId: ID.unique(),\n phone: '+14255550123'\n);\n\nfinal userId = token.userId;\n```\n```client-apple\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\");\n\nlet account = Account(client);\n\nlet token = try await account.createPhoneToken(\n userId: ID.unique(),\n phone: \"+14255550123\"\n);\n\nlet userId = token.userId;\n```\n\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Account\nimport io.appwrite.ID\n\nval client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\");\n\nval account = Account(client);\n\nval token = account.createPhoneToken(\n userId = ID.unique(),\n phone = \"+14255550123\"\n);\n\nval userId = token.userId;\n```\n\n```graphql\nmutation {\n accountCreatePhoneToken(userId: \"unique()\", phone: \"+14255550123\") {\n _id\n userId\n secret\n expire\n }\n}\n```\n\n{% /multicode %}\n\n# Login {% #login %}\n\nAfter 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.\n\n{% multicode %}\n\n```client-web\nimport { Client, Account, ID } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nconst account = new Account(client);\n\nconst session = await account.createSession({\n userId: userId,\n secret: ''\n});\n```\n\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nfinal account = Account(client);\n\nfinal session = await account.createSession(\n userId: userId,\n secret: ''\n);\n```\n```client-apple\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\");\n\nlet account = Account(client);\n\nlet session = try await account.createSession(\n userId: userId,\n secret: \"\"\n);\n```\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Account\nimport io.appwrite.ID\n\nval client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\");\n\nval account = Account(client);\n\nval session = account.createSession(\n userId = userId,\n secret = \"\"\n);\n```\n\n```graphql\nmutation {\n accountCreateSession(userId: \"\", secret: \"\") {\n _id\n userId\n provider\n expire\n }\n}\n```\n\n{% /multicode %}\n\nAfter the secret is verified, a session will be created."}, {"path": "docs/products/auth/preferences", "title": "Preferences", "description": "Store and manage user preferences in Appwrite using Account API and Teams API for individual and shared settings.", "content": "Preferences allow you to store settings like theme choice, language selection, or notification preferences that are specific to individual users or shared across teams.\n\n# User preferences {% #user-preferences %}\n\nYou can store user preferences on a user's account using Appwrite's [Update Preferences](/docs/references/cloud/client-web/account#updatePrefs) endpoint.\n\nPreferences 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.\n\n## Update user preferences\n\nUse the `updatePrefs` method to store user preferences as a JSON object.\n\n{% multicode %}\n```client-web\nimport { Client, Account } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject(''); // Your project ID\n\nconst account = new Account(client);\n\nconst promise = account.updatePrefs({darkTheme: true, language: 'en'});\n\npromise.then(function (response) {\n console.log(response); // Success\n}, function (error) {\n console.log(error); // Failure\n});\n```\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject(''); // Your project ID\n\nfinal account = Account(client);\n\nfinal user = await account.updatePrefs(\n prefs: {\n \"darkTheme\": true,\n \"language\": \"en\",\n }\n);\n```\n```client-apple\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n\nlet account = Account(client)\n\nlet user = try await account.updatePrefs(\n prefs: [\n \"darkTheme\": true,\n \"language\": \"en\"\n ]\n)\n```\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Account\n\nval client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n\nval account = Account(client)\n\nval user = account.updatePrefs(\n prefs = mapOf(\n \"darkTheme\" to true,\n \"language\" to \"en\"\n )\n)\n```\n```graphql\nmutation {\n accountUpdatePrefs(\n prefs: \"{\\\"darkTheme\\\": true, \\\"language\\\": \\\"en\\\"}\"\n ) {\n _id\n name\n prefs {\n data\n }\n }\n}\n```\n{% /multicode %}\n\n## Get user preferences\n\nRetrieve stored preferences with the `getPrefs` method.\n\n{% multicode %}\n```client-web\nimport { Client, Account } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject(''); // Your project ID\n\nconst account = new Account(client);\n\nconst promise = account.getPrefs();\n\npromise.then(function (response) {\n console.log(response); // Success\n}, function (error) {\n console.log(error); // Failure\n});\n```\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject(''); // Your project ID\n\nfinal account = Account(client);\n\nfinal prefs = await account.getPrefs();\n```\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Account\n\nval client = Client(context)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n\nval account = Account(client)\n\nval prefs = account.getPrefs()\n```\n```client-apple\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n\nlet account = Account(client)\n\nlet prefs = try await account.getPrefs()\n```\n```graphql\nquery {\n accountGetPrefs {\n data\n }\n}\n```\n{% /multicode %}\n\n# Team preferences {% #team-preferences %}\n\nTeam 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.\n\nTeam 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.\n\n{% arrow_link href=\"/docs/products/auth/teams\" %}\nLearn more about Appwrite Teams\n{% /arrow_link %}\n\n## Update team preferences\n\nStore team-wide settings using the `updatePrefs` method with a team ID.\n\n{% multicode %}\n```client-web\nimport { Client, Teams } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject(''); // Your project ID\n\nconst teams = new Teams(client);\n\nconst promise = teams.updatePrefs(\n '',\n {\n theme: 'corporate',\n notificationsEnabled: true,\n defaultView: 'kanban'\n }\n);\n\npromise.then(function (response) {\n console.log(response); // Success\n}, function (error) {\n console.log(error); // Failure\n});\n```\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject(''); // Your project ID\n\nfinal teams = Teams(client);\n\nfinal team = await teams.updatePrefs(\n teamId: '',\n prefs: {\n \"theme\": \"corporate\",\n \"notificationsEnabled\": true,\n \"defaultView\": \"kanban\"\n }\n);\n```\n```client-apple\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n\nlet teams = Teams(client)\n\nlet team = try await teams.updatePrefs(\n teamId: \"\",\n prefs: [\n \"theme\": \"corporate\",\n \"notificationsEnabled\": true,\n \"defaultView\": \"kanban\"\n ]\n)\n```\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Teams\n\nval client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n\nval teams = Teams(client)\n\nval team = teams.updatePrefs(\n teamId = \"\",\n prefs = mapOf(\n \"theme\" to \"corporate\",\n \"notificationsEnabled\" to true,\n \"defaultView\" to \"kanban\"\n )\n)\n```\n{% /multicode %}\n\n## Get team preferences\n\nFetch team preferences by passing a team ID to the `getPrefs` method.\n\n{% multicode %}\n```client-web\nimport { Client, Teams } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject(''); // Your project ID\n\nconst teams = new Teams(client);\n\nconst promise = teams.getPrefs('');\n\npromise.then(function (prefs) {\n console.log(prefs); // Team preferences\n}, function (error) {\n console.log(error);\n});\n```\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject(''); // Your project ID\n\nfinal teams = Teams(client);\n\nfinal prefs = await teams.getPrefs(\n teamId: ''\n);\n```\n```client-apple\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n\nlet teams = Teams(client)\n\nlet prefs = try await teams.getPrefs(\n teamId: \"\"\n)\n```\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Teams\n\nval client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n\nval teams = Teams(client)\n\nval prefs = teams.getPrefs(\n teamId = \"\"\n)\n```\n{% /multicode %}\n\n# Browser localStorage {% #localstorage %}\n\nFor device-specific preferences that don't need to sync across devices, the browser's localStorage API is a simple option.\n\n- Device-specific: Settings are only available on the current device\n- No server-side processing required\n- Data persists even after browser sessions end\n- Limited to ~5MB per origin\n\n```js\n// Store a preference\nlocalStorage.setItem('darkMode', 'true');\n\n// Retrieve a preference\nconst darkMode = localStorage.getItem('darkMode');\n```\n\n{% info title=\"Storing larger data\" %}\nFor complex preference structures or when storing larger amounts of data, Appwrite Databases offer a flexible solution. [Learn more about Appwrite Databases](/docs/products/databases).\n{% /info %}"}, {"path": "docs/products/auth/presences", "title": "Presences", "description": "Track which signed-in users are active right now and broadcast their status in realtime with the Appwrite Presences API.", "content": "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.\n\nA 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).\n\n# Set the user's presence {% #set-the-users-presence %}\n\nOnce 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.\n\n{% multicode %}\n```client-web\nimport { Client, Presences, ID, Permission, Role } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nconst presences = new Presences(client);\n\nconst presence = await presences.upsert({\n presenceId: ID.unique(),\n status: 'online',\n metadata: { page: '/dashboard' },\n permissions: [\n Permission.read(Role.users())\n ]\n});\n```\n\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nfinal presences = Presences(client);\n\nfinal presence = await presences.upsert(\n presenceId: ID.unique(),\n status: 'online',\n metadata: { 'page': '/dashboard' },\n permissions: [\n Permission.read(Role.users()),\n ],\n);\n```\n\n```client-apple\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nlet presences = Presences(client)\n\nlet presence = try await presences.upsert(\n presenceId: ID.unique(),\n status: \"online\",\n metadata: [\"page\": \"/dashboard\"],\n permissions: [\n Permission.read(Role.users())\n ]\n)\n```\n\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.ID\nimport io.appwrite.Permission\nimport io.appwrite.Role\nimport io.appwrite.services.Presences\n\nval client = Client(context)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nval presences = Presences(client)\n\nval presence = presences.upsert(\n presenceId = ID.unique(),\n status = \"online\",\n metadata = mapOf(\"page\" to \"/dashboard\"),\n permissions = listOf(\n Permission.read(Role.users())\n )\n)\n```\n{% /multicode %}\n\nStore 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`.\n\n# Update on activity changes {% #update-on-activity-changes %}\n\nMost apps update presence on a few specific signals:\n\n- **Window focus and blur** to flip between `online` and `away`.\n- **Route changes** to update the `page` field in `metadata` and show \"viewing this page\".\n- **Typing events** in a chat or comment box to set `status: 'typing'` and clear it when the user stops.\n- **A heartbeat timer** (for example every 30 seconds) to push the `expiresAt` forward and keep the record alive while the user is active.\n\n```client-web\nasync function setStatus(status, metadata = {}) {\n await presences.upsert({\n presenceId,\n status,\n metadata,\n permissions: [\n Permission.read(Role.users())\n ]\n });\n}\n\nwindow.addEventListener('focus', () => setStatus('online'));\nwindow.addEventListener('blur', () => setStatus('away'));\n```\n\nThere 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.\n\n# Show other users' presence {% #show-other-users-presence %}\n\nList 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.\n\n{% multicode %}\n```client-web\nimport { Client, Presences } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nconst presences = new Presences(client);\n\nconst result = await presences.list();\n\nconst onlineUsers = new Map(\n result.presences.map(presence => [presence.userId, presence])\n);\n```\n\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nfinal presences = Presences(client);\n\nfinal result = await presences.list();\n\nfinal onlineUsers = {\n for (final presence in result.presences) presence.userId: presence\n};\n```\n\n```client-apple\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nlet presences = Presences(client)\n\nlet result = try await presences.list()\n\nvar onlineUsers: [String: Any] = [:]\nfor presence in result.presences {\n onlineUsers[presence.userId] = presence\n}\n```\n\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Presences\n\nval client = Client(context)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nval presences = Presences(client)\n\nval result = presences.list()\n\nval onlineUsers = result.presences\n .associateBy { it.userId }\n .toMutableMap()\n```\n{% /multicode %}\n\nThen 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.\n\n{% multicode %}\n```client-web\nimport { Client, Realtime, Channel } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nconst realtime = new Realtime(client);\n\nawait realtime.subscribe(Channel.presences(), response => {\n const presence = response.payload;\n if (response.events.includes('presences.*.delete')) {\n onlineUsers.delete(presence.userId);\n } else {\n onlineUsers.set(presence.userId, presence);\n }\n});\n```\n\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nfinal realtime = Realtime(client);\n\nfinal subscription = realtime.subscribe([Channel.presences()]);\n\nsubscription.stream.listen((response) {\n final presence = response.payload;\n if (response.events.contains('presences.*.delete')) {\n onlineUsers.remove(presence['userId']);\n } else {\n onlineUsers[presence['userId']] = presence;\n }\n});\n```\n\n```client-apple\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nlet realtime = Realtime(client)\n\nlet subscription = realtime.subscribe(channels: [Channel.presences()]) { response in\n guard let payload = response.payload as? [String: Any],\n let userId = payload[\"userId\"] as? String else { return }\n\n if (response.events?.contains(\"presences.*.delete\") == true) {\n onlineUsers.removeValue(forKey: userId)\n } else {\n onlineUsers[userId] = payload\n }\n}\n```\n\n```client-android-kotlin\nimport io.appwrite.Channel\nimport io.appwrite.Client\nimport io.appwrite.services.Realtime\n\nval client = Client(context)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nval realtime = Realtime(client)\n\nrealtime.subscribe(Channel.presences()) { response ->\n val payload = response.payload as? Map ?: return@subscribe\n val userId = payload[\"userId\"] as? String ?: return@subscribe\n\n if (response.events.contains(\"presences.*.delete\")) {\n onlineUsers.remove(userId)\n } else {\n onlineUsers[userId] = payload\n }\n}\n```\n{% /multicode %}\n\n# Clear presence on sign out {% #clear-presence-on-sign-out %}\n\nA 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.\n\n{% multicode %}\n```client-web\nawait presences.delete({ presenceId });\nawait account.deleteSession({ sessionId: 'current' });\n```\n\n```client-flutter\nawait presences.delete(presenceId: presenceId);\nawait account.deleteSession(sessionId: 'current');\n```\n\n```client-apple\ntry await presences.delete(presenceId: presenceId)\ntry await account.deleteSession(sessionId: \"current\")\n```\n\n```client-android-kotlin\npresences.delete(presenceId = presenceId)\naccount.deleteSession(sessionId = \"current\")\n```\n{% /multicode %}\n\nIf 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.\n\n# Scoping who can see a presence {% #scoping-who-can-see-a-presence %}\n\nPresences use the standard Appwrite [permissions system](/docs/advanced/security/permissions). Set read permissions on each record to match how your app already groups users:\n\n- `Role.users()` for any signed-in user, useful for a global \"X users online\" counter.\n- `Role.team('')` for collaboration features that should only show statuses to teammates.\n- `Role.user('')` for one-to-one features such as DMs, where only the recipient should see the sender's typing state.\n\nPass 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:\n\n{% multicode %}\n```client-web\nimport { Client, Presences, ID, Permission, Role } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nconst presences = new Presences(client);\n\nconst presence = await presences.upsert({\n presenceId: ID.unique(),\n status: 'typing',\n permissions: [\n Permission.read(Role.user(''))\n ]\n});\n```\n\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nfinal presences = Presences(client);\n\nfinal presence = await presences.upsert(\n presenceId: ID.unique(),\n status: 'typing',\n permissions: [\n Permission.read(Role.user('')),\n ],\n);\n```\n\n```client-apple\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nlet presences = Presences(client)\n\nlet presence = try await presences.upsert(\n presenceId: ID.unique(),\n status: \"typing\",\n permissions: [\n Permission.read(Role.user(\"\"))\n ]\n)\n```\n\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.ID\nimport io.appwrite.Permission\nimport io.appwrite.Role\nimport io.appwrite.services.Presences\n\nval client = Client(context)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nval presences = Presences(client)\n\nval presence = presences.upsert(\n presenceId = ID.unique(),\n status = \"typing\",\n permissions = listOf(\n Permission.read(Role.user(\"\"))\n )\n)\n```\n{% /multicode %}\n\nPresence 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.\n\nIf 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.\n\n# Where to next {% #where-to-next %}\n\n- [Realtime: Presences](/docs/apis/realtime/presences). The full concept reference, including channel patterns, expiry behaviour, and server-side usage.\n- [Realtime channels](/docs/apis/realtime/channels). See how `presences` fits alongside `account`, `teams`, and `rows`.\n- [Permissions](/docs/advanced/security/permissions). Refresher on how `Role.team()` and `Role.user()` work."}, {"path": "docs/products/auth/quick-start", "title": "Start with Authentication", "description": "Effortlessly add authentication to your apps - simple signup & login in just minutes with Appwrite Authentication", "content": "You can get up and running with Appwrite Authentication in minutes.\nYou can add basic email and password authentication to your app with just a few lines of code.\n\n{% section #sign-up step=1 title=\"Signup\" %}\nYou can use the Appwrite [Client SDKs](/docs/sdks#client) to create an account using email and password.\n\n{% multicode %}\n```client-web\nimport { Client, Account, ID } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject(''); // Your project ID\n\nconst account = new Account(client);\n\nconst user = await account.create({\n userId: ID.unique(), \n email: 'email@example.com', \n password: 'password'\n});\n```\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject(''); // Your project ID\n\nfinal account = Account(client);\n\nfinal user = await account.create(\n userId: ID.unique(),\n email: 'email@example.com',\n password: 'password',\n);\n```\n```client-apple\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n\nlet account = Account(client)\n\nlet user = try await account.create(\n userId: ID.unique(),\n email: \"email@example.com\",\n password: \"password\"\n)\n```\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Account\nimport io.appwrite.ID\n\nval client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n\nval account = Account(client)\n\nval user = account.create(\n userId = ID.unique(),\n email = \"email@example.com\",\n password = \"password\"\n)\n```\n```graphql\nmutation {\n accountCreate(userId: \"unique()\", email: \"email@example.com\", password: \"password\") {\n _id\n email\n name\n }\n}\n```\n\n```client-react-native\nimport { Client, Account, ID } from \"appwrite\";\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject(''); // Your project ID\n\nconst account = new Account(client);\n\nconst user = await account.create({\n userId: ID.unique(), \n email: 'email@example.com', \n password: 'password'\n});\n```\n{% /multicode %}\n{% /section %}\n\n{% section #login step=2 title=\"Login\" %}\n\nAfter you've created your account, users can be logged in using the [Create Email Session](/docs/references/cloud/client-web/account#createEmailPasswordSession) method.\n\n{% multicode %}\n```client-web\nconst session = await account.createEmailPasswordSession({\n email: email, \n password: password\n});\n```\n\n```client-flutter\nfinal session = await account.createEmailPasswordSession(\n email: 'email@example.com',\n password: 'password'\n);\n```\n\n```client-apple\nlet session = try await account.createEmailPasswordSession(\n email: \"email@example.com\",\n password: \"password\"\n)\n```\n\n```client-android-kotlin\nval session = account.createEmailPasswordSession(\n email = \"email@example.com\",\n password = \"password\"\n)\n```\n\n```graphql\nmutation {\n accountcreateEmailPasswordSession(email: \"email@example.com\", password: \"password\") {\n _id\n userId\n provider\n expire\n }\n}\n```\n\n```client-react-native\nconst session = await account.createEmailPasswordSession({\n email: email, \n password: password\n});\n```\n{% /multicode %}\n{% /section %}\n\n{% section #check-authentication-state step=3 title=\"Check authentication state\" %}\nAfter logging in, you can check the authentication state of the user.\n\nAppwrite's SDKs are stateless, so you need to manage the session state in your app. \nYou can use the [Get Account](/docs/references/cloud/client-web/account#get) method to check if the user is logged in.\n\n{% multicode %}\n```client-web\ntry {\n const user = await account.get();\n // Logged in\n} catch (err) {\n // Not logged in\n}\n```\n```client-flutter\ntry {\n final user = await account.get();\n // Logged in\n} catch(e) {\n // Not logged in\n}\n```\n```client-apple\ndo {\n let user = try account.get()\n // Logged in\n} catch {\n // Not logged in\n}\n```\n```client-android-kotlin\nreturn try {\n val user = account.get()\n // Logged in\n} catch (e: AppwriteException) {\n // Not logged in\n}\n```\n```graphql\nquery {\n accountGet {\n _id\n _createdAt\n _updatedAt\n name\n registration\n status\n labels\n passwordUpdate\n email\n phone\n emailVerification\n phoneVerification\n prefs {\n data\n }\n accessedAt\n }\n}\n```\n```client-react-native\ntry {\n const user = await account.get();\n // Logged in\n} catch (err) {\n // Not logged in\n}\n```\n{% /multicode %}\n{% /section %}\n\n{% section #auth-and-nav step=4 title=\"Navigation (Optional)\" %}\nA common pattern is to use route guards to redirect users to the login page if they are not authenticated.\nYou can check the authentication state on app launch and before entering a protected route by calling `get()`.\n\nRoute guard implementations are **opinionated** and depend on the platform and frame you are using. \nTake a look at some example usages from different platforms as inspiration.\n\n{% accordion %}\n{% accordion_item title=\"Web frameworks\" %}\nBefore routing to a page, you can check if the user is logged in and redirect them to the login page if they are not.\n\n{% tabs %}\n{% tabsitem #react-router title=\"React router\" %}\nYou can use [React router loaders](https://reactrouter.com/en/main/route/loader) to check if the user is logged in before rendering a route.\n\nYou can find a similar example in this [YouTube video](https://youtu.be/pyfwQUc5Ssk).\n\n```client-web \nimport * as React from \"react\";\nimport {\n createBrowserRouter,\n} from \"react-router-dom\";\nimport \"./index.css\";\n\nimport Login from \"./Login\";\nimport Protected from \"./Protected\";\nimport { account } from \"./lib/appwrite\";\nimport { redirect } from \"react-router-dom\";\n\n\nconst router = createBrowserRouter([\n {\n path: \"/protected\",\n element: ,\n loader: async () => {\n try{\n // logged in? pass user to the route\n const user = await account.get();\n return { user };\n }\n catch {\n // not logged in? redirect to login\n throw redirect('/login')\n }\n }\n },\n {\n path: \"/login\",\n element: ,\n },\n]);\n\nexport default router;\n```\n{% /tabsitem %}\n{% tabsitem #vue-router title=\"Vue router\" %}\nYou 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.\n\nFirst, create a simple Pinia store to manage the authentication state.\n```client-web\nimport { account, ID, type Models } from '@/lib/appwrite'\nimport type { register } from 'module';\nimport { defineStore } from 'pinia'\n\nexport const useAuthStore = defineStore({\n id: 'auth',\n state: () => ({\n user: null as null | Models.User,\n }),\ngetters: {\n isLoggedIn(): boolean {\n return !!this.user;\n },\n},\n actions: {\n async init() {\n try {\n this.user = await account.get();\n }\n catch (error) {\n this.user = null;\n }\n },\n // ... other operations\n },\n})\n```\nThen, check the authentication state before routing to a protected route.\n```client-web\nimport { createApp } from 'vue'\nimport { createPinia } from 'pinia'\n\nimport App from './App.vue'\nimport router from './router'\nimport { useAuthStore } from './stores/auth'\n\nconst app = createApp(App)\napp.use(createPinia())\n\nconst auth = useAuthStore();\n\nauth.init().then(() => {\n router.beforeEach((to, from, next) => {\n // Not logged in? \n if (to.name == 'protected' && auth.isLoggedIn == false) {\n // Redirect to login if going to a protected route\n next({ name: 'login' })\n } else {\n next()\n }\n })\n app.use(router)\n app.mount('#app')\n})\n```\n{% /tabsitem %}\n{% tabsitem #angular-router title=\"Angular router\" %}\n```client-web\nimport { Injectable } from '@angular/core';\nimport { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot, UrlTree, Router } from '@angular/router';\nimport { Observable, from, of } from 'rxjs';\nimport { catchError, map } from 'rxjs/operators';\nimport { account } from './lib/appwrite';\n\n@Injectable({\n providedIn: 'root'\n})\nexport class AuthGuardGuard implements CanActivate {\n constructor(private router: Router) {}\n\n canActivate(\n route: ActivatedRouteSnapshot,\n state: RouterStateSnapshot): Observable | Promise | boolean | UrlTree {\n if (route.routeConfig?.path === \"protected\") {\n return this.checkLogin();\n }\n return of(true);\n }\n\n private checkLogin(): Observable {\n return from(account.get()).pipe(\n map(() => true),\n catchError(() => of(this.router.createUrlTree(['/login'])))\n );\n }\n}\n```\n{% /tabsitem %}\n{% tabsitem #svelte title=\"Svelte\" %}\nIn the root level `+layout.svelte` file, you can check the authentication state before rendering a route.\n```client-web\n// src/routes/+layout.js\nimport { appwrite } from \"$lib/appwrite\";\n\n// Turn off SSR globally, turning the project into a static site\nexport const ssr = false;\n\nexport const load = async () => {\n try {\n return {\n account: await appwrite.account.get(),\n };\n } catch {\n return {\n account: null,\n };\n }\n};\n```\n\nThis will be accessible in the `load` function of each child route.\n```client-web\n// src/routes/protected/+page.js\nimport { redirect } from '@sveltejs/kit';\n\n/** @type {import('./$types').PageLoad} */\nexport async function load({ parent }) {\n\tconst { account } = await parent();\n\tif (!account) {\n\t\tthrow redirect(303, '/login');\n\t}\n}\n```\n{% /tabsitem %}\n{% /tabs %}\n\n{% /accordion_item %}\n{% accordion_item title=\"Mobile and native\" %}\nWith mobile apps, you can apply similar logic to check the authentication state before displaying a screen or view.\n\n{% tabs %}\n{% tabsitem #flutter-go-router title=\"Flutter Go router\" %}\nThis example uses the Flutter Go router as an example, but the same concepts apply to other routing libraries.\n\nFirst, create a `ChangeNotifier` to manage the authentication state.\n\n```client-flutter\nimport 'package:flutter/material.dart';\nimport 'package:appwrite/appwrite.dart' show Client, ID;\nimport 'package:appwrite/appwrite.dart' as Appwrite;\nimport 'package:appwrite/models.dart' show User;\n\nclass Account extends ChangeNotifier {\n final Appwrite.Account _account;\n\n User? _user;\n User? get user => _user;\n\n Account(Client client) : _account = Appwrite.Account(client);\n\n Future init() async {\n try {\n _user = await _account.get();\n \n notifyListeners();\n } catch(e) {\n debugPrint(e.toString());\n rethrow;\n }\n }\n // ... other operations\n}\n```\n\nYou can then use this state to redirect users to the login page if they are not authenticated.\n```client-flutter\nimport 'package:flutter/material.dart';\nimport 'package:go_router/go_router.dart';\nimport 'package:provider/provider.dart';\n\nimport './providers/account.dart';\n\nimport './pages/login.dart';\nimport './pages/protected.dart';\n\nString Function(BuildContext context, GoRouterState state) redirect = \n (BuildContext context, GoRouterState state) => \n context.read().user == null && state.matchedLocation != '/login'\n ? '/login' \n : state.matchedLocation;\n\nfinal router = GoRouter(\n redirect: redirect,\n initialLocation: '/login',\n routes: [\n GoRoute(\n path: '/login',\n pageBuilder: (context, state) => const MaterialPage(child: LoginPage()),\n ),\n GoRoute(\n path: '/protected',\n pageBuilder: (context, state) => const MaterialPage(child: ProtectedPage()),\n )\n ], \n);\n```\n{% /tabsitem %}\n\n{% tabsitem #apple title=\"Apple\" %}\nFor Apple platforms, this example uses a `NavigationStack` but you can use similar concepts with other navigation methods.\n\nInitialize Appwrite and create an `AppwriteService`.\n```client-apple\nimport Foundation\nimport Appwrite\nimport AppwriteModels\nimport JSONCodable\n\nclass Appwrite {\n var client: Client\n var account: Account\n var tablesDB: TablesDB\n let databaseId = \"default\"\n let tableId = \"ideas-tracker\"\n\n public init() {\n self.client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n \n self.account = Account(client)\n }\n public func getUser() async throws -> User<[String: AnyCodable]> {\n let user = try await account.get()\n // you can also store the user in a local store\n return user\n }\n}\n\n```\n\nOn launch, you can display a `SplashView` while you verify the authentication state.\n```client-apple\nimport Foundation\nimport SwiftUI\n\nstruct SplashView: View {\n @EnvironmentObject private var router: Router\n @EnvironmentObject private var AppwriteService: AppwriteService\n\n var body: some View {\n NavigationStack(path: $router.routes) {\n VStack {\n Text(\"Example App\")\n .font(.largeTitle)\n .fontWeight(.bold)\n .padding()\n }.task {\n let user = await self.AppwriteService.getUser();\n\n if !user {\n router.pushReplacement(.login)\n } else {\n router.pushReplacement(.protected)\n }\n }\n .navigationDestination(for: Route.self, destination: { $0 })\n\n }\n }\n}\n```\n\nIn your router, you can also check the authentication state before rendering a route.\n\n```client-apple\nfinal class Router: ObservableObject {\n @Published var routes = [Route]()\n\n func push(_ screen: Route) {\n // Make sure you've already stored the user and auth state in a local store\n if (screen == .protected && !isLoggedIn){\n routes.append(.login)\n }\n routes.append(screen)\n }\n // ... other operations\n}\n```\n{% /tabsitem %}\n{% tabsitem #android title=\"Android\" %}\nCreate some Appwrite Service, for example, `AppwriteService` to manage the authentication state.\nYou can find a version of this example in the [Appwrite Android tutorial](/docs/tutorials/android/step-1).\n```client-android-kotlin\n//... imports\n\nobject Appwrite {\n private const val ENDPOINT = \"https://.cloud.appwrite.io/v1\"\n private const val PROJECT_ID = \"\"\n\n private lateinit var client: Client\n\n fun init(context: Context) {\n client = Client(context)\n .setEndpoint(ENDPOINT)\n .setProject(PROJECT_ID)\n }\n}\n\n```\n\nThen, create an auth service to manage the authentication state.\n\n```client-android-kotlin\n//... imports\n\nclass AccountService(client: Client) {\n private val account = Account(client)\n\n suspend fun getLoggedIn(): User>? {\n return try {\n account.get()\n } catch (e: AppwriteException) {\n null\n }\n } \n // ... other operations\n}\n```\n\nWrap your routes in some view, for example, `AppContent`, to check the authentication state before rendering a route.\n\n```client-android-kotlin\n@Composable\nprivate fun AppContent(accountService: AccountService) {\n val user = remember { mutableStateOf>?>(null) }\n val screen = remember { mutableStateOf(Screen.Protected) }\n\n LaunchedEffect(screen) {\n user.value = accountService.getLoggedIn()\n }\n\n Scaffold(bottomBar = { AppBottomBar(screen) }) { padding ->\n Column(modifier = Modifier.padding(padding)) {\n when (screen.value) {\n Screen.User -> LoginScreen(user, accountService)\n else -> ProtectedScreen(user.value)\n }\n }\n }\n}\n```\n\nIn the `MainActivity` class, initialize the Appwrite service and display the `AppContent` based on the authentication state.\n\n```client-android-kotlin\n// ...imports \nIn the `MainActivity` class, initialize the Appwrite service.\nclass MainActivity : ComponentActivity() {\n override fun onCreate(savedInstanceState: Bundle?) {\n super.onCreate(savedInstanceState)\n\n Appwrite.init(applicationContext)\n\n setContent {\n // Update this line 👇\n AppContent(Appwrite.account)\n }\n }\n}\n```\n{% /tabsitem %}\n{% tabsitem #react-native title=\"React Native\" %}\nThis 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.\nYou can find a version of this example in the [Appwrite Android tutorial](/docs/tutorials/android/step-1).\n\nCreate a `UserContext` to manage the authentication state.\n```client-react-native\nimport { StyleSheet, Text, View } from 'react-native';\nimport { UserProvider } from './contexts/UserContext';\nimport { Router } from './lib/Router';\n\nexport default function App() {\n return (\n \n \n \n );\n}\n```\n\nThen, consume the `UserContext` in your `Router` component to check the authentication state before rendering a route.\n```client-react-native\nimport { NavigationContainer } from '@react-navigation/native';\nimport LoginScreen from '../views/Login';\nimport ProtectedSCreen from '../views/Protected';\nimport { createNativeStackNavigator } from '@react-navigation/native-stack';\nimport { useUser } from '../contexts/UserContext';\n\nconst Stack = createNativeStackNavigator();\nexport function Router() {\n const user = useUser();\n return (\n \n \n {user.current == null ? (\n \n ) : (\n \n )}\n\n \n \n );\n}\n```\n{% /tabsitem %}\n{% /tabs %}\n{% /accordion_item %}\n{% /accordion %}\n{% /section %}"}, {"path": "docs/products/auth/security", "title": "Security", "description": "Prioritize security in your applications with Appwrite. Discover best practices, security features, and guidelines to protect user data and ensure authentication integrity.", "content": "Appwrite provides many security features to keep both your Appwrite project and your user's information secure.\n\n{% partial file=\"auth-security.md\" /%}"}, {"path": "docs/products/auth/server-side-rendering", "title": "SSR login", "description": "How to implement SSR authentication with Appwrite", "content": "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.\n\nSSR 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.\n\nThis guide will walk you through the process of implementing an SSR application with Appwrite.\n\n# SSR authentication flow {% #ssr-auth-flow %}\n\nIn client-side rendered web apps, a [Client SDK](/docs/sdks#client) is used to perform authentication directly from the client's web browser.\n\nWith 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.\n\nHere's a high-level overview of the authentication flow:\n\n1. The user enters their credentials in their web browser.\n2. The browser sends the credentials to your server.\n3. Your server uses the Server SDK to authenticate the user with Appwrite.\n4. If the authentication is successful, your server sends a session cookie to the client's web browser.\n5. The client's web browser sends the session cookie to your server with subsequent request.\n6. Your server uses the session cookie to make authenticated requests to Appwrite on behalf of the client.\n\n{% only_dark %}\n![CSR vs SSR flow diagram](/images/docs/auth/ssr/dark/ssr.avif)\n{% /only_dark %}\n{% only_light %}\n![CSR vs SSR flow diagram](/images/docs/auth/ssr/ssr.avif)\n{% /only_light %}\n\n# Initialize clients {% #initialize-clients %}\n{% info title=\"Server SDK required\" %}\nServer-side rendering requires a [Server SDK](/docs/sdks#server) instead of a Client SDK.\n{% /info %}\nIn SSR, your server-side application will be making authentication requests to Appwrite and passing session cookies to your client-side app on the browser.\n\nWe'll need to initialize two Appwrite clients, one for admin requests and one for session-based requests.\n\n## Admin client {% #admin-client %}\n\n{% info title=\"Admin clients\" %}\nAdmin clients should only be used if you need to perform admin actions that bypass permissions\nor [unauthenticated requests that bypass rate limits](#rate-limits).\n{% /info %}\n\nTo initialize the admin client, we'll need to first [generate an API key](/docs/advanced/security/api-keys#create-api-key).\nThe API key should have the following scope in order to perform authentication:\n\n| Category {% width=120 %} | Required scopes | Purpose |\n|-----------|---------------------|---------|\n| Sessions | `sessions.write` | Allows API key to create, update, and delete sessions. |\n\n{% multicode %}\n```server-nodejs\nimport { Client } from \"node-appwrite\"; // Using the server SDK\n\nconst adminClient = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n .setKey(''); // Your secret API key\n```\n```php\nuse Appwrite\\Client;\nuse Appwrite\\Services\\Account;\n\n$adminClient = (new Client())\n ->setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n ->setProject('') // Your project ID\n ->setKey(''); // Your secret API key\n\n\n```\n```python\nfrom appwrite.client import Client\n\nadmin_client = (Client()\n .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint \\\n .set_project('') # Your project ID\n .set_key('') # Your secret API key\n )\n\n\n```\n```rust\nuse appwrite::Client;\n\nlet admin_client = Client::new()\n .set_endpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .set_project(\"\") // Your project ID\n .set_key(\"\"); // Your secret API key\n```\n{% /multicode %}\n\nIt 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.\n\n## Session client {% #session-client %}\n\nThe session client will be used to make requests to Appwrite on behalf of the end-user.\nIt will be initialized with the session, usually stored within a cookie.\n\nYou should create a new client for each request and **never** share the client between requests.\n\nUse `a_session_` 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.\n\n{% multicode %}\n```server-nodejs\nconst sessionClient = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject(''); // Your project ID\n\nconst session = req.cookies['a_session_']; // Get the session cookie from the request\nif (session) {\n sessionClient.setSession(session);\n}\n```\n```php\n$sessionClient = (new Client())\n ->setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n ->setProject(''); // Your project ID\n\n$session = $_COOKIE['a_session_']; // Get the session cookie from the request\nif ($session) {\n $sessionClient->setSession($session);\n}\n```\n\n```python\nfrom flask import request\nfrom appwrite.client import Client\n\nsession_client = (Client()\n .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('') # Your project ID\n )\n\n# Get the session cookie from the request\nsession = request.cookies.get('a_session_')\nif session:\n session_client.set_session(session)\n\n```\n```rust\nuse appwrite::Client;\n\nlet session_client = Client::new()\n .set_endpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .set_project(\"\"); // Your project ID\n\nlet session = req.cookie(\"a_session_\"); // Get the session cookie from the request\nif let Some(session) = session {\n session_client.set_session(session);\n}\n```\n{% /multicode %}\n\n# Creating email/password sessions {% #creating-sessions %}\n\nThe most straightforward type of session to integrate is email/password.\n\nCreate 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.\nOnce 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.\n\nUse 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.\nHere's an example with Express and PHP, but the same concepts apply to most frameworks.\n{% multicode %}\n```server-nodejs\nimport express from 'express';\n\n// Initialize admin client here\n// ...\n\napp.post('/login', async (req, res) => {\n // Get email and password from request\n const { email, password } = req.body;\n\n const account = new Account(adminClient);\n\n try {\n // Create the session using the Appwrite client\n const session = await account.createEmailPasswordSession({\n email,\n password\n });\n\n // Set the session cookie\n res.cookie('a_session_', session.secret, { // use the session secret as the cookie value\n httpOnly: true,\n secure: true,\n sameSite: 'strict',\n expires: new Date(session.expire),\n path: '/',\n });\n\n res.status(200).json({ success: true });\n } catch (e) {\n res.status(400).json({ success: false, error: e.message });\n }\n});\n```\n\n```php\ncreateEmailPasswordSession($email, $password);\n\n // Set the session cookie\n setcookie('a_session_', $session['secret'], [\n 'httpOnly' => true,\n 'secure' => true,\n 'sameSite' => 'strict',\n 'expires' => strtotime($session['expire']),\n 'path' => '/',\n ]);\n\n echo json_encode(['success' => true]);\n} catch (Exception $e) {\n echo json_encode(['success' => false, 'error' => $e->getMessage()]);\n}\n```\n```python\nfrom flask import Flask, request, jsonify, make_response\n\n# Initialize admin client here\n# ...\n\n@app.post('/login')\ndef login():\n body = request.json\n # Get email and password from request\n email = body['email']\n password = body['password']\n\n try:\n account = Account(admin_client)\n\n # Create the session using the Appwrite client\n session = account.create_email_password_session(email, password)\n resp = make_response(jsonify({'success': True}))\n\n # Set the session cookie\n resp.set_cookie('a_session_',\n session['secret'],\n httponly=True,\n secure=True,\n samesite='Strict',\n expires=session['expire'],\n path='/'\n )\n return resp\n except Exception as e:\n return jsonify({'success': False, 'error': str(e)}), 400\n```\n```rust\nuse appwrite::Client;\nuse appwrite::services::account::Account;\n\n// Initialize admin client here\n// ...\n\nasync fn login(admin_client: &Client, email: &str, password: &str) -> Result<(), Box> {\n let account = Account::new(admin_client);\n\n // Create the session using the Appwrite client\n let session = account.create_email_password_session(\n email,\n password,\n ).await?;\n\n // Set the session cookie using the session secret\n // Use your framework's cookie API to set:\n // name: \"a_session_\"\n // value: session.secret\n // httpOnly: true, secure: true, sameSite: strict\n // expires: session.expire\n\n Ok(())\n}\n```\n{% /multicode %}\n\nWe also recommend using the `httpOnly`, `secure`, and `sameSite` cookie options to ensure that the cookie is only sent over HTTPS,\nand is not accessible to JavaScript. This will prevent XSS attacks.\n\n# Making authenticated requests {% #making-authenticated-requests %}\n\nOnce 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.\n\nTo 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.\nWhen the browser makes a request to your domain's endpoints, it will automatically include session cookies.\n\n{% multicode %}\n\n```server-nodejs\n// Initialize the session client here\n\napp.get('/user', async (req, res) => {\n // First, read the session cookie from the request\n const session = req.cookies['a_session_'];\n\n // If the session cookie is not present, return an error\n if (!session) {\n return res.status(401).json({ success: false, error: 'Unauthorized' });\n }\n\n // Pass the session cookie to the Appwrite client\n sessionClient.setSession(session);\n\n // Now, you can make authenticated requests to the Appwrite API\n const account = new Account(sessionClient);\n try {\n const user = await account.get();\n\n res.status(200).json({ success: true, user });\n } catch (e) {\n res.status(400).json({ success: false, error: e.message });\n }\n});\n```\n```php\n'];\n\n// If the session cookie is not present, return an error\nif (!$session) {\n return http_response_code(401);\n}\n\n// Pass the session cookie to the Appwrite client\n$sessionClient->setSession($session);\n$account = new Account($sessionClient);\n\n// Now, you can make authenticated requests to the Appwrite API\ntry {\n $user = $account->get();\n\n echo json_encode(['success' => true, 'user' => $user]);\n} catch (Exception $e) {\n echo json_encode(['success' => false, 'error' => $e->getMessage()]);\n}\n```\n```python\n# Initialize the session client here\n\n@app.get('/user')\ndef get_user():\n # First, read the session cookie from the request\n session = request.cookies.get('a_session_')\n\n # If the session cookie is not present, return an error\n if not session:\n return jsonify({'success': False, 'error': 'Unauthorized'}), 401\n\n # pass the session cookie to the Appwrite client\n session_client.set_session(session)\n account = Account(session_client)\n\n # Now, you can make authenticated requests to the Appwrite API\n try:\n user = account.get()\n return jsonify({'success': True, 'user': user})\n except Exception as e:\n return jsonify({'success': False, 'error': str(e)}), 400\n\n```\n```rust\nuse appwrite::Client;\nuse appwrite::services::account::Account;\n\n// Initialize the session client here\n\n// First, read the session cookie from the request\n// This depends on your HTTP framework (e.g. actix-web, axum, rocket)\nlet session = req.cookie(\"a_session_\");\n\n// If the session cookie is not present, return an error\nif session.is_none() {\n // return 401 Unauthorized\n}\n\n// Pass the session cookie to the Appwrite client\nlet session_client = Client::new()\n .set_endpoint(\"https://.cloud.appwrite.io/v1\")\n .set_project(\"\");\nsession_client.set_session(session.unwrap());\n\n// Now, you can make authenticated requests to the Appwrite API\nlet account = Account::new(&session_client);\nlet user = account.get().await?;\n```\n{% /multicode %}\n\n# Rate limits {% #rate-limits %}\nUnauthenticated requests are subject to [rate limits](/docs/advanced/security/rate-limits).\nNormally, rate limits are applied by an abuse key, which is usually a combination of IP and another factor like user ID.\nWhen you make unauthenticated requests from your server, however, all requests originate from the same IP and no user ID is provided.\nThis means that all unauthenticated requests from your server will be **subject to the same rate limits**.\n\nThese rate limits protect your Appwrite server from abuse, if you need to make unauthenticated requests from your server,\nthere are ways to bypass rate limits.\n\n# Making unauthenticated requests {% #making-unauthenticated-requests %}\nUnauthenticated requests are used for displaying information to users before they log in.\nFor example some apps may display all public posts on the home page, and only show private posts to logged-in users.\n\nThere are two ways to make unauthenticated requests:\n\n{% table %}\n* Guest sessions\n* Admin clients\n---\n* Uses the `createAnonymousSession` method to create a guest session.\n* Uses an API key to bypass rate limits.\n---\n* Creates a session for unauthenticated users so each user has their own rate limit.\n* Bypasses rate limits completely because API keys are not limited.\n---\n* Still respects access permissions.\n* Also bypasses access permissions.\n---\n* Can be turned into a full session later by creating an account.\n* Cannot be later turned into a full session.\n{% /table %}\n\nYou can create a guest session using the `createAnonymousSession` method.\nThis will create a session for unauthenticated users, and each user will have their own rate limit.\n\n{% multicode %}\n```server-nodejs\nconst sdk = require('node-appwrite');\n\n// Init SDK\nconst client = new sdk.Client();\n\nconst account = new sdk.Account(client);\n\nclient\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n;\n\nconst promise = account.createAnonymousSession();\n\npromise.then(function (response) {\n console.log(response);\n}, function (error) {\n console.log(error);\n});\n```\n```php\nsetEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n ->setProject('') // Your project ID\n;\n\n$account = new Account($client);\n\n$result = $account->createAnonymousSession();\n```\n```python\nfrom appwrite.client import Client\nfrom appwrite.services.account import Account\n\nclient = (Client()\n .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('') # Your project ID\n )\n\naccount = Account(client)\n\nresult = account.create_anonymous_session()\n```\n```rust\nuse appwrite::Client;\nuse appwrite::services::account::Account;\n\nlet client = Client::new()\n .set_endpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .set_project(\"\"); // Your project ID\n\nlet account = Account::new(&client);\n\nlet result = account.create_anonymous_session().await?;\n```\n{% /multicode %}\n\n# Forwarding user agent {% #forwarding-user-agent %}\n\nAppwrite 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.\n\n{% multicode %}\n```server-nodejs\nclient.setForwardedUserAgent(req.headers['user-agent']);\n```\n```php\nsetForwardedUserAgent($_SERVER['HTTP_USER_AGENT']);\n```\n```python\nclient.set_forwarded_user_agent(request.headers.get('user-agent'))\n```\n```rust\n// This depends on your HTTP framework (e.g. actix-web, axum, rocket)\nlet user_agent = req.header(\"user-agent\");\nclient.add_header(\"x-forwarded-user-agent\", user_agent);\n```\n{% /multicode %}\n\n# OAuth2 {% #oauth2 %}\n\nServer-side OAuth2 authentication requires two server endpoints:\n\nCreate 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.\n\n{% multicode %}\n```server-nodejs\nimport { Client, Account, OAuthProvider } from \"node-appwrite\"; // Using the server SDK\n\nconst adminClient = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n .setKey(''); // Your secret API key\n\napp.get('/oauth', async (req, res) => {\n const account = new Account(adminClient);\n\n const redirectUrl = await account.createOAuth2Token({\n provider: OAuthProvider.Github, // Provider\n success: 'https://example.com/oauth/success', // Success URL\n failure: 'https://example.com/oauth/failure', // Failure URL\n });\n\n res.redirect(redirectUrl);\n});\n```\n```php\nsetEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n ->setProject('') // Your project ID\n ->setKey(''); // Your secret API key\n\n$account = new Account($adminClient);\n\n$redirectUrl = $account->createOAuth2Token(\n OAuthProvider::GITHUB(),\n 'https://example.com/oauth/success', // Success URL\n 'https://example.com/oauth/failure', // Failure URL\n);\n\nheader('Location' . $redirectUrl);\n```\n```python\nfrom appwrite.client import Client\nfrom appwrite.services.account import Account, OAuthProvider\nfrom flask import Flask, request ,redirect, make_response, jsonify\n\nadmin_client = (Client()\n .set_endpoint('https://.cloud.appwrite.io/v1')\n .set_project('')\n .set_key('')\n )\n\n@app.get('/oauth')\ndef oauth():\n account = Account(admin_client)\n\n redirect_url = account.create_o_auth2_token(\n OAuthProvider.Github, # Provider\n 'https://example.com/oauth/success', # Success URL\n 'https://example.com/oauth/failure', # Failure URL\n )\n\n return redirect(redirect_url)\n```\n```rust\nuse appwrite::Client;\nuse appwrite::services::account::Account;\nuse appwrite::enums::OAuthProvider;\n\nlet admin_client = Client::new()\n .set_endpoint(\"https://.cloud.appwrite.io/v1\")\n .set_project(\"\")\n .set_key(\"\");\n\nlet account = Account::new(&admin_client);\n\nlet redirect_url = account.create_o_auth2_token(\n OAuthProvider::Github, // Provider\n Some(\"https://example.com/oauth/success\"), // Success URL\n Some(\"https://example.com/oauth/failure\"), // Failure URL\n None, // Scopes\n).await?;\n\n// Redirect the user to redirect_url\n```\n{% /multicode %}\n\nNext, 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.\n\n{% multicode %}\n```server-nodejs\napp.get('/oauth/success', async (req, res) => {\n const account = new Account(adminClient);\n\n // Get the userId and secret from the URL parameters\n const { userId, secret } = req.query;\n\n try {\n // Create the session using the Appwrite client\n const session = await account.createSession({\n userId,\n secret\n });\n\n // Set the session cookie\n res.cookie('a_session_', session.secret, { // Use the session secret as the cookie value\n httpOnly: true,\n secure: true,\n sameSite: 'strict',\n maxAge: sesion.expire\n path: '/',\n });\n\n res.status(200).json({ success: true });\n } catch (e) {\n res.status(400).json({ success: false, error: e.message });\n }\n});\n```\n```php\nsetEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n ->setProject('') // Your project ID\n ->setKey(''); // Your secret API key\n\n$account = new Account($adminClient);\n\n// Get the userId and secret from the URL parameters\n$userId = $_GET['userId'];\n$secret = $_GET['secret'];\n\ntry {\n // Create the session using the Appwrite client\n $session = $account->createSession($userId, $secret);\n\n // Set the session cookie\n setcookie('a_session_', $session['secret'], [\n 'httpOnly' => true,\n 'secure' => true,\n 'sameSite' => 'strict',\n 'expires' => strtotime($session['expire']),\n 'path' => '/',\n ]);\n\n echo json_encode(['success' => true]);\n} catch (Exception $e) {\n echo json_encode(['success' => false, 'error' => $e->getMessage()]);\n}\n```\n```python\n@app.get('/oauth/success')\ndef oauth_success():\n account = Account(admin_client)\n\n # Get the userId and secret from the URL parameters\n user_id = request.args.get('userId')\n secret = request.args.get('secret')\n\n try:\n # Create the session using the Appwrite client\n session = account.create_session(user_id, secret)\n\n # Set the session cookie\n res = make_response(jsonify({'success': True}))\n\n # Set session cookie\n res.set_cookie(\n 'a_session_',\n session['secret'],\n httponly=True,\n secure=True,\n samesite='Strict',\n max_age=session['expire'],\n path='/'\n )\n\n return res\n\n except Exception as e:\n return jsonify({'success': False, 'error': str(e)}), 400\n```\n```rust\nuse appwrite::Client;\nuse appwrite::services::account::Account;\n\nlet admin_client = Client::new()\n .set_endpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .set_project(\"\") // Your project ID\n .set_key(\"\"); // Your secret API key\n\nlet account = Account::new(&admin_client);\n\n// Get the userId and secret from the URL parameters\n// This depends on your HTTP framework (e.g. actix-web, axum, rocket)\nlet user_id = req.query(\"userId\");\nlet secret = req.query(\"secret\");\n\n// Create the session using the Appwrite client\nlet session = account.create_session(\n &user_id,\n &secret,\n).await?;\n\n// Set the session cookie using the session secret\n// Use your framework's cookie API to set:\n// name: \"a_session_\"\n// value: session.secret\n// httpOnly: true, secure: true, sameSite: strict\n// expires: session.expire\n```\n{% /multicode %}\n\nNow 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.\n\n# Tutorials {% #tutorials %}\nIf you'd like to see SSR authentication implemented in a full auth example, see these tutorials.\n\n{% cards %}\n{% cards_item href=\"/docs/tutorials/nextjs-ssr-auth\" title=\"Next.js SSR\" icon=\"icon-nextjs\"%}\n{% /cards_item %}\n{% cards_item href=\"/docs/tutorials/sveltekit-ssr-auth\" title=\"SvelteKit SSR\" icon=\"icon-svelte\" %}\n{% /cards_item %}\n{% cards_item href=\"/docs/tutorials/nuxt-ssr-auth\" title=\"Nuxt SSR\" icon=\"icon-nuxt\" %}\n{% /cards_item %}\n{% cards_item href=\"/docs/tutorials/astro-ssr-auth\" title=\"Astro SSR\" icon=\"icon-astro\" %}\n{% /cards_item %}\n{% /cards %}"}, {"path": "docs/products/auth/team-invites", "title": "Team invites", "description": "Learn how to manage team invites in Appwrite. Implement both client-side email invites and server-side custom flows for team memberships.", "content": "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.\n\n# Invite client-side\n\nClient-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:\n1. Creates a new user account if one doesn't exist for the email address\n2. Sends an automated email invitation to the user\n3. Creates a pending membership\n4. Activates the membership when the user accepts\n\nClient-side invites are ideal when you want a simple, automated process that lets your users manage their own team invitations.\nAppwrite handles the email delivery with built-in templates and localization support, making it easy to implement a standard invite acceptance flow with email verification.\n\n{% multicode %}\n```client-web\nimport { Client, Teams } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://cloud.appwrite.io/v1')\n .setProject('');\n\nconst teams = new Teams(client);\n\n// Create membership with email invite\nconst membership = await teams.createMembership(\n '',\n ['developer'], // roles\n 'user@example.com', // email\n undefined, // userId (optional)\n undefined, // phone (optional)\n 'https://yourapp.com/accept-invite' // url - redirect after email click\n);\n```\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://cloud.appwrite.io/v1')\n .setProject('');\n\nfinal teams = Teams(client);\n\n// Create membership with email invite\nfinal membership = await teams.createMembership(\n teamId: '',\n roles: ['developer'],\n email: 'user@example.com',\n url: 'https://yourapp.com/accept-invite' // redirect after email click\n);\n```\n```client-apple\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n\nlet teams = Teams(client)\n\n// Create membership with email invite\nlet membership = try await teams.createMembership(\n teamId: \"\",\n roles: [\"developer\"],\n email: \"user@example.com\",\n url: \"https://yourapp.com/accept-invite\" // redirect after email click\n)\n```\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Teams\n\nval client = Client(context)\n .setEndpoint(\"https://cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n\nval teams = Teams(client)\n\n// Create membership with email invite\nval response = teams.createMembership(\n teamId = \"\",\n roles = listOf(\"developer\"),\n email = \"user@example.com\",\n url = \"https://yourapp.com/accept-invite\" // redirect after email click\n)\n```\n{% /multicode %}\n\n## Accept invitations\n\nFor client-side email invites, users must accept the invitation to join the team. The acceptance flow:\n1. User receives an email with an invitation link containing a secret token\n2. Clicking the link redirects to your app\n3. Your app calls the acceptance endpoint\n4. Upon success, the user gains immediate access\n\n{% multicode %}\n```client-web\nimport { Client, Teams } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://cloud.appwrite.io/v1')\n .setProject('');\n\nconst teams = new Teams(client);\n\n// Accept the invitation using the membership ID and secret\nconst response = await teams.updateMembershipStatus(\n '',\n '',\n '',\n ''\n);\n```\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://cloud.appwrite.io/v1')\n .setProject('');\n\nfinal teams = Teams(client);\n\n// Accept the invitation using the membership ID and secret\nfinal response = await teams.updateMembershipStatus(\n teamId: '',\n membershipId: '',\n userId: '',\n secret: ''\n);\n```\n```client-apple\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nlet teams = Teams(client)\n\n// Accept the invitation using the membership ID and secret\nlet response = try await teams.updateMembershipStatus(\n teamId: \"\",\n membershipId: \"\",\n userId: \"\",\n secret: \"\"\n)\n```\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Teams\n\nval client = Client(context)\n .setEndpoint(\"https://cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nval teams = Teams(client)\n\n// Accept the invitation using the membership ID and secret\nval response = teams.updateMembershipStatus(\n teamId = \"\",\n membershipId = \"\",\n userId = \"\",\n secret = \"\"\n)\n```\n{% /multicode %}\n\n# Server-side custom flows\n\nServer-side membership creation bypasses the email invitation process, allowing direct member addition. This approach:\n1. Creates an active membership immediately\n2. Doesn't require user acceptance\n3. Gives you complete control over the invitation workflow\n\nThis makes them perfect for scenarios requiring custom workflows, such as bulk user management, automated team assignments, or integration with external systems.\nSince memberships are created directly, users gain immediate access without waiting for email acceptance.\n\n{% multicode %}\n```server-nodejs\nconst sdk = require('node-appwrite');\n\nconst client = new sdk.Client()\n .setEndpoint('https://cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nconst teams = new sdk.Teams(client);\n\n// Create membership directly with userId\nconst membership = await teams.createMembership(\n '',\n ['developer'],\n '',\n 'John Doe' // optional name\n);\n```\n```server-python\nfrom appwrite.client import Client\nfrom appwrite.services.teams import Teams\n\nclient = Client()\nclient.set_endpoint('https://cloud.appwrite.io/v1')\nclient.set_project('')\nclient.set_key('')\n\nteams = Teams(client)\n\n# Create membership directly with userId\nmembership = teams.create_membership(\n team_id='',\n roles=['developer'],\n user_id='',\n name='John Doe' # optional\n)\n```\n```server-swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n .setKey(\"\") // Your secret API key\n\nlet teams = Teams(client)\n\n// Create membership directly with userId\nlet membership = try await teams.createMembership(\n teamId: \"\",\n roles: [\"developer\"],\n userId: \"\",\n name: \"John Doe\" // optional\n)\n```\n```server-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Teams\n\nval client = Client()\n .setEndpoint(\"https://cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n .setKey(\"\") // Your secret API key\n\nval teams = Teams(client)\n\n// Create membership directly with userId\nval response = teams.createMembership(\n teamId = \"\",\n roles = listOf(\"developer\"),\n userId = \"\",\n name = \"John Doe\" // optional\n)\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::teams::Teams;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box> {\n let client = Client::new()\n .set_endpoint(\"https://cloud.appwrite.io/v1\")\n .set_project(\"\")\n .set_key(\"\");\n\n let teams = Teams::new(&client);\n\n // Create membership directly with userId\n let membership = teams.create_membership(\n \"\", // teamId\n vec![\"developer\"], // roles\n None, // email (optional)\n Some(\"\"), // userId (optional)\n None, // phone (optional)\n None, // url (optional)\n Some(\"John Doe\"), // name (optional)\n ).await?;\n\n println!(\"{:?}\", membership);\n Ok(())\n}\n```\n{% /multicode %}\n\n# Manage memberships\n\nOnce team memberships are created, you'll need to manage their lifecycle. This includes checking status, updating roles, and removing members when necessary.\n\n## Check membership status\n\nBefore 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.\n\n### Client-side\nTo check membership status client-side, first list the teams and then get the memberships for a specific team:\n\n{% multicode %}\n```client-web\nimport { Client, Teams } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://cloud.appwrite.io/v1')\n .setProject('');\n\nconst teams = new Teams(client);\n\n// Get list of teams the user is part of\nconst teamsList = await teams.list();\n\n// For a specific team, get all memberships\nconst response = await teams.listMemberships({\n teamId: ''\n});\n\n// Find membership for specific user\nconst userMembership = response.memberships.find(\n membership => membership.userId === ''\n);\n\nif (userMembership) {\n console.log(userMembership.confirm); // false = invited, true = joined\n console.log(userMembership.roles); // ['developer', etc]\n}\n```\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://cloud.appwrite.io/v1')\n .setProject('');\n\nfinal teams = Teams(client);\n\n// Get list of teams the user is part of\nfinal teamsList = await teams.list();\n\n// For a specific team, get all memberships\nfinal response = await teams.listMemberships(\n teamId: ''\n);\n\n// Find membership for specific user\nfinal userMembership = response.memberships.firstWhere(\n (membership) => membership.userId == '',\n orElse: () => null\n);\n\nif (userMembership != null) {\n print(userMembership.confirm); // false = invited, true = joined\n print(userMembership.roles); // ['developer', etc]\n}\n```\n```client-apple\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nlet teams = Teams(client)\n\n// Get list of teams the user is part of\nlet teamsList = try await teams.list()\n\n// For a specific team, get all memberships\nlet response = try await teams.listMemberships(\n teamId: \"\"\n)\n\n// Find membership for specific user\nif let userMembership = response.memberships.first(where: { $0.userId == \"\" }) {\n print(userMembership.confirm) // false = invited, true = joined\n print(userMembership.roles) // ['developer', etc]\n}\n```\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Teams\n\nval client = Client(context)\n .setEndpoint(\"https://cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nval teams = Teams(client)\n\n// Get list of teams the user is part of\nval teamsList = teams.list()\n\n// For a specific team, get all memberships\nval response = teams.listMemberships(\n teamId = \"team_id\"\n)\n\n// Find membership for specific user\nval userMembership = response.memberships.find {\n it.userId == \"\"\n}\n\nuserMembership?.let {\n println(it.confirm) // false = invited, true = joined\n println(it.roles) // ['developer', etc]\n}\n```\n{% /multicode %}\n\n### Server-side\nServer-side requires iterating through teams and memberships since the data isn't filtered for a specific user:\n\n{% multicode %}\n```server-nodejs\nconst sdk = require('node-appwrite');\n\nconst client = new sdk.Client()\n .setEndpoint('https://cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nconst teams = new sdk.Teams(client);\n\n// Get all teams\nconst teamsList = await teams.list();\n\n// Iterate through teams to find memberships\nfor (const team of teamsList.teams) {\n const response = await teams.listMemberships({\n teamId: team.$id\n });\n\n // Find membership for specific user\n const userMembership = response.memberships.find(\n membership => membership.userId === ''\n );\n\n if (userMembership) {\n console.log(`Team: ${team.name}`);\n console.log(`Joined: ${userMembership.joined}`); // null if invited, timestamp if joined\n console.log(`Roles: ${userMembership.roles}`);\n }\n}\n```\n```server-python\nfrom appwrite.client import Client\nfrom appwrite.services.teams import Teams\n\nclient = Client()\nclient.set_endpoint('https://cloud.appwrite.io/v1')\nclient.set_project('')\nclient.set_key('')\n\nteams = Teams(client)\n\n// Get all teams\nteams_list = teams.list()\n\n// Iterate through teams to find memberships\nfor team in teams_list['teams']:\n response = teams.list_memberships(team['$id'])\n\n // Find membership for specific user\n user_membership = next(\n (m for m in response['memberships'] if m['userId'] == ''),\n None\n )\n\n if user_membership:\n print(f\"Team: {team['name']}\")\n print(f\"Joined: {user_membership['joined']}\") # null if invited, timestamp if joined\n print(f\"Roles: {user_membership['roles']}\")\n```\n```server-swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\")\n\nlet teams = Teams(client)\n\n// Get all teams\nlet teamsList = try await teams.list()\n\n// Iterate through teams to find memberships\nfor team in teamsList.teams {\n let response = try await teams.listMemberships(\n teamId: team.$id\n )\n\n // Find membership for specific user\n if let userMembership = response.memberships.first(where: { $0.userId == \"\" }) {\n print(\"Team: \\(team.name)\")\n print(\"Joined: \\(userMembership.joined)\") # null if invited, timestamp if joined\n print(\"Roles: \\(userMembership.roles)\")\n }\n}\n```\n```server-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Teams\n\nval client = Client()\n .setEndpoint(\"https://cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\")\n\nval teams = Teams(client)\n\n// Get all teams\nval teamsList = teams.list()\n\n// Iterate through teams to find memberships\nteamsList.teams.forEach { team ->\n val response = teams.listMemberships(teamId = team.$id)\n\n // Find membership for specific user\n val userMembership = response.memberships.find {\n it.userId == \"\"\n }\n\n userMembership?.let {\n println(\"Team: ${team.name}\")\n println(\"Joined: ${it.joined}\") # null if invited, timestamp if joined\n println(\"Roles: ${it.roles}\")\n }\n}\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::teams::Teams;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box> {\n let client = Client::new()\n .set_endpoint(\"https://cloud.appwrite.io/v1\")\n .set_project(\"\")\n .set_key(\"\");\n\n let teams = Teams::new(&client);\n\n // Get all teams\n let teams_list = teams.list(\n None, // queries (optional)\n None, // search (optional)\n None, // total (optional)\n ).await?;\n\n // Iterate through teams to find memberships\n for team in &teams_list.teams {\n let response = teams.list_memberships(\n &team.id, // teamId\n None, // queries (optional)\n None, // search (optional)\n None, // total (optional)\n ).await?;\n\n // Find membership for specific user\n if let Some(membership) = response.memberships.iter().find(|m| m.user_id == \"\") {\n println!(\"Team: {}\", team.name);\n println!(\"Joined: {:?}\", membership.joined); // None if invited, timestamp if joined\n println!(\"Roles: {:?}\", membership.roles);\n }\n }\n\n Ok(())\n}\n```\n{% /multicode %}\n\n## Remove members\n\nTeam owners can remove members or users can leave teams:\n\n{% multicode %}\n```client-web\nimport { Client, Teams } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://cloud.appwrite.io/v1')\n .setProject('');\n\nconst teams = new Teams(client);\n\nawait teams.deleteMembership(\n '',\n ''\n);\n```\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://cloud.appwrite.io/v1')\n .setProject('');\n\nfinal teams = Teams(client);\n\nawait teams.deleteMembership(\n teamId: '',\n membershipId: ''\n);\n```\n```client-apple\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nlet teams = Teams(client)\n\ntry await teams.deleteMembership(\n teamId: \"\",\n membershipId: \"\"\n)\n```\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Teams\n\nval client = Client(context)\n .setEndpoint(\"https://cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nval teams = Teams(client)\n\nteams.deleteMembership(\n teamId = \"\",\n membershipId = \"\"\n)\n```\n{% /multicode %}\n\n# Manage team permissions\n\nTeams 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.\n\n## Update roles\n\nYou 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:\n\n{% multicode %}\n```client-web\nimport { Client, Teams } from \"appwrite\"\n\nconst client = new Client()\n .setEndpoint('https://cloud.appwrite.io/v1')\n .setProject('')\n\nconst teams = new Teams(client)\n\n// Update member roles\nawait teams.updateMembership(\n '',\n '',\n ['admin', 'developer']\n)\n```\n```client-flutter\nimport 'package:appwrite/appwrite.dart'\n\nfinal client = Client()\n .setEndpoint('https://cloud.appwrite.io/v1')\n .setProject('')\n\nfinal teams = Teams(client)\n\n// Update member roles\nawait teams.updateMembership(\n teamId: '',\n membershipId: '',\n roles: ['admin', 'developer']\n)\n```\n```client-apple\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nlet teams = Teams(client)\n\n// Update member roles\ntry await teams.updateMembership(\n teamId: \"\",\n membershipId: \"\",\n roles: [\"admin\", \"developer\"]\n)\n```\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Teams\n\nval client = Client(context)\n .setEndpoint(\"https://cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nval teams = Teams(client)\n\n// Update member roles\nteams.updateMembership(\n teamId = \"\",\n membershipId = \"\",\n roles = listOf(\"admin\", \"developer\")\n)\n```\n{% /multicode %}\n\n## Check role access\n\nYou can verify if a user has specific roles:\n\n{% multicode %}\n```client-web\nimport { Client, Teams } from \"appwrite\"\n\nconst client = new Client()\n .setEndpoint('https://cloud.appwrite.io/v1')\n .setProject('')\n\nconst teams = new Teams(client)\n\n// Get team memberships\nconst response = await teams.listMemberships({\n teamId: ''\n});\n\n// Check if user has specific role\nconst membership = response.memberships.find(m => m.userId === '')\nconst isAdmin = membership?.roles.includes('admin') ?? false\n```\n```client-flutter\nimport 'package:appwrite/appwrite.dart'\n\nfinal client = Client()\n .setEndpoint('https://cloud.appwrite.io/v1')\n .setProject('')\n\nfinal teams = Teams(client)\n\n// Get team memberships\nfinal response = await teams.listMemberships(\n teamId: ''\n)\n\n// Check if user has specific role\nfinal membership = response.memberships.firstWhere(\n (m) => m.userId == '',\n orElse: () => null\n)\nfinal isAdmin = membership?.roles.contains('admin') ?? false\n```\n```client-apple\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nlet teams = Teams(client)\n\n// Get team memberships\nlet response = try await teams.listMemberships(\n teamId: \"\"\n)\n\n// Check if user has specific role\nlet membership = response.memberships.first { $0.userId == \"\" }\nlet isAdmin = membership?.roles.contains(\"admin\") ?? false\n```\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Teams\n\nval client = Client(context)\n .setEndpoint(\"https://cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nval teams = Teams(client)\n\n// Get team memberships\nval response = teams.listMemberships(\n teamId = \"\"\n)\n\n// Check if user has specific role\nval membership = response.memberships.find { it.userId == \"\" }\nval isAdmin = membership?.roles?.contains(\"admin\") ?: false\n```\n{% /multicode %}\n\nSee how to grant row and file access to team roles in the [permissions](/docs/advanced/security/permissions#example-2-team-roles) guide.\n\n{% arrow_link href=\"/docs/products/auth/teams\" %}\nLearn more about team management\n{% /arrow_link %}"}, {"path": "docs/products/auth/teams", "title": "Teams", "description": "Master team management in the Appwrite Cloud. Explore team-related functions, permissions, and more.", "content": "Teams are a good way to allow users to share access to resources.\nFor 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.\nYou can further give special rights to parts of a team using team roles.\n\nThe 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.\n\n{% arrow_link href=\"/docs/products/auth/multi-tenancy\" %}\nLearn about using Teams for multi-tenancy\n{% /arrow_link %}\n\n# Create team {% #create %}\nFor example, we can create a team called `teachers` with roles `maths`, `sciences`, `arts`, and `literature`.\n\nThe creator of the team is also granted the `owner` role. **Only those with the `owner` role can invite and remove members**.\n\n{% multicode %}\n```client-web\nimport { Client, Teams } from \"appwrite\";\n\nconst client = new Client();\n\nconst teams = new Teams(client);\n\nclient\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n;\n\nconst promise = teams.create(\n 'teachers',\n 'Teachers',\n ['maths', 'sciences', 'arts', 'literature']\n);\n\npromise.then(function (response) {\n console.log(response); // Success\n}, function (error) {\n console.log(error); // Failure\n});\n```\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nvoid main() { // Init SDK\n Client client = Client();\n Teams teams = Teams(client);\n\n client\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n ;\n Future result = teams.create(\n teamId: 'teachers',\n name: 'Teachers',\n roles: ['maths', 'sciences', 'arts', 'literature']\n );\n\n result\n .then((response) {\n print(response);\n }).catchError((error) {\n print(error.response);\n });\n}\n```\n```client-apple\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n\nlet teams = Teams(client)\n\nlet team = try await teams.create(\n teamId: \"teachers\",\n name: \"Teachers\",\n roles: [\"maths\", \"sciences\", \"arts\", \"literature\"]\n)\n```\n```kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Teams\n\nval client = Client(context)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n\nval teams = Teams(client)\n\nval response = teams.create(\n teamId = \"teachers\",\n name = \"Teachers\",\n roles = listOf(\"maths\", \"sciences\", \"arts\", \"literature\")\n)\n```\n\n{% /multicode %}\n\n# Invite a member {% #create-membership %}\n\nYou can invite members to a team by creating team memberships. For example, inviting \"David\" a math teacher, to the teachers team.\n\n{% multicode %}\n```client-web\nimport { Client, Teams } from \"appwrite\";\n\nconst client = new Client();\n\nconst teams = new Teams(client);\n\nclient\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n;\n\nconst promise = teams.createMembership(\n 'teachers',\n [\"maths\"],\n \"david@example.com\"\n );\n\npromise.then(function (response) {\n console.log(response); // Success\n}, function (error) {\n console.log(error); // Failure\n});\n```\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nvoid main() { // Init SDK\n Client client = Client();\n Teams teams = Teams(client);\n\n client\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n ;\n Future result = teams.createMembership(\n teamId: 'teachers',\n roles: ['maths'],\n email: 'david@example.com'\n );\n\n result\n .then((response) {\n print(response);\n }).catchError((error) {\n print(error.response);\n });\n}\n```\n```client-apple\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n\nlet teams = Teams(client)\n\nlet membership = try await teams.createMembership(\n teamId: \"teachers\",\n roles: [\"maths\"],\n email: \"david@example.com\"\n)\n```\n```kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Teams\n\nval client = Client(context)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n\nval teams = Teams(client)\n\nval response = teams.createMembership(\n teamId = \"teachers\",\n roles = listOf(\"maths\"),\n email = \"david@example.com\"\n)\n```\n\n{% /multicode %}\n\n# Using the CLI {% #using-the-CLI %}\n\n{% partial file=\"cli-disclaimer.md\" /%}\n\nUse the CLI command `appwrite teams create-membership [options]` to invite a new member into your team.\n\n```sh\nappwrite teams create-membership --team-id \"\" --roles --phone \"+12065550100\" --name \"\" --user-id \"\"\n```\n\nYou can also get, update, and delete a user's membership. However, you cannot use the CLI to configure permissions for team members.\n\n{% arrow_link href=\"/docs/tooling/command-line/teams#commands\" %}\nLearn more about the CLI teams commands\n{% /arrow_link %}\n\n# Permissions {% #permissions %}\n\nYou can grant permissions to all members of a team using the `Role.team()` role or\nindividual roles in the team using the `Role.team(, [, , ...])` role.\n| Description | Role |\n| ------------------------------------------- | ------------------------------------------- |\n| All members | `Role.team()`|\n| Select roles | `Role.team(, [, , ...])`|\n\n\n{% arrow_link href=\"/docs/advanced/security/permissions\" %}\nLearn more about permissions\n{% /arrow_link %}\n\n# Memberships privacy {% #memberships-privacy %}\n\nIn 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**\n\nThese details can be made private:\n\n- `userName` - The member's name\n- `userEmail` - The member's email address\n- `mfa` - Whether the member has enabled multi-factor authentication"}, {"path": "docs/products/auth/tokens", "title": "Tokens", "description": "What are tokens and how to use them in Appwrite", "content": "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.\nSome auth methods like [Magic URL login](/docs/products/auth/magic-url), \n[Email OTP login](/docs/products/auth/email-otp), or [Phone (SMS) login](/docs/products/auth/phone-sms) already generate tokens.\n\nYou can also create custom tokens using the [Create token](/docs/products/auth/custom-token) \nendpoint of the [Users API](/docs/products/auth/users). This can be used to implement **custom authentication flows**.\n\nTokens are created with the following properties:\n\n| Property | Type | Description |\n| ------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------ |\n| `$id` | string | Token ID. |\n| `$createdAt` | string | Token creation date in ISO 8601 format. |\n| `userId` | string | User ID. |\n| `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. |\n| `expire` | string | Token expiration date in ISO 8601 format. |\n\nMany 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:\n\n# Token login {% #token-login %}\nYou can find different usage of tokens in the Appwrite.\n\n{% cards %}\n{% cards_item href=\"/docs/products/auth/custom-token\" title=\"Custom token login\" %}\n{% /cards_item %}\n{% cards_item href=\"/docs/products/auth/email-otp\" title=\"Email OTP login\" %}\n{% /cards_item %}\n{% cards_item href=\"/docs/products/auth/magic-url\" title=\"Email magic URL\" %}\n{% /cards_item %}\n{% cards_item href=\"/docs/products/auth/phone-sms\" title=\"Phone (SMS) OTP\" %}\n{% /cards_item %}\n{% /cards %}"}, {"path": "docs/products/auth/users", "title": "Manage users", "description": "Manage user identities and profiles effectively with Appwrite. Dive into user management features, account settings, and user data customization", "content": "Appwrite Users API is used for managing users in server applications.\n\nUsers API can only be used with an API key and the [Server SDK](/docs/sdks#server) to manage all users.\n\nIf you need to act on behalf of users through an Appwrite Function or your own backend, use [JWT login](/docs/products/auth/jwt).\n\nNeed 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.\n\n{% partial file=\"account-vs-user.md\" /%}\n\nThe users API can be used to create users, import users, update user info, get user audit logs, and remove users.\n\n{% arrow_link href=\"/docs/references/cloud/server-nodejs/users\" %}\nLearn more in the Users API references\n{% /arrow_link %}"}, {"path": "docs/products/auth/verify-user", "title": "Verify user", "description": "Learn about Appwrite's email and phone verification system, including verification flows and role-based access control.", "content": "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.\n\n# Verify email {% #verify-email %}\n\nTo 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.\n\n{% multicode %}\n```client-web\nimport { Client, Account } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://cloud.appwrite.io/v1')\n .setProject('') // Your project ID\n\nconst account = new Account(client);\n\nconst promise = account.createVerification({\n url: 'https://example.com/verify'\n});\n\npromise.then(function (response) {\n console.log(response); // Success\n}, function (error) {\n console.log(error); // Failure\n});\n```\n\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nvoid main() {\n Client client = Client()\n .setEndpoint('https://cloud.appwrite.io/v1')\n .setProject('');\n\n Account account = Account(client);\n\n Future result = account.createVerification(\n url: 'https://example.com/verify'\n );\n\n result.then((response) {\n print(response);\n }).catchError((error) {\n print(error.response);\n });\n}\n```\n\n```client-apple\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nlet account = Account(client)\n\nlet token = try await account.createVerification(\n url: \"https://example.com/verify\"\n)\n```\n\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Account\n\nval client = Client(context)\n .setEndpoint(\"https://cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nval account = Account(client)\n\nval response = account.createVerification(\n url = \"https://example.com/verify\"\n)\n```\n{% /multicode %}\n\nAfter 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.\n\nNext, implement the verification page that handles the redirect.\n\n{% multicode %}\n```client-web\nimport { Client, Account } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://cloud.appwrite.io/v1')\n .setProject('');\n\nconst account = new Account(client);\n\nconst urlParams = new URLSearchParams(window.location.search);\nconst secret = urlParams.get('secret');\nconst userId = urlParams.get('userId');\n\nconst promise = account.updateVerification({\n userId,\n secret\n});\n\npromise.then(function (response) {\n console.log(response); // Success\n}, function (error) {\n console.log(error); // Failure\n});\n```\n\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nvoid main() {\n Client client = Client()\n .setEndpoint('https://cloud.appwrite.io/v1')\n .setProject('');\n\n Account account = Account(client);\n\n Future result = account.updateVerification(\n userId: '',\n secret: ''\n );\n\n result.then((response) {\n print(response);\n }).catchError((error) {\n print(error.response);\n });\n}\n```\n\n```client-apple\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nlet account = Account(client)\n\nlet response = try await account.updateVerification(\n userId: \"\",\n secret: \"\"\n)\n```\n\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Account\n\nval client = Client(context)\n .setEndpoint(\"https://cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nval account = Account(client)\n\nval response = account.updateVerification(\n userId = \"\",\n secret = \"\"\n)\n```\n{% /multicode %}\n\n# Verify phone {% #verify-phone %}\n\nTo verify a phone number, first ensure the user is logged in and has a phone number set on their account.\n\n{% multicode %}\n```client-web\nconst response = await account.updatePhone({\n phone: '+12065550100',\n password: 'password'\n});\n```\n\n```client-flutter\nFuture result = account.updatePhone(\n phone: '+12065550100',\n password: 'password'\n);\n\nresult.then((response) {\n print(response);\n}).catchError((error) {\n print(error.response);\n});\n```\n\n```client-apple\nlet response = try await account.updatePhone(\n phone: \"+12065550100\",\n password: \"password\"\n)\n```\n\n```client-android-kotlin\nval response = account.updatePhone(\n phone = \"+12065550100\",\n password = \"password\"\n)\n```\n{% /multicode %}\n\nThen initiate verification by calling `createPhoneVerification`.\n\n{% multicode %}\n```client-web\nconst response = await account.createPhoneVerification();\n```\n\n```client-flutter\nFuture result = account.createPhoneVerification();\n\nresult.then((response) {\n print(response);\n}).catchError((error) {\n print(error.response);\n});\n```\n\n```client-apple\nlet response = try await account.createPhoneVerification()\n```\n\n```client-android-kotlin\nval response = account.createPhoneVerification()\n```\n{% /multicode %}\n\nAfter the user receives the verification code, complete verification by calling `updatePhoneVerification`.\n\n{% multicode %}\n```client-web\nconst response = await account.updatePhoneVerification({\n userId: '[USER_ID]',\n secret: '[SECRET]'\n});\n```\n\n```client-flutter\nFuture result = account.updatePhoneVerification(\n userId: '',\n secret: ''\n);\n\nresult.then((response) {\n print(response);\n}).catchError((error) {\n print(error.response);\n});\n```\n\n```client-apple\nlet response = try await account.updatePhoneVerification(\n userId: \"\",\n secret: \"\"\n)\n```\n\n```client-android-kotlin\nval response = account.updatePhoneVerification(\n userId = \"\",\n secret = \"\"\n)\n```\n{% /multicode %}\n\n# Restrict access {% #restrict-access %}\n\nYou can restrict resource access to verified users in two ways:\n- Use `user([USER_ID], \"verified\")` to restrict access to a specific verified user\n- Use `users(\"verified\")` to restrict access to any verified user\n\n# Verification events {% #verification-events %}\n\nThe following events are triggered during the verification process:\n\n- `users.*.verification.*` - Triggers on any user's verification token event\n- `users.*.verification.*.create` - Triggers when a verification token for a user is created\n- `users.*.verification.*.update` - Triggers when a verification token for a user is validated\n\nEach event returns a Token Object."}, {"path": "docs/products/avatars", "title": "Avatars", "description": "Generate avatars, icons, and images for your applications. Use Appwrite Avatars to create user initials, QR codes, country flags, browser icons, and more.", "content": "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.\n\nAll 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.\n\n{% arrow_link href=\"/docs/products/avatars/quick-start\" %}\nGet started with Avatars in minutes\n{% /arrow_link %}\n\n# Capabilities {% #capabilities %}\n\nAppwrite Avatars supports multiple image generation and manipulation features to enhance your application's visual elements.\n\n{% cards %}\n{% cards_item href=\"/docs/products/avatars/initials\" title=\"User initials\" %}\nGenerate avatar images from user names or initials with customizable colors and sizes.\n{% /cards_item %}\n{% cards_item href=\"/docs/products/avatars/qr-codes\" title=\"QR codes\" %}\nCreate QR codes for authentication, sharing, and other use cases with customizable size and margin.\n{% /cards_item %}\n{% cards_item href=\"/docs/products/avatars/flags\" title=\"Country flags\" %}\nFetch country flag icons for displaying user locations and regional information.\n{% /cards_item %}\n{% cards_item href=\"/docs/products/avatars/browsers\" title=\"Browser icons\" %}\nRetrieve browser icons for displaying user agent information and device compatibility.\n{% /cards_item %}\n{% cards_item href=\"/docs/products/avatars/payment-methods\" title=\"Payment methods\" %}\nGet payment method logos for checkout flows and transaction displays.\n{% /cards_item %}\n{% cards_item href=\"/docs/products/avatars/favicons\" title=\"Favicons\" %}\nFetch favicons from remote websites for link previews and bookmark displays.\n{% /cards_item %}\n{% cards_item href=\"/docs/products/avatars/screenshots\" title=\"Screenshots\" %}\nCapture webpage screenshots with customizable viewport, theme, and browser settings.\n{% /cards_item %}\n{% cards_item href=\"/docs/products/avatars/image-manipulation\" title=\"Image proxy\" %}\nTransform remote images with resizing, cropping, and quality adjustments.\n{% /cards_item %}\n{% /cards %}\n\n# Image transformations {% #image-transformations %}\n\nAll 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.\n\n# No authentication required {% #no-authentication %}\n\nThe 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."}, {"path": "docs/products/avatars/browsers", "title": "Browser icons", "description": "Retrieve browser icons for displaying user agent information and device compatibility.", "content": "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.\n\n# Get browser icon {% #get-browser %}\n\nRetrieve a browser icon by browser code.\n\n{% multicode %}\n```client-web\nimport { Client, Avatars, Browser } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nconst avatars = new Avatars(client);\n\nconst result = avatars.getBrowser({\n code: Browser.GoogleChrome,\n width: 100,\n height: 100\n});\n\nconsole.log(result); // Resource URL\n```\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nfinal avatars = Avatars(client);\n\nFuture result = avatars.getBrowser(\n code: Browser.googleChrome,\n width: 100,\n height: 100\n).then((bytes) {\n // Use the browser icon bytes\n return bytes;\n}).catchError((error) {\n print(error.response);\n});\n```\n```client-apple\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nlet avatars = Avatars(client)\n\nlet byteBuffer = try await avatars.getBrowser(\n code: Browser.googleChrome,\n width: 100,\n height: 100\n)\n```\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Avatars\nimport io.appwrite.enums.Browser\n\nval client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nval avatars = Avatars(client)\n\nval result = avatars.getBrowser(\n code = Browser.GOOGLE_CHROME,\n width = 100,\n height = 100\n)\n```\n```client-react-native\nimport { Client, Avatars } from 'react-native-appwrite';\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nconst avatars = new Avatars(client);\n\nconst result = avatars.getBrowser({\n code: 'chrome',\n width: 100,\n height: 100\n});\n\nconsole.log(result); // Resource URL\n```\n{% /multicode %}\n\n# Parameters {% #parameters %}\n\nThe `getBrowser` method accepts the following parameters:\n\n| Parameter | Type | Description |\n| --------- | ---- | ----------- |\n| code | string | The browser code. Supported codes include `chrome`, `firefox`, `safari`, `edge`, `opera`, and others. |\n| width | integer | The width of the output image in pixels. Accepts values between `0-2000`. |\n| height | integer | The height of the output image in pixels. Accepts values between `0-2000`. |\n\n# Supported browsers {% #supported-browsers %}\n\nCommon browser codes include:\n\n{% multicode %}\n```client-web\nimport { Browser } from \"appwrite\";\n\n// Chrome\nconst chrome = avatars.getBrowser({\n code: Browser.GoogleChrome,\n width: 100,\n height: 100\n});\n\n// Firefox\nconst firefox = avatars.getBrowser({\n code: Browser.MozillaFirefox,\n width: 100,\n height: 100\n});\n\n// Safari\nconst safari = avatars.getBrowser({\n code: Browser.Safari,\n width: 100,\n height: 100\n});\n\n// Edge\nconst edge = avatars.getBrowser({\n code: Browser.MicrosoftEdge,\n width: 100,\n height: 100\n});\n\n// Opera\nconst opera = avatars.getBrowser({\n code: Browser.Opera,\n width: 100,\n height: 100\n});\n```\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\n// Chrome\nFuture chrome = avatars.getBrowser(\n code: Browser.googleChrome,\n width: 100,\n height: 100\n);\n\n// Firefox\nFuture firefox = avatars.getBrowser(\n code: Browser.mozillaFirefox,\n width: 100,\n height: 100\n);\n\n// Safari\nFuture safari = avatars.getBrowser(\n code: Browser.safari,\n width: 100,\n height: 100\n);\n\n// Edge\nFuture edge = avatars.getBrowser(\n code: Browser.microsoftEdge,\n width: 100,\n height: 100\n);\n\n// Opera\nFuture opera = avatars.getBrowser(\n code: Browser.opera,\n width: 100,\n height: 100\n);\n```\n```client-apple\nimport Appwrite\n\n// Chrome\nlet chrome = try await avatars.getBrowser(\n code: Browser.googleChrome,\n width: 100,\n height: 100\n)\n\n// Firefox\nlet firefox = try await avatars.getBrowser(\n code: Browser.mozillaFirefox,\n width: 100,\n height: 100\n)\n\n// Safari\nlet safari = try await avatars.getBrowser(\n code: Browser.safari,\n width: 100,\n height: 100\n)\n\n// Edge\nlet edge = try await avatars.getBrowser(\n code: Browser.microsoftEdge,\n width: 100,\n height: 100\n)\n\n// Opera\nlet opera = try await avatars.getBrowser(\n code: Browser.opera,\n width: 100,\n height: 100\n)\n```\n```client-android-kotlin\nimport io.appwrite.enums.Browser\n\n// Chrome\nval chrome = avatars.getBrowser(\n code = Browser.GOOGLE_CHROME,\n width = 100,\n height = 100\n)\n\n// Firefox\nval firefox = avatars.getBrowser(\n code = Browser.MOZILLA_FIREFOX,\n width = 100,\n height = 100\n)\n\n// Safari\nval safari = avatars.getBrowser(\n code = Browser.SAFARI,\n width = 100,\n height = 100\n)\n\n// Edge\nval edge = avatars.getBrowser(\n code = Browser.MICROSOFT_EDGE,\n width = 100,\n height = 100\n)\n\n// Opera\nval opera = avatars.getBrowser(\n code = Browser.OPERA,\n width = 100,\n height = 100\n)\n```\n{% /multicode %}\n\n# Use cases {% #use-cases %}\n\nBrowser icons are commonly used for:\n\n- **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\n- **Analytics dashboards**: Display browser usage statistics and user agent information\n- **Session management**: Show active sessions with browser information\n- **Compatibility indicators**: Display supported browsers for features or content\n- **Security logs**: Visualize login attempts and session information by browser\n- **User activity**: Display browser information in activity feeds and audit logs"}, {"path": "docs/products/avatars/favicons", "title": "Favicons", "description": "Fetch favicons from remote websites for link previews and bookmark displays.", "content": "The favicon endpoint retrieves favicons from remote websites. This is useful for displaying website icons in link previews, bookmarks, and social sharing interfaces.\n\n# Get favicon {% #get-favicon %}\n\nRetrieve a favicon from a remote website URL.\n\n{% multicode %}\n```client-web\nimport { Client, Avatars } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nconst avatars = new Avatars(client);\n\nconst result = avatars.getFavicon({\n url: 'https://example.com',\n width: 100,\n height: 100\n});\n\nconsole.log(result); // Resource URL\n```\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nfinal avatars = Avatars(client);\n\nFuture result = avatars.getFavicon(\n url: 'https://example.com',\n width: 100,\n height: 100\n).then((bytes) {\n // Use the favicon image bytes\n return bytes;\n}).catchError((error) {\n print(error.response);\n});\n```\n```client-apple\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nlet avatars = Avatars(client)\n\nlet byteBuffer = try await avatars.getFavicon(\n url: \"https://example.com\",\n width: 100,\n height: 100\n)\n```\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Avatars\n\nval client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nval avatars = Avatars(client)\n\nval result = avatars.getFavicon(\n url = \"https://example.com\",\n width = 100,\n height = 100\n)\n```\n```client-react-native\nimport { Client, Avatars } from 'react-native-appwrite';\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nconst avatars = new Avatars(client);\n\nconst result = avatars.getFavicon({\n url: 'https://example.com',\n width: 100,\n height: 100\n});\n\nconsole.log(result); // Resource URL\n```\n{% /multicode %}\n\n# Parameters {% #parameters %}\n\nThe `getFavicon` method accepts the following parameters:\n\n| Parameter | Type | Description |\n| --------- | ---- | ----------- |\n| url | string | The URL of the website to fetch the favicon from. Must be a valid HTTP or HTTPS URL. |\n| width | integer | The width of the output image in pixels. Accepts values between `0-2000`. |\n| height | integer | The height of the output image in pixels. Accepts values between `0-2000`. |\n\n# Favicon retrieval {% #favicon-retrieval %}\n\nThe service automatically attempts to retrieve favicons from common locations on the target website, including standard favicon paths and HTML meta tags.\n\n{% multicode %}\n```client-web\n// Standard website\nconst exampleFavicon = avatars.getFavicon({\n url: 'https://example.com',\n width: 64,\n height: 64\n});\n\n// Website with subdomain\nconst subdomainFavicon = avatars.getFavicon({\n url: 'https://blog.example.com',\n width: 64,\n height: 64\n});\n\n// Website with path\nconst pathFavicon = avatars.getFavicon({\n url: 'https://example.com/page',\n width: 64,\n height: 64\n});\n```\n```client-flutter\n// Standard website\nFuture exampleFavicon = avatars.getFavicon(\n url: 'https://example.com',\n width: 64,\n height: 64\n);\n\n// Website with subdomain\nFuture subdomainFavicon = avatars.getFavicon(\n url: 'https://blog.example.com',\n width: 64,\n height: 64\n);\n\n// Website with path\nFuture pathFavicon = avatars.getFavicon(\n url: 'https://example.com/page',\n width: 64,\n height: 64\n);\n```\n```client-apple\n// Standard website\nlet exampleFavicon = try await avatars.getFavicon(\n url: \"https://example.com\",\n width: 64,\n height: 64\n)\n\n// Website with subdomain\nlet subdomainFavicon = try await avatars.getFavicon(\n url: \"https://blog.example.com\",\n width: 64,\n height: 64\n)\n\n// Website with path\nlet pathFavicon = try await avatars.getFavicon(\n url: \"https://example.com/page\",\n width: 64,\n height: 64\n)\n```\n```client-android-kotlin\n// Standard website\nval exampleFavicon = avatars.getFavicon(\n url = \"https://example.com\",\n width = 64,\n height = 64\n)\n\n// Website with subdomain\nval subdomainFavicon = avatars.getFavicon(\n url = \"https://blog.example.com\",\n width = 64,\n height = 64\n)\n\n// Website with path\nval pathFavicon = avatars.getFavicon(\n url = \"https://example.com/page\",\n width = 64,\n height = 64\n)\n```\n{% /multicode %}\n\n# Use cases {% #use-cases %}\n\nFavicons are commonly used for:\n\n- **Link previews**: Display website icons in link preview cards and social sharing\n- **Bookmarks**: Show website icons in bookmark lists and collections\n- **Referral tracking**: Display source website icons in analytics and referral reports\n- **Content aggregation**: Show website icons in RSS feed readers and content aggregators\n- **Social media**: Display website icons in social media link previews and embeds"}, {"path": "docs/products/avatars/flags", "title": "Country flags", "description": "Retrieve country flag icons by country code for displaying user locations and regional information.", "content": "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.\n\n# Get country flag {% #get-flag %}\n\nRetrieve a country flag icon by its [ISO 3166-1](https://en.wikipedia.org/wiki/ISO_3166-1) country code.\n\n{% multicode %}\n```client-web\nimport { Client, Avatars, Flag } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nconst avatars = new Avatars(client);\n\nconst result = avatars.getFlag({\n code: Flag.UnitedStates,\n width: 100,\n height: 100\n});\n\nconsole.log(result); // Resource URL\n```\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nfinal avatars = Avatars(client);\n\nFuture result = avatars.getFlag(\n code: Flag.unitedStates,\n width: 100,\n height: 100\n).then((bytes) {\n // Use the flag image bytes\n return bytes;\n}).catchError((error) {\n print(error.response);\n});\n```\n```client-apple\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nlet avatars = Avatars(client)\n\nlet byteBuffer = try await avatars.getFlag(\n code: Flag.unitedStates,\n width: 100,\n height: 100\n)\n```\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Avatars\nimport io.appwrite.enums.Flag\n\nval client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nval avatars = Avatars(client)\n\nval result = avatars.getFlag(\n code = Flag.UNITED_STATES,\n width = 100,\n height = 100\n)\n```\n```client-react-native\nimport { Client, Avatars } from 'react-native-appwrite';\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nconst avatars = new Avatars(client);\n\nconst result = avatars.getFlag({\n code: 'US',\n width: 100,\n height: 100\n});\n\nconsole.log(result); // Resource URL\n```\n{% /multicode %}\n\n# Parameters {% #parameters %}\n\nThe `getFlag` method accepts the following parameters:\n\n| Parameter | Type | Description |\n| --------- | ---- | ----------- |\n| code | string | The ISO [ISO 3166-1](https://en.wikipedia.org/wiki/ISO_3166-1) country code (e.g., `US`, `GB`, `FR`). |\n| width | integer | The width of the output image in pixels. Accepts values between `0-2000`. |\n| height | integer | The height of the output image in pixels. Accepts values between `0-2000`. |\n\n# Country codes {% #country-codes %}\n\nUse ISO 3166-1 alpha-2 country codes to specify the country. These are two-letter codes that uniquely identify countries.\n\n{% multicode %}\n```client-web\nimport { Flag } from \"appwrite\";\n\n// United States\nconst usFlag = avatars.getFlag({\n code: Flag.UnitedStates,\n width: 100,\n height: 100\n});\n\n// United Kingdom\nconst ukFlag = avatars.getFlag({\n code: Flag.UnitedKingdom,\n width: 100,\n height: 100\n});\n\n// France\nconst frFlag = avatars.getFlag({\n code: Flag.France,\n width: 100,\n height: 100\n});\n```\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\n// United States\nFuture usFlag = avatars.getFlag(\n code: Flag.unitedStates,\n width: 100,\n height: 100\n);\n\n// United Kingdom\nFuture ukFlag = avatars.getFlag(\n code: Flag.unitedKingdom,\n width: 100,\n height: 100\n);\n\n// France\nFuture frFlag = avatars.getFlag(\n code: Flag.france,\n width: 100,\n height: 100\n);\n```\n```client-apple\nimport Appwrite\n\n// United States\nlet usFlag = try await avatars.getFlag(\n code: Flag.unitedStates,\n width: 100,\n height: 100\n)\n\n// United Kingdom\nlet ukFlag = try await avatars.getFlag(\n code: Flag.unitedKingdom,\n width: 100,\n height: 100\n)\n\n// France\nlet frFlag = try await avatars.getFlag(\n code: Flag.france,\n width: 100,\n height: 100\n)\n```\n```client-android-kotlin\nimport io.appwrite.enums.Flag\n\n// United States\nval usFlag = avatars.getFlag(\n code = Flag.UNITED_STATES,\n width = 100,\n height = 100\n)\n\n// United Kingdom\nval ukFlag = avatars.getFlag(\n code = Flag.UNITED_KINGDOM,\n width = 100,\n height = 100\n)\n\n// France\nval frFlag = avatars.getFlag(\n code = Flag.FRANCE,\n width = 100,\n height = 100\n)\n```\n{% /multicode %}\n\n# Use cases {% #use-cases %}\n\nCountry flags are commonly used for:\n\n- **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\n- **User profiles**: Display user country or location in profile pages\n- **Regional settings**: Show available regions or languages by country\n- **Analytics dashboards**: Visualize geographic data and user distribution\n- **Localization**: Indicate content availability or regional restrictions\n- **Shipping information**: Display origin and destination countries in shipping interfaces"}, {"path": "docs/products/avatars/image-manipulation", "title": "Image proxy", "description": "Transform remote images with resizing, cropping, and quality adjustments for optimal display and performance.", "content": "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.\n\n# Proxy remote image {% #get-image %}\n\nFetch and transform an image from a remote URL with various transformation options.\n\n{% multicode %}\n```client-web\nimport { Client, Avatars } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nconst avatars = new Avatars(client);\n\nconst result = avatars.getImage({\n url: 'https://example.com/image.jpg',\n width: 800,\n height: 600,\n quality: 90\n});\n\nconsole.log(result); // Resource URL\n```\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nfinal avatars = Avatars(client);\n\nFuture result = avatars.getImage(\n url: 'https://example.com/image.jpg',\n width: 800,\n height: 600,\n quality: 90\n).then((bytes) {\n // Use the transformed image bytes\n return bytes;\n}).catchError((error) {\n print(error.response);\n});\n```\n```client-apple\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nlet avatars = Avatars(client)\n\nlet byteBuffer = try await avatars.getImage(\n url: \"https://example.com/image.jpg\",\n width: 800,\n height: 600,\n quality: 90\n)\n```\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Avatars\n\nval client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nval avatars = Avatars(client)\n\nval result = avatars.getImage(\n url = \"https://example.com/image.jpg\",\n width = 800,\n height = 600,\n quality = 90\n)\n```\n```client-react-native\nimport { Client, Avatars } from 'react-native-appwrite';\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nconst avatars = new Avatars(client);\n\nconst result = avatars.getImage({\n url: 'https://example.com/image.jpg',\n width: 800,\n height: 600,\n quality: 90\n});\n\nconsole.log(result); // Resource URL\n```\n{% /multicode %}\n\n# Parameters {% #parameters %}\n\nThe `getImage` method accepts the following parameters:\n\n| Parameter | Type | Description |\n| --------- | ---- | ----------- |\n| url | string | The URL of the remote image to fetch and transform. Must be a valid HTTP or HTTPS URL. |\n| width | integer | The width of the output image in pixels. The image will be resized maintaining aspect ratio. Accepts values between `0-4000`. |\n| height | integer | The height of the output image in pixels. The image will be resized maintaining aspect ratio. Accepts values between `0-4000`. |\n| 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. |\n| 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`. |\n| output | string | The output image format. Supported formats: `jpg`, `jpeg`, `png`, `gif`, `webp`, `avif`, `heic`. If not provided, uses the original image format. |\n\n# Resizing {% #resizing %}\n\nResize images to specific dimensions while maintaining aspect ratio. When only width or height is provided, the other dimension is calculated automatically.\n\n{% multicode %}\n```client-web\n// Resize by width only (height calculated automatically)\nconst widthOnly = avatars.getImage({\n url: 'https://example.com/image.jpg',\n width: 800\n});\n\n// Resize by height only (width calculated automatically)\nconst heightOnly = avatars.getImage({\n url: 'https://example.com/image.jpg',\n height: 600\n});\n\n// Resize to specific dimensions\nconst specificSize = avatars.getImage({\n url: 'https://example.com/image.jpg',\n width: 800,\n height: 600\n});\n```\n```client-flutter\n// Resize by width only (height calculated automatically)\nFuture widthOnly = avatars.getImage(\n url: 'https://example.com/image.jpg',\n width: 800\n);\n\n// Resize by height only (width calculated automatically)\nFuture heightOnly = avatars.getImage(\n url: 'https://example.com/image.jpg',\n height: 600\n);\n\n// Resize to specific dimensions\nFuture specificSize = avatars.getImage(\n url: 'https://example.com/image.jpg',\n width: 800,\n height: 600\n);\n```\n```client-apple\n// Resize by width only (height calculated automatically)\nlet widthOnly = try await avatars.getImage(\n url: \"https://example.com/image.jpg\",\n width: 800\n)\n\n// Resize by height only (width calculated automatically)\nlet heightOnly = try await avatars.getImage(\n url: \"https://example.com/image.jpg\",\n height: 600\n)\n\n// Resize to specific dimensions\nlet specificSize = try await avatars.getImage(\n url: \"https://example.com/image.jpg\",\n width: 800,\n height: 600\n)\n```\n```client-android-kotlin\n// Resize by width only (height calculated automatically)\nval widthOnly = avatars.getImage(\n url = \"https://example.com/image.jpg\",\n width = 800\n)\n\n// Resize by height only (width calculated automatically)\nval heightOnly = avatars.getImage(\n url = \"https://example.com/image.jpg\",\n height = 600\n)\n\n// Resize to specific dimensions\nval specificSize = avatars.getImage(\n url = \"https://example.com/image.jpg\",\n width = 800,\n height = 600\n)\n```\n{% /multicode %}\n\n# Cropping {% #cropping %}\n\nWhen both width and height are specified, you can control how the image is cropped using the gravity parameter.\n\n{% multicode %}\n```client-web\n// Crop from center\nconst centerCrop = avatars.getImage({\n url: 'https://example.com/image.jpg',\n width: 400,\n height: 400,\n gravity: 'center'\n});\n\n// Crop from top-left\nconst topLeftCrop = avatars.getImage({\n url: 'https://example.com/image.jpg',\n width: 400,\n height: 400,\n gravity: 'top-left'\n});\n\n// Crop from bottom-right\nconst bottomRightCrop = avatars.getImage({\n url: 'https://example.com/image.jpg',\n width: 400,\n height: 400,\n gravity: 'bottom-right'\n});\n```\n```client-flutter\n// Crop from center\nFuture centerCrop = avatars.getImage(\n url: 'https://example.com/image.jpg',\n width: 400,\n height: 400,\n gravity: 'center'\n);\n\n// Crop from top-left\nFuture topLeftCrop = avatars.getImage(\n url: 'https://example.com/image.jpg',\n width: 400,\n height: 400,\n gravity: 'top-left'\n);\n\n// Crop from bottom-right\nFuture bottomRightCrop = avatars.getImage(\n url: 'https://example.com/image.jpg',\n width: 400,\n height: 400,\n gravity: 'bottom-right'\n);\n```\n```client-apple\n// Crop from center\nlet centerCrop = try await avatars.getImage(\n url: \"https://example.com/image.jpg\",\n width: 400,\n height: 400,\n gravity: \"center\"\n)\n\n// Crop from top-left\nlet topLeftCrop = try await avatars.getImage(\n url: \"https://example.com/image.jpg\",\n width: 400,\n height: 400,\n gravity: \"top-left\"\n)\n\n// Crop from bottom-right\nlet bottomRightCrop = try await avatars.getImage(\n url: \"https://example.com/image.jpg\",\n width: 400,\n height: 400,\n gravity: \"bottom-right\"\n)\n```\n```client-android-kotlin\n// Crop from center\nval centerCrop = avatars.getImage(\n url = \"https://example.com/image.jpg\",\n width = 400,\n height = 400,\n gravity = \"center\"\n)\n\n// Crop from top-left\nval topLeftCrop = avatars.getImage(\n url = \"https://example.com/image.jpg\",\n width = 400,\n height = 400,\n gravity = \"top-left\"\n)\n\n// Crop from bottom-right\nval bottomRightCrop = avatars.getImage(\n url = \"https://example.com/image.jpg\",\n width = 400,\n height = 400,\n gravity = \"bottom-right\"\n)\n```\n{% /multicode %}\n\n# Quality and format {% #quality-format %}\n\nAdjust image quality and output format to optimize file size and performance.\n\n{% multicode %}\n```client-web\n// High quality JPEG\nconst highQuality = avatars.getImage({\n url: 'https://example.com/image.jpg',\n width: 1200,\n quality: 95,\n output: 'jpg'\n});\n\n// Optimized WebP\nconst webpOptimized = avatars.getImage({\n url: 'https://example.com/image.jpg',\n width: 1200,\n quality: 85,\n output: 'webp'\n});\n\n// Compressed for mobile\nconst mobileOptimized = avatars.getImage({\n url: 'https://example.com/image.jpg',\n width: 800,\n quality: 75,\n output: 'jpg'\n});\n```\n```client-flutter\n// High quality JPEG\nFuture highQuality = avatars.getImage(\n url: 'https://example.com/image.jpg',\n width: 1200,\n quality: 95,\n output: 'jpg'\n);\n\n// Optimized WebP\nFuture webpOptimized = avatars.getImage(\n url: 'https://example.com/image.jpg',\n width: 1200,\n quality: 85,\n output: 'webp'\n);\n\n// Compressed for mobile\nFuture mobileOptimized = avatars.getImage(\n url: 'https://example.com/image.jpg',\n width: 800,\n quality: 75,\n output: 'jpg'\n);\n```\n```client-apple\n// High quality JPEG\nlet highQuality = try await avatars.getImage(\n url: \"https://example.com/image.jpg\",\n width: 1200,\n quality: 95,\n output: \"jpg\"\n)\n\n// Optimized WebP\nlet webpOptimized = try await avatars.getImage(\n url: \"https://example.com/image.jpg\",\n width: 1200,\n quality: 85,\n output: \"webp\"\n)\n\n// Compressed for mobile\nlet mobileOptimized = try await avatars.getImage(\n url: \"https://example.com/image.jpg\",\n width: 800,\n quality: 75,\n output: \"jpg\"\n)\n```\n```client-android-kotlin\n// High quality JPEG\nval highQuality = avatars.getImage(\n url = \"https://example.com/image.jpg\",\n width = 1200,\n quality = 95,\n output = \"jpg\"\n)\n\n// Optimized WebP\nval webpOptimized = avatars.getImage(\n url = \"https://example.com/image.jpg\",\n width = 1200,\n quality = 85,\n output = \"webp\"\n)\n\n// Compressed for mobile\nval mobileOptimized = avatars.getImage(\n url = \"https://example.com/image.jpg\",\n width = 800,\n quality = 75,\n output = \"jpg\"\n)\n```\n{% /multicode %}\n\n# Use cases {% #use-cases %}\n\nImage proxy is commonly used for:\n\n- **Responsive images**: Generate multiple sizes for different screen sizes and devices\n- **Thumbnail generation**: Create thumbnails from full-size images for galleries and lists\n- **Format optimization**: Convert images to modern formats like WebP or AVIF for better compression\n- **Performance optimization**: Reduce image file sizes while maintaining acceptable quality\n- **Content delivery**: Transform images on-the-fly for CDN delivery and caching\n- **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"}, {"path": "docs/products/avatars/initials", "title": "User initials", "description": "Generate avatar images from user names or initials with customizable appearance and dimensions.", "content": "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.\n\n# Generate initials {% #generate-initials %}\n\nGenerate an avatar image from a user's name. The service automatically extracts initials from the name and displays them on a colored background.\n\n{% multicode %}\n```client-web\nimport { Client, Avatars } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nconst avatars = new Avatars(client);\n\nconst result = avatars.getInitials({\n name: 'John Doe',\n width: 200,\n height: 200,\n background: '000000'\n});\n\nconsole.log(result); // Resource URL\n```\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nfinal avatars = Avatars(client);\n\nFuture result = avatars.getInitials(\n name: 'John Doe',\n width: 200,\n height: 200,\n background: '000000'\n).then((bytes) {\n // Use the image bytes\n return bytes;\n}).catchError((error) {\n print(error.response);\n});\n```\n```client-apple\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nlet avatars = Avatars(client)\n\nlet byteBuffer = try await avatars.getInitials(\n name: \"John Doe\",\n width: 200,\n height: 200,\n background: \"000000\"\n)\n```\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Avatars\n\nval client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nval avatars = Avatars(client)\n\nval result = avatars.getInitials(\n name = \"John Doe\",\n width = 200,\n height = 200,\n background = \"000000\"\n)\n```\n```client-react-native\nimport { Client, Avatars } from 'react-native-appwrite';\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nconst avatars = new Avatars(client);\n\nconst result = avatars.getInitials({\n name: 'John Doe',\n width: 200,\n height: 200,\n background: '000000'\n});\n\nconsole.log(result); // Resource URL\n```\n{% /multicode %}\n\n# Parameters {% #parameters %}\n\nThe `getInitials` method accepts the following parameters:\n\n| Parameter | Type | Description |\n| --------- | ---- | ----------- |\n| name | string | The name to generate initials from. The service extracts the first letter of each word. |\n| width | integer | The width of the output image in pixels. Accepts values between `0-2000`. |\n| height | integer | The height of the output image in pixels. Accepts values between `0-2000`. |\n| background | string | The background color in hexadecimal format without the leading `#`. Defaults to a random color if not provided. |\n\n# Customization {% #customization %}\n\nYou 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.\n\n{% multicode %}\n```client-web\n// Square avatar with custom background\nconst squareAvatar = avatars.getInitials({\n name: 'Jane Smith',\n width: 150,\n height: 150,\n background: 'FF5733'\n});\n\n// Rectangular avatar\nconst rectangularAvatar = avatars.getInitials({\n name: 'Bob Johnson',\n width: 200,\n height: 100,\n background: '3498DB'\n});\n\n// Random background color (omit background parameter)\nconst randomBackground = avatars.getInitials({\n name: 'Alice Williams',\n width: 180,\n height: 180\n});\n```\n```client-flutter\n// Square avatar with custom background\nFuture squareAvatar = avatars.getInitials(\n name: 'Jane Smith',\n width: 150,\n height: 150,\n background: 'FF5733'\n);\n\n// Rectangular avatar\nFuture rectangularAvatar = avatars.getInitials(\n name: 'Bob Johnson',\n width: 200,\n height: 100,\n background: '3498DB'\n);\n\n// Random background color (omit background parameter)\nFuture randomBackground = avatars.getInitials(\n name: 'Alice Williams',\n width: 180,\n height: 180\n);\n```\n```client-apple\n// Square avatar with custom background\nlet squareAvatar = try await avatars.getInitials(\n name: \"Jane Smith\",\n width: 150,\n height: 150,\n background: \"FF5733\"\n)\n\n// Rectangular avatar\nlet rectangularAvatar = try await avatars.getInitials(\n name: \"Bob Johnson\",\n width: 200,\n height: 100,\n background: \"3498DB\"\n)\n\n// Random background color (omit background parameter)\nlet randomBackground = try await avatars.getInitials(\n name: \"Alice Williams\",\n width: 180,\n height: 180\n)\n```\n```client-android-kotlin\n// Square avatar with custom background\nval squareAvatar = avatars.getInitials(\n name = \"Jane Smith\",\n width = 150,\n height = 150,\n background = \"FF5733\"\n)\n\n// Rectangular avatar\nval rectangularAvatar = avatars.getInitials(\n name = \"Bob Johnson\",\n width = 200,\n height = 100,\n background = \"3498DB\"\n)\n\n// Random background color (omit background parameter)\nval randomBackground = avatars.getInitials(\n name = \"Alice Williams\",\n width = 180,\n height = 180\n)\n```\n{% /multicode %}\n\n# Use cases {% #use-cases %}\n\nUser initials avatars are commonly used in:\n\n- **User profiles**: Display avatars in user lists, comments, and profile pages when no profile picture is available\n- **Team members**: Show team member avatars in collaboration interfaces\n- **Notifications**: Display sender avatars in notification systems\n- **Activity feeds**: Show user avatars in activity and timeline views"}, {"path": "docs/products/avatars/payment-methods", "title": "Payment methods", "description": "Retrieve payment method logos for checkout flows and transaction displays.", "content": "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.\n\n# Get payment method logo {% #get-credit-card %}\n\nRetrieve a payment method or credit card logo by code.\n\n{% multicode %}\n```client-web\nimport { Client, Avatars, CreditCard } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nconst avatars = new Avatars(client);\n\nconst result = avatars.getCreditCard({\n code: CreditCard.Visa,\n width: 100,\n height: 100\n});\n\nconsole.log(result); // Resource URL\n```\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nfinal avatars = Avatars(client);\n\nFuture result = avatars.getCreditCard(\n code: CreditCard.visa,\n width: 100,\n height: 100\n).then((bytes) {\n // Use the payment method logo bytes\n return bytes;\n}).catchError((error) {\n print(error.response);\n});\n```\n```client-apple\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nlet avatars = Avatars(client)\n\nlet byteBuffer = try await avatars.getCreditCard(\n code: CreditCard.visa,\n width: 100,\n height: 100\n)\n```\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Avatars\nimport io.appwrite.enums.CreditCard\n\nval client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nval avatars = Avatars(client)\n\nval result = avatars.getCreditCard(\n code = CreditCard.VISA,\n width = 100,\n height = 100\n)\n```\n```client-react-native\nimport { Client, Avatars } from 'react-native-appwrite';\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nconst avatars = new Avatars(client);\n\nconst result = avatars.getCreditCard({\n code: 'visa',\n width: 100,\n height: 100\n});\n\nconsole.log(result); // Resource URL\n```\n{% /multicode %}\n\n# Parameters {% #parameters %}\n\nThe `getCreditCard` method accepts the following parameters:\n\n| Parameter | Type | Description |\n| --------- | ---- | ----------- |\n| code | string | The payment method or credit card code. Supported codes include `visa`, `mastercard`, `amex`, `discover`, `paypal`, and others. |\n| width | integer | The width of the output image in pixels. Accepts values between `0-2000`. |\n| height | integer | The height of the output image in pixels. Accepts values between `0-2000`. |\n\n# Supported payment methods {% #supported-payment-methods %}\n\nCommon payment method codes include:\n\n{% multicode %}\n```client-web\nimport { CreditCard } from \"appwrite\";\n\n// Visa\nconst visa = avatars.getCreditCard({\n code: CreditCard.Visa,\n width: 100,\n height: 100\n});\n\n// Mastercard\nconst mastercard = avatars.getCreditCard({\n code: CreditCard.Mastercard,\n width: 100,\n height: 100\n});\n\n// American Express\nconst amex = avatars.getCreditCard({\n code: CreditCard.AmericanExpress,\n width: 100,\n height: 100\n});\n\n// Discover\nconst discover = avatars.getCreditCard({\n code: CreditCard.Discover,\n width: 100,\n height: 100\n});\n```\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\n// Visa\nFuture visa = avatars.getCreditCard(\n code: CreditCard.visa,\n width: 100,\n height: 100\n);\n\n// Mastercard\nFuture mastercard = avatars.getCreditCard(\n code: CreditCard.mastercard,\n width: 100,\n height: 100\n);\n\n// American Express\nFuture amex = avatars.getCreditCard(\n code: CreditCard.americanExpress,\n width: 100,\n height: 100\n);\n\n// Discover\nFuture discover = avatars.getCreditCard(\n code: CreditCard.discover,\n width: 100,\n height: 100\n);\n```\n```client-apple\nimport Appwrite\n\n// Visa\nlet visa = try await avatars.getCreditCard(\n code: CreditCard.visa,\n width: 100,\n height: 100\n)\n\n// Mastercard\nlet mastercard = try await avatars.getCreditCard(\n code: CreditCard.mastercard,\n width: 100,\n height: 100\n)\n\n// American Express\nlet amex = try await avatars.getCreditCard(\n code: CreditCard.americanExpress,\n width: 100,\n height: 100\n)\n\n// Discover\nlet discover = try await avatars.getCreditCard(\n code: CreditCard.discover,\n width: 100,\n height: 100\n)\n```\n```client-android-kotlin\nimport io.appwrite.enums.CreditCard\n\n// Visa\nval visa = avatars.getCreditCard(\n code = CreditCard.VISA,\n width = 100,\n height = 100\n)\n\n// Mastercard\nval mastercard = avatars.getCreditCard(\n code = CreditCard.MASTERCARD,\n width = 100,\n height = 100\n)\n\n// American Express\nval amex = avatars.getCreditCard(\n code = CreditCard.AMERICAN_EXPRESS,\n width = 100,\n height = 100\n)\n\n// Discover\nval discover = avatars.getCreditCard(\n code = CreditCard.DISCOVER,\n width = 100,\n height = 100\n)\n```\n{% /multicode %}\n\n# Use cases {% #use-cases %}\n\nPayment method logos are commonly used for:\n\n- **Checkout flows**: Display accepted payment methods during checkout\n- **Payment settings**: Show saved payment methods in user account settings\n- **Transaction history**: Display payment method used for each transaction\n- **Subscription management**: Show payment methods associated with subscriptions\n- **Billing information**: Display payment method logos in invoices and receipts"}, {"path": "docs/products/avatars/qr-codes", "title": "QR codes", "description": "Generate QR codes from text strings with customizable size and margin for authentication, sharing, and data encoding.", "content": "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.\n\n# Generate QR code {% #generate-qr %}\n\nGenerate a QR code image from a text string. The QR code can be scanned by any standard QR code reader.\n\n{% multicode %}\n```client-web\nimport { Client, Avatars } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nconst avatars = new Avatars(client);\n\nconst result = avatars.getQR({\n text: 'https://example.com',\n size: 300,\n margin: 1,\n download: false\n});\n\nconsole.log(result); // Resource URL\n```\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nfinal avatars = Avatars(client);\n\nFuture result = avatars.getQR(\n text: 'https://example.com',\n size: 300,\n margin: 1,\n download: false\n).then((bytes) {\n // Use the QR code image bytes\n return bytes;\n}).catchError((error) {\n print(error.response);\n});\n```\n```client-apple\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nlet avatars = Avatars(client)\n\nlet byteBuffer = try await avatars.getQR(\n text: \"https://example.com\",\n size: 300,\n margin: 1,\n download: false\n)\n```\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Avatars\n\nval client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nval avatars = Avatars(client)\n\nval result = avatars.getQR(\n text = \"https://example.com\",\n size = 300,\n margin = 1,\n download = false\n)\n```\n```client-react-native\nimport { Client, Avatars } from 'react-native-appwrite';\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nconst avatars = new Avatars(client);\n\nconst result = avatars.getQR({\n text: 'https://example.com',\n size: 300,\n margin: 1,\n download: false\n});\n\nconsole.log(result); // Resource URL\n```\n{% /multicode %}\n\n# Parameters {% #parameters %}\n\nThe `getQR` method accepts the following parameters:\n\n| Parameter | Type | Description |\n| --------- | ---- | ----------- |\n| text | string | The text string to encode in the QR code. Can be a URL, authentication URI, or any text data. |\n| size | integer | The size of the QR code in pixels. Accepts values between `0-1000`. Defaults to `200` if not provided. |\n| margin | integer | The margin around the QR code in pixels. Accepts values between `0-10`. Defaults to `1` if not provided. |\n| download | boolean | Whether to download the image or return a URL. Defaults to `false`. |\n\n# Two-factor authentication {% #two-factor-authentication %}\n\nQR codes are commonly used for two-factor authentication (2FA). When setting up TOTP authentication, you can generate a QR code from the authenticator URI.\n\n{% multicode %}\n```client-web\n// After creating an MFA authenticator\nconst authenticator = await account.createMfaAuthenticator('totp');\nconst qrCode = avatars.getQR({\n text: authenticator.uri,\n size: 400,\n margin: 2\n});\n```\n```client-flutter\n// After creating an MFA authenticator\nfinal authenticator = await account.createMfaAuthenticator('totp');\nfinal qrCode = avatars.getQR(\n text: authenticator.uri,\n size: 400,\n margin: 2\n);\n```\n```client-apple\n// After creating an MFA authenticator\nlet authenticator = try await account.createMfaAuthenticator(type: \"totp\")\nlet qrCode = try await avatars.getQR(\n text: authenticator.uri,\n size: 400,\n margin: 2\n)\n```\n```client-android-kotlin\n// After creating an MFA authenticator\nval authenticator = account.createMfaAuthenticator(\"totp\")\nval qrCode = avatars.getQR(\n text = authenticator.uri,\n size = 400,\n margin = 2\n)\n```\n{% /multicode %}\n\n# Customization {% #customization %}\n\nAdjust 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.\n\n{% multicode %}\n```client-web\n// Small QR code with minimal margin\nconst smallQR = avatars.getQR({\n text: 'https://example.com',\n size: 150,\n margin: 0\n});\n\n// Large QR code for printing\nconst largeQR = avatars.getQR({\n text: 'https://example.com',\n size: 800,\n margin: 3\n});\n\n// Standard size for web display\nconst standardQR = avatars.getQR({\n text: 'https://example.com',\n size: 300,\n margin: 1\n});\n```\n```client-flutter\n// Small QR code with minimal margin\nFuture smallQR = avatars.getQR(\n text: 'https://example.com',\n size: 150,\n margin: 0\n);\n\n// Large QR code for printing\nFuture largeQR = avatars.getQR(\n text: 'https://example.com',\n size: 800,\n margin: 3\n);\n\n// Standard size for web display\nFuture standardQR = avatars.getQR(\n text: 'https://example.com',\n size: 300,\n margin: 1\n);\n```\n```client-apple\n// Small QR code with minimal margin\nlet smallQR = try await avatars.getQR(\n text: \"https://example.com\",\n size: 150,\n margin: 0\n)\n\n// Large QR code for printing\nlet largeQR = try await avatars.getQR(\n text: \"https://example.com\",\n size: 800,\n margin: 3\n)\n\n// Standard size for web display\nlet standardQR = try await avatars.getQR(\n text: \"https://example.com\",\n size: 300,\n margin: 1\n)\n```\n```client-android-kotlin\n// Small QR code with minimal margin\nval smallQR = avatars.getQR(\n text = \"https://example.com\",\n size = 150,\n margin = 0\n)\n\n// Large QR code for printing\nval largeQR = avatars.getQR(\n text = \"https://example.com\",\n size = 800,\n margin = 3\n)\n\n// Standard size for web display\nval standardQR = avatars.getQR(\n text = \"https://example.com\",\n size = 300,\n margin = 1\n)\n```\n{% /multicode %}\n\n# Use cases {% #use-cases %}\n\nQR codes are commonly used for:\n\n- **Two-factor authentication**: Generate QR codes for TOTP authenticator setup\n- **Team invitations**: Create QR codes for team invite links to enable quick member onboarding\n- **Link sharing**: Create QR codes for URLs to enable quick access\n- **Event tickets**: Generate QR codes for event registration and check-in\n- **Wi-Fi credentials**: Encode Wi-Fi network information for easy connection\n- **Contact information**: Share vCard data encoded in QR codes"}, {"path": "docs/products/avatars/quick-start", "title": "Start with Avatars", "description": "Get started quickly with Appwrite Avatars. Learn how to generate user initials, QR codes, and other avatar images in minutes.", "content": "You can start using Appwrite Avatars immediately. The service is publicly accessible and does not require authentication or API keys.\n\n# Initialize the client {% #initialize-client %}\n\nFirst, initialize the Appwrite client with your project endpoint and project ID.\n\n{% multicode %}\n```client-web\nimport { Client, Avatars } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nconst avatars = new Avatars(client);\n```\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nfinal avatars = Avatars(client);\n```\n```client-apple\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nlet avatars = Avatars(client)\n```\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Avatars\n\nval client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nval avatars = Avatars(client)\n```\n```client-react-native\nimport { Client, Avatars } from 'react-native-appwrite';\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nconst avatars = new Avatars(client);\n```\n{% /multicode %}\n\n# Generate user initials {% #generate-initials %}\n\nGenerate an avatar image from a user's name or initials. This is useful for displaying user profiles when no profile picture is available.\n\n{% multicode %}\n```client-web\nconst result = avatars.getInitials({\n name: 'John Doe',\n width: 200,\n height: 200,\n background: '000000'\n});\n\nconsole.log(result); // Resource URL\n```\n```client-flutter\nFuture result = avatars.getInitials(\n name: 'John Doe',\n width: 200,\n height: 200,\n background: '000000'\n).then((bytes) {\n // Use the image bytes\n return bytes;\n}).catchError((error) {\n print(error.response);\n});\n```\n```client-apple\nlet byteBuffer = try await avatars.getInitials(\n name: \"John Doe\",\n width: 200,\n height: 200,\n background: \"000000\"\n)\n```\n```client-android-kotlin\nval result = avatars.getInitials(\n name = \"John Doe\",\n width = 200,\n height = 200,\n background = \"000000\"\n)\n```\n```client-react-native\nconst result = avatars.getInitials({\n name: 'John Doe',\n width: 200,\n height: 200,\n background: '000000'\n});\n\nconsole.log(result); // Resource URL\n```\n{% /multicode %}\n\n# Generate QR code {% #generate-qr %}\n\nCreate a QR code from any text string. This is commonly used for two-factor authentication, sharing links, or encoding data.\n\n{% multicode %}\n```client-web\nconst result = avatars.getQR({\n text: 'https://example.com',\n size: 300,\n margin: 1,\n download: false\n});\n\nconsole.log(result); // Resource URL\n```\n```client-flutter\nFuture result = avatars.getQR(\n text: 'https://example.com',\n size: 300,\n margin: 1,\n download: false\n).then((bytes) {\n // Use the QR code image bytes\n return bytes;\n}).catchError((error) {\n print(error.response);\n});\n```\n```client-apple\nlet byteBuffer = try await avatars.getQR(\n text: \"https://example.com\",\n size: 300,\n margin: 1,\n download: false\n)\n```\n```client-android-kotlin\nval result = avatars.getQR(\n text = \"https://example.com\",\n size = 300,\n margin = 1,\n download = false\n)\n```\n```client-react-native\nconst result = avatars.getQR({\n text: 'https://example.com',\n size: 300,\n margin: 1,\n download: false\n});\n\nconsole.log(result); // Resource URL\n```\n{% /multicode %}\n\n# Get country flag {% #get-flag %}\n\nRetrieve a country flag icon by country code. This is useful for displaying user locations or regional information.\n\n{% multicode %}\n```client-web\nimport { Flag } from \"appwrite\";\n\nconst result = avatars.getFlag({\n code: Flag.UnitedStates,\n width: 100,\n height: 100\n});\n\nconsole.log(result); // Resource URL\n```\n```client-flutter\nFuture result = avatars.getFlag(\n code: Flag.unitedStates,\n width: 100,\n height: 100\n).then((bytes) {\n // Use the flag image bytes\n return bytes;\n}).catchError((error) {\n print(error.response);\n});\n```\n```client-apple\nlet byteBuffer = try await avatars.getFlag(\n code: Flag.unitedStates,\n width: 100,\n height: 100\n)\n```\n```client-android-kotlin\nimport io.appwrite.enums.Flag\n\nval result = avatars.getFlag(\n code = Flag.UNITED_STATES,\n width = 100,\n height = 100\n)\n```\n```client-react-native\nconst result = avatars.getFlag({\n code: 'US',\n width: 100,\n height: 100\n});\n\nconsole.log(result); // Resource URL\n```\n{% /multicode %}\n\n# Next steps {% #next-steps %}\n\nNow that you've generated your first avatars, explore more advanced features:\n\n- Learn about [user initials](/docs/products/avatars/initials) customization\n- Discover [QR code](/docs/products/avatars/qr-codes) options\n- Explore [image manipulation](/docs/products/avatars/image-manipulation) capabilities"}, {"path": "docs/products/avatars/screenshots", "title": "Screenshots", "description": "Capture webpage screenshots with customizable viewport, theme, browser settings, and geolocation options for comprehensive web page documentation.", "content": "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.\n\nThis 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.\n\n# Get webpage screenshot {% #get-screenshot %}\n\nCapture a screenshot of a remote webpage with customizable browser settings and viewport options.\n\n{% multicode %}\n```client-web\nimport { Client, Avatars } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nconst avatars = new Avatars(client);\n\nconst result = avatars.getScreenshot({\n url: 'https://example.com'\n});\n\nconsole.log(result); // Resource URL\n```\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nfinal avatars = Avatars(client);\n\nFuture result = avatars.getScreenshot(\n url: 'https://example.com'\n).then((bytes) {\n // Use the screenshot image bytes\n return bytes;\n}).catchError((error) {\n print(error.response);\n});\n```\n```client-apple\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nlet avatars = Avatars(client)\n\nlet byteBuffer = try await avatars.getScreenshot(\n url: \"https://example.com\"\n)\n```\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Avatars\n\nval client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nval avatars = Avatars(client)\n\nval result = avatars.getScreenshot(\n url = \"https://example.com\"\n)\n```\n```client-react-native\nimport { Client, Avatars } from 'react-native-appwrite';\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nconst avatars = new Avatars(client);\n\nconst result = avatars.getScreenshot({\n url: 'https://example.com'\n});\n\nconsole.log(result); // Resource URL\n```\n{% /multicode %}\n\n# Parameters {% #parameters %}\n\nThe `getScreenshot` method accepts the following parameters:\n\n| Parameter | Type | Description |\n| --------- | ---- | ----------- |\n| url | string | The URL of the website to capture. Must be a valid HTTP or HTTPS URL. |\n| headers | object | HTTP headers to send with the browser request. Pass a key-value object with custom headers. Defaults to `{}` if not provided. |\n| viewportWidth | integer | The width of the browser viewport in pixels. Accepts values between `1-1920`. Defaults to `1280` if not provided. |\n| viewportHeight | integer | The height of the browser viewport in pixels. Accepts values between `1-1080`. Defaults to `720` if not provided. |\n| 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. |\n| theme | string | The browser color scheme theme. Accepts: `light` or `dark`. Defaults to `light` if not provided. |\n| userAgent | string | A custom user agent string for the browser request. Defaults to browser default if not provided. |\n| fullpage | boolean | Capture the full scrollable page (`true`) or only the viewport (`false`). Defaults to `false` if not provided. |\n| locale | string | The browser locale code (e.g., `en-US`, `fr-FR`). Defaults to browser default if not provided. |\n| timezone | string | IANA timezone identifier (e.g., `America/New_York`, `Europe/London`). Defaults to browser default if not provided. |\n| latitude | float | Geolocation latitude for the screenshot. Accepts values between `-90` to `90`. Defaults to `0` if not provided. |\n| longitude | float | Geolocation longitude for the screenshot. Accepts values between `-180` to `180`. Defaults to `0` if not provided. |\n| accuracy | float | Geolocation accuracy in meters. Accepts values between `0-100000`. Defaults to `0` if not provided. |\n| touch | boolean | Enable touch device support (`true`) or disable it (`false`). Defaults to `false` if not provided. |\n| 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. |\n| sleep | integer | Wait time in seconds before taking the screenshot. Accepts values between `0-10`. Defaults to `0` if not provided. |\n| 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. |\n| 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. |\n| quality | integer | Screenshot quality. Accepts values between `0-100`. `-1` preserves original image quality. Defaults to `-1` if not provided. |\n| output | string | Output image format. Supported formats: `jpg`, `jpeg`, `png`, `gif`, `webp`. Defaults to `png` if not provided. |\n\n# Viewport customization {% #viewport-customization %}\n\nControl the browser viewport to capture web pages as they would appear on different devices and screen sizes.\n\n{% multicode %}\n```client-web\n// Desktop viewport\nconst desktopScreenshot = avatars.getScreenshot({\n url: 'https://example.com',\n viewportWidth: 1920,\n viewportHeight: 1080\n});\n\n// Tablet viewport\nconst tabletScreenshot = avatars.getScreenshot({\n url: 'https://example.com',\n viewportWidth: 768,\n viewportHeight: 1024\n});\n\n// Mobile viewport\nconst mobileScreenshot = avatars.getScreenshot({\n url: 'https://example.com',\n viewportWidth: 375,\n viewportHeight: 667\n});\n\n// Full page capture\nconst fullpageScreenshot = avatars.getScreenshot({\n url: 'https://example.com',\n fullpage: true\n});\n```\n```client-flutter\n// Desktop viewport\nFuture desktopScreenshot = avatars.getScreenshot(\n url: 'https://example.com',\n viewportWidth: 1920,\n viewportHeight: 1080\n);\n\n// Tablet viewport\nFuture tabletScreenshot = avatars.getScreenshot(\n url: 'https://example.com',\n viewportWidth: 768,\n viewportHeight: 1024\n);\n\n// Mobile viewport\nFuture mobileScreenshot = avatars.getScreenshot(\n url: 'https://example.com',\n viewportWidth: 375,\n viewportHeight: 667\n);\n\n// Full page capture\nFuture fullpageScreenshot = avatars.getScreenshot(\n url: 'https://example.com',\n fullpage: true\n);\n```\n```client-apple\n// Desktop viewport\nlet desktopScreenshot = try await avatars.getScreenshot(\n url: \"https://example.com\",\n viewportWidth: 1920,\n viewportHeight: 1080\n)\n\n// Tablet viewport\nlet tabletScreenshot = try await avatars.getScreenshot(\n url: \"https://example.com\",\n viewportWidth: 768,\n viewportHeight: 1024\n)\n\n// Mobile viewport\nlet mobileScreenshot = try await avatars.getScreenshot(\n url: \"https://example.com\",\n viewportWidth: 375,\n viewportHeight: 667\n)\n\n// Full page capture\nlet fullpageScreenshot = try await avatars.getScreenshot(\n url: \"https://example.com\",\n fullpage: true\n)\n```\n```client-android-kotlin\n// Desktop viewport\nval desktopScreenshot = avatars.getScreenshot(\n url = \"https://example.com\",\n viewportWidth = 1920,\n viewportHeight = 1080\n)\n\n// Tablet viewport\nval tabletScreenshot = avatars.getScreenshot(\n url = \"https://example.com\",\n viewportWidth = 768,\n viewportHeight = 1024\n)\n\n// Mobile viewport\nval mobileScreenshot = avatars.getScreenshot(\n url = \"https://example.com\",\n viewportWidth = 375,\n viewportHeight = 667\n)\n\n// Full page capture\nval fullpageScreenshot = avatars.getScreenshot(\n url = \"https://example.com\",\n fullpage = true\n)\n```\n{% /multicode %}\n\n# Browser customization {% #browser-customization %}\n\nCustomize browser settings like theme, user agent, locale, and timezone to simulate different browser environments.\n\n{% multicode %}\n```client-web\nimport { Client, Avatars, Theme, Timezone } from \"appwrite\";\n\n// Dark theme screenshot\nconst darkThemeScreenshot = avatars.getScreenshot({\n url: 'https://example.com',\n theme: Theme.Dark\n});\n\n// Custom locale and timezone\nconst localizedScreenshot = avatars.getScreenshot({\n url: 'https://example.com',\n locale: 'fr-FR',\n timezone: Timezone.EuropeParis\n});\n\n// Custom user agent\nconst chromeScreenshot = avatars.getScreenshot({\n url: 'https://example.com',\n userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'\n});\n\n// Touch device\nconst touchScreenshot = avatars.getScreenshot({\n url: 'https://example.com',\n touch: true\n});\n```\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\n// Dark theme screenshot\nFuture darkThemeScreenshot = avatars.getScreenshot(\n url: 'https://example.com',\n theme: Theme.dark\n);\n\n// Custom locale and timezone\nFuture localizedScreenshot = avatars.getScreenshot(\n url: 'https://example.com',\n locale: 'fr-FR',\n timezone: Timezone.europeParis\n);\n\n// Custom user agent\nFuture chromeScreenshot = avatars.getScreenshot(\n url: 'https://example.com',\n userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) AppleWebKit/605.1.15'\n);\n\n// Touch device\nFuture touchScreenshot = avatars.getScreenshot(\n url: 'https://example.com',\n touch: true\n);\n```\n```client-apple\nimport Appwrite\n\n// Dark theme screenshot\nlet darkThemeScreenshot = try await avatars.getScreenshot(\n url: \"https://example.com\",\n theme: Theme.dark\n)\n\n// Custom locale and timezone\nlet localizedScreenshot = try await avatars.getScreenshot(\n url: \"https://example.com\",\n locale: \"fr-FR\",\n timezone: Timezone.europeParis\n)\n\n// Custom user agent\nlet chromeScreenshot = try await avatars.getScreenshot(\n url: \"https://example.com\",\n userAgent: \"Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) AppleWebKit/605.1.15\"\n)\n\n// Touch device\nlet touchScreenshot = try await avatars.getScreenshot(\n url: \"https://example.com\",\n touch: true\n)\n```\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Avatars\nimport io.appwrite.enums.Theme\nimport io.appwrite.enums.Timezone\n\n// Dark theme screenshot\nval darkThemeScreenshot = avatars.getScreenshot(\n url = \"https://example.com\",\n theme = Theme.DARK\n)\n\n// Custom locale and timezone\nval localizedScreenshot = avatars.getScreenshot(\n url = \"https://example.com\",\n locale = \"fr-FR\",\n timezone = Timezone.EUROPE_PARIS\n)\n\n// Custom user agent\nval chromeScreenshot = avatars.getScreenshot(\n url = \"https://example.com\",\n userAgent = \"Mozilla/5.0 (Linux; Android 10) AppleWebKit/537.36\"\n)\n\n// Touch device\nval touchScreenshot = avatars.getScreenshot(\n url = \"https://example.com\",\n touch = true\n)\n```\n{% /multicode %}\n\n# Geolocation simulation {% #geolocation-simulation %}\n\nSimulate different geographic locations to see how web pages render with location-based content and features.\n\n{% multicode %}\n```client-web\nimport { Client, Avatars, Timezone } from \"appwrite\";\n\n// New York location\nconst nyScreenshot = avatars.getScreenshot({\n url: 'https://example.com',\n permissions: [\"geolocation\"],\n latitude: 40.7128,\n longitude: -74.0060,\n accuracy: 100,\n timezone: Timezone.AmericaNewYork\n});\n\n// London location\nconst londonScreenshot = avatars.getScreenshot({\n url: 'https://example.com',\n permissions: [\"geolocation\"],\n latitude: 51.5074,\n longitude: -0.1278,\n accuracy: 100,\n timezone: Timezone.EuropeLondon\n});\n\n// Tokyo location\nconst tokyoScreenshot = avatars.getScreenshot({\n url: 'https://example.com',\n permissions: [\"geolocation\"],\n latitude: 35.6762,\n longitude: 139.6503,\n accuracy: 100,\n timezone: Timezone.AsiaTokyo\n});\n```\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\n// New York location\nFuture nyScreenshot = avatars.getScreenshot(\n url: 'https://example.com',\n permissions: [\"geolocation\"],\n latitude: 40.7128,\n longitude: -74.0060,\n accuracy: 100,\n timezone: Timezone.americaNewYork\n);\n\n// London location\nFuture londonScreenshot = avatars.getScreenshot(\n url: 'https://example.com',\n permissions: [\"geolocation\"],\n latitude: 51.5074,\n longitude: -0.1278,\n accuracy: 100,\n timezone: Timezone.europeLondon\n);\n\n// Tokyo location\nFuture tokyoScreenshot = avatars.getScreenshot(\n url: 'https://example.com',\n permissions: [\"geolocation\"],\n latitude: 35.6762,\n longitude: 139.6503,\n accuracy: 100,\n timezone: Timezone.asiaTokyo\n);\n```\n```client-apple\nimport Appwrite\n\n// New York location\nlet nyScreenshot = try await avatars.getScreenshot(\n url: \"https://example.com\",\n permissions: [\"geolocation\"],\n latitude: 40.7128,\n longitude: -74.0060,\n accuracy: 100,\n timezone: Timezone.americaNewYork\n)\n\n// London location\nlet londonScreenshot = try await avatars.getScreenshot(\n url: \"https://example.com\",\n permissions: [\"geolocation\"],\n latitude: 51.5074,\n longitude: -0.1278,\n accuracy: 100,\n timezone: Timezone.europeLondon\n)\n\n// Tokyo location\nlet tokyoScreenshot = try await avatars.getScreenshot(\n url: \"https://example.com\",\n permissions: [\"geolocation\"],\n latitude: 35.6762,\n longitude: 139.6503,\n accuracy: 100,\n timezone: Timezone.asiaTokyo\n)\n```\n```client-android-kotlin\nimport io.appwrite.services.Avatars\nimport io.appwrite.enums.Timezone\n\n// New York location\nval nyScreenshot = avatars.getScreenshot(\n url = \"https://example.com\",\n permissions = listOf(\"geolocation\"),\n latitude = 40.7128,\n longitude = -74.0060,\n accuracy = 100,\n timezone = Timezone.AMERICA_NEW_YORK\n)\n\n// London location\nval londonScreenshot = avatars.getScreenshot(\n url = \"https://example.com\",\n permissions = listOf(\"geolocation\"),\n latitude = 51.5074,\n longitude = -0.1278,\n accuracy = 100,\n timezone = Timezone.EUROPE_LONDON\n)\n\n// Tokyo location\nval tokyoScreenshot = avatars.getScreenshot(\n url = \"https://example.com\",\n permissions = listOf(\"geolocation\"),\n latitude = 35.6762,\n longitude = 139.6503,\n accuracy = 100,\n timezone = Timezone.ASIA_TOKYO\n)\n```\n{% /multicode %}\n\n# Wait time before capture {% #wait-time %}\n\nUse 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.\n\n{% multicode %}\n```client-web\n// Wait for page to load fully\nconst delayedScreenshot = avatars.getScreenshot({\n url: 'https://example.com',\n sleep: 5 // Wait 5 seconds before capture\n});\n\n// Immediate capture\nconst instantScreenshot = avatars.getScreenshot({\n url: 'https://example.com',\n sleep: 0 // No wait time\n});\n```\n```client-flutter\n// Wait for page to load fully\nFuture delayedScreenshot = avatars.getScreenshot(\n url: 'https://example.com',\n sleep: 5 // Wait 5 seconds before capture\n);\n\n// Immediate capture\nFuture instantScreenshot = avatars.getScreenshot(\n url: 'https://example.com',\n sleep: 0 // No wait time\n);\n```\n```client-apple\n// Wait for page to load fully\nlet delayedScreenshot = try await avatars.getScreenshot(\n url: \"https://example.com\",\n sleep: 5 // Wait 5 seconds before capture\n)\n\n// Immediate capture\nlet instantScreenshot = try await avatars.getScreenshot(\n url: \"https://example.com\",\n sleep: 0 // No wait time\n)\n```\n```client-android-kotlin\n// Wait for page to load fully\nval delayedScreenshot = avatars.getScreenshot(\n url = \"https://example.com\",\n sleep = 5 // Wait 5 seconds before capture\n)\n\n// Immediate capture\nval instantScreenshot = avatars.getScreenshot(\n url = \"https://example.com\",\n sleep = 0 // No wait time\n)\n```\n{% /multicode %}\n\n# Browser permissions {% #browser-permissions %}\n\nGrant 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.\n\n## Available permissions\n\nThe following permissions can be granted to the browser session:\n\n| Permission | Description |\n| ---------- | ----------- |\n| `geolocation` | Access device location coordinates |\n| `camera` | Access camera for video/photo capture |\n| `microphone` | Access microphone for audio recording |\n| `notifications` | Send browser notifications |\n| `midi` | Access MIDI devices for music applications |\n| `push` | Receive push notifications |\n| `clipboard-read` | Read from clipboard |\n| `clipboard-write` | Write to clipboard |\n| `payment-handler` | Handle payment requests |\n| `usb` | Access USB devices |\n| `bluetooth` | Access Bluetooth devices |\n| `accelerometer` | Access device accelerometer sensor |\n| `gyroscope` | Access device gyroscope sensor |\n| `magnetometer` | Access device magnetometer sensor |\n| `ambient-light-sensor` | Access ambient light sensor |\n| `background-sync` | Sync data in the background |\n| `persistent-storage` | Use persistent storage |\n| `screen-wake-lock` | Prevent screen from sleeping |\n| `web-share` | Use Web Share API |\n| `xr-spatial-tracking` | Track spatial positioning for XR/VR |\n\nBy pre-granting permissions, you can capture screenshots of fully functional pages in their active state, rather than showing permission prompts or degraded experiences.\n\n{% multicode %}\n```client-web\nimport { Client, Avatars, Output } from \"appwrite\";\n\n// Grant geolocation permission\nconst geoScreenshot = avatars.getScreenshot({\n url: 'https://example.com/map',\n permissions: [\"geolocation\"],\n latitude: 40.7128,\n longitude: -74.0060,\n output: Output.Png\n});\n\n// Grant multiple permissions\nconst multiPermScreenshot = avatars.getScreenshot({\n url: 'https://example.com/video-call',\n permissions: [\"camera\", \"microphone\", \"notifications\"],\n output: Output.Webp\n});\n\n// Grant storage and sync permissions\nconst storageScreenshot = avatars.getScreenshot({\n url: 'https://example.com/offline-app',\n permissions: [\"persistent-storage\", \"background-sync\"],\n output: Output.Jpg\n});\n\n// No special permissions\nconst defaultScreenshot = avatars.getScreenshot({\n url: 'https://example.com',\n permissions: []\n});\n```\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\n// Grant geolocation permission\nFuture geoScreenshot = avatars.getScreenshot(\n url: 'https://example.com/map',\n permissions: [\"geolocation\"],\n latitude: 40.7128,\n longitude: -74.0060,\n output: Output.png\n);\n\n// Grant multiple permissions\nFuture multiPermScreenshot = avatars.getScreenshot(\n url: 'https://example.com/video-call',\n permissions: [\"camera\", \"microphone\", \"notifications\"],\n output: Output.webp\n);\n\n// Grant storage and sync permissions\nFuture storageScreenshot = avatars.getScreenshot(\n url: 'https://example.com/offline-app',\n permissions: [\"persistent-storage\", \"background-sync\"],\n output: Output.jpg\n);\n\n// No special permissions\nFuture defaultScreenshot = avatars.getScreenshot(\n url: 'https://example.com',\n permissions: []\n);\n```\n```client-apple\nimport Appwrite\n\n// Grant geolocation permission\nlet geoScreenshot = try await avatars.getScreenshot(\n url: \"https://example.com/map\",\n permissions: [\"geolocation\"],\n latitude: 40.7128,\n longitude: -74.0060,\n output: Output.png\n)\n\n// Grant multiple permissions\nlet multiPermScreenshot = try await avatars.getScreenshot(\n url: \"https://example.com/video-call\",\n permissions: [\"camera\", \"microphone\", \"notifications\"],\n output: Output.webp\n)\n\n// Grant storage and sync permissions\nlet storageScreenshot = try await avatars.getScreenshot(\n url: \"https://example.com/offline-app\",\n permissions: [\"persistent-storage\", \"background-sync\"],\n output: Output.jpg\n)\n\n// No special permissions\nlet defaultScreenshot = try await avatars.getScreenshot(\n url: \"https://example.com\",\n permissions: []\n)\n```\n```client-android-kotlin\nimport io.appwrite.services.Avatars\nimport io.appwrite.enums.Output\n\n// Grant geolocation permission\nval geoScreenshot = avatars.getScreenshot(\n url = \"https://example.com/map\",\n permissions = listOf(\"geolocation\"),\n latitude = 40.7128,\n longitude = -74.0060,\n output = Output.PNG\n)\n\n// Grant multiple permissions\nval multiPermScreenshot = avatars.getScreenshot(\n url = \"https://example.com/video-call\",\n permissions = listOf(\"camera\", \"microphone\", \"notifications\"),\n output = Output.WEBP\n)\n\n// Grant storage and sync permissions\nval storageScreenshot = avatars.getScreenshot(\n url = \"https://example.com/offline-app\",\n permissions = listOf(\"persistent-storage\", \"background-sync\"),\n output = Output.JPG\n)\n\n// No special permissions\nval defaultScreenshot = avatars.getScreenshot(\n url = \"https://example.com\",\n permissions = emptyList()\n)\n```\n{% /multicode %}\n\n# Use cases {% #use-cases %}\n\nScreenshots are commonly used for:\n\n- **Web documentation**: Automatically generate visual documentation of web applications and websites\n- **Link previews**: Display website previews in link cards and social sharing interfaces\n- **Performance testing**: Capture pages in different browser environments to verify responsive design\n- **Accessibility testing**: Generate screenshots with different viewport sizes and accessibility settings\n- **Localization testing**: Verify how pages render in different locales and timezones\n- **Browser compatibility**: Test how pages appear with different user agents and browser settings\n- **Content archival**: Create visual snapshots of web pages for archival and reference\n- **A/B testing**: Compare visual renderings of different page variants\n- **Automated reporting**: Generate visual reports and dashboards with live web page screenshots\n- **QA automation**: Verify visual consistency across different browser and device configurations"}, {"path": "docs/products/databases", "title": "Databases", "description": "Store and query structured data with Appwrite Databases. Databases provide performant and scalable storage for your application, business, and user data.", "content": "Appwrite Databases let you store and query structured data. \nDatabases provide high-performance and scalable data storage for your key application, business, and user data.\n\n{% info title=\"Looking for file storage?\" %}\nDatabases store data, if you need to store files like images, PDFs or videos, use [Appwrite Storage](/docs/products/storage).\n{% /info %}\n\nYou can organize data into databases, tables, and rows. You can also paginate, order, and query rows.\nFor complex business logic, Appwrite supports relationships to help you model your data.\n\n{% arrow_link href=\"/docs/products/databases/quick-start\" %}\nQuick start \n{% /arrow_link %}"}, {"path": "docs/products/databases/ai-suggestions", "title": "AI suggestions", "description": "Use AI suggestions to automatically generate database schemas. Learn how to create tables with recommended columns and indexes based on your table name and context.", "content": "AI suggestions generate columns and indexes for your tables based on the table name, existing database structure, and optional context you provide.\nThis feature analyzes your database to recommend appropriate schema designs that follow best practices.\n\n{% section #create-table step=1 title=\"Create table\" %}\nNavigate to **Databases** in the Appwrite Console, select your database, and click **Create table**.\n\nEnter a descriptive table name. AI suggestions will use this name to generate relevant columns and indexes.\n{% /section %}\n\n{% section #enable-suggestions step=2 title=\"Enable AI suggestions\" %}\nIn the table creation dialog, enable **AI suggestions**.\n\n{% only_dark %}\n![Enable AI suggestions](/images/docs/databases/dark/ai-suggestions-enable.avif)\n{% /only_dark %}\n{% only_light %}\n![Enable AI suggestions](/images/docs/databases/ai-suggestions-enable.avif)\n{% /only_light %}\n\nOptionally, 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.\"\n\nClick **Generate suggestions** to analyze your table name and database structure.\n{% /section %}\n\n{% section #review-suggestions step=3 title=\"Review suggestions\" %}\nAI suggestions will generate recommended columns and indexes for your table.\n\n{% only_dark %}\n![Review AI suggestions](/images/docs/databases/dark/ai-suggestions-review.avif)\n{% /only_dark %}\n{% only_light %}\n![Review AI suggestions](/images/docs/databases/ai-suggestions-review.avif)\n{% /only_light %}\n\nReview each suggested column:\n- Column name and type\n- Whether it's required or optional\n- Default values\n- Array configurations\n\nReview suggested indexes to optimize query performance.\n{% /section %}\n\n{% section #apply-changes step=4 title=\"Apply or modify\" %}\nYou can modify any suggestion before applying:\n- Edit column names, types, or configurations\n- Remove suggestions you don't need\n- Add additional columns manually\n\nClick **Apply** to apply your schema. The table will be created with your approved columns and indexes.\n\n{% info title=\"Manual columns\" %}\nYou can always add, modify, or remove [columns](/docs/products/databases/tables#columns) after table creation by navigating to your table's **Columns** tab.\n{% /info %}\n{% /section %}"}, {"path": "docs/products/databases/atomic-numeric-operations", "title": "Atomic numeric operations", "description": "Safely increment and decrement numeric fields without race conditions. Perfect for counters, quotas, inventory, and usage metrics in high-concurrency applications.", "content": "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.\n\nThese operations work with `integer`, `bigint`, and `float` columns. Use `bigint` columns when your counters or accumulators may exceed the 32-bit integer range.\n\n# How atomic operations work {% #how-atomic-operations-work %}\n\nInstead 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.\n\n**Traditional approach:**\n1. Fetch row → `{ likes: 42 }`\n2. Update client-side → `likes: 43`\n3. Write back → `{ likes: 43 }`\n\n**Atomic approach:**\n1. Call `incrementRowColumn()` with the column name and the value to increment by\n2. Server applies atomically → `likes: 43`\n\n# When to use atomic operations {% #when-to-use-atomic-operations %}\n\nAtomic numeric operations work well for:\n\n- **Social features**: Likes, follows, comment counts\n- **Usage metering**: API credits, storage quotas, request limits\n- **Game state**: Scores, lives, currency, experience points\n- **E-commerce**: Stock counts, inventory levels\n- **Workflow tracking**: Retry counts, progress indicators\n- **Rate limiting**: Request counters, usage tracking\n\n# Perform atomic operations {% #perform-atomic-operations %}\n\nUse the `incrementRowColumn` and `decrementRowColumn` methods to perform atomic numeric operations. The server will apply these changes atomically under concurrency control.\n\n## Increment a field {% #increment-field %}\n\n{% multicode %}\n```client-web\nimport { Client, TablesDB } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject(''); // Your project ID\n\nconst tablesDB = new TablesDB(client);\n\nconst result = await tablesDB.incrementRowColumn({\n databaseId: '',\n tableId: '',\n rowId: '',\n column: 'likes', // column\n value: 1 // value\n});\n```\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nfinal tablesDB = TablesDB(client);\n\nfinal row = await tablesDB.incrementRowColumn(\n databaseId: '',\n tableId: '',\n rowId: '',\n column: 'likes',\n value: 1\n);\n```\n```client-apple\nimport Appwrite\nimport AppwriteModels\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nlet tablesDB = TablesDB(client)\n\nlet row = try await tablesDB.incrementRowColumn(\n databaseId: \"\",\n tableId: \"\",\n rowId: \"\",\n column: \"likes\",\n value: 1\n)\n```\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.TablesDB\n\nval client = Client(applicationContext)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nval tablesDB = TablesDB(client)\n\nval row = tablesDB.incrementRowColumn(\n databaseId = \"\",\n tableId = \"\",\n rowId = \"\",\n column = \"likes\",\n value = 1\n)\n```\n```server-nodejs\nconst sdk = require('node-appwrite');\n\nconst client = new sdk.Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n .setKey(''); // Your secret API key\n\nconst tablesDB = new sdk.TablesDB(client);\n\nconst result = await tablesDB.incrementRowColumn({\n databaseId: '',\n tableId: '',\n rowId: '',\n column: 'likes', // column\n value: 1 // value\n});\n```\n```server-python\nfrom appwrite.client import Client\nfrom appwrite.services.tables_db import TablesDB\n\nclient = Client()\nclient.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint\nclient.set_project('') # Your project ID\nclient.set_key('') # Your secret API key\n\ntablesDB = TablesDB(client)\n\nresult = tablesDB.increment_row_column(\n database_id = '',\n table_id = '',\n row_id = '',\n column = 'likes', # column\n value = 1 # value\n)\n```\n```rust\nuse appwrite::Client;\nuse appwrite::services::tables_db::TablesDB;\n\nlet client = Client::new()\n .set_endpoint(\"https://.cloud.appwrite.io/v1\")\n .set_project(\"\")\n .set_key(\"\");\n\nlet tables_db = TablesDB::new(&client);\n\nlet result = tables_db.increment_row_column(\n \"\",\n \"\",\n \"\",\n \"likes\", // column\n Some(1.0), // value\n None, // max\n None, // transaction_id\n).await?;\n```\n```graphql\nmutation {\n databasesIncrementRowColumn(\n databaseId: \"\",\n tableId: \"\",\n rowId: \"\",\n column: \"likes\",\n value: 1\n ) {\n _id\n _tableId\n _databaseId\n _createdAt\n _updatedAt\n _permissions\n data\n }\n}\n```\n{% /multicode %}\n\n## Decrement a field {% #decrement-field %}\n\nUse the `decrementRowColumn` method to decrease numeric fields:\n\n{% multicode %}\n```client-web\nimport { Client, TablesDB } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject(''); // Your project ID\n\nconst tablesDB = new TablesDB(client);\n\nconst result = await tablesDB.decrementRowColumn({\n databaseId: '',\n tableId: '',\n rowId: '',\n column: 'credits', // column\n value: 5 // value\n});\n```\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nfinal tablesDB = TablesDB(client);\n\nfinal row = await tablesDB.decrementRowColumn(\n databaseId: '',\n tableId: '',\n rowId: '',\n column: 'credits',\n value: 5\n);\n```\n```client-apple\nimport Appwrite\nimport AppwriteModels\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nlet tablesDB = TablesDB(client)\n\nlet row = try await tablesDB.decrementRowColumn(\n databaseId: \"\",\n tableId: \"\",\n rowId: \"\",\n column: \"credits\",\n value: 5\n)\n```\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.TablesDB\n\nval client = Client(applicationContext)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nval tablesDB = TablesDB(client)\n\nval row = tablesDB.decrementRowColumn(\n databaseId = \"\",\n tableId = \"\",\n rowId = \"\",\n column = \"credits\",\n value = 5\n)\n```\n```server-nodejs\nconst sdk = require('node-appwrite');\n\nconst client = new sdk.Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n .setKey(''); // Your secret API key\n\nconst tablesDB = new sdk.TablesDB(client);\n\nconst result = await tablesDB.decrementRowColumn({\n databaseId: '',\n tableId: '',\n rowId: '',\n column: 'credits', // column\n value: 5 // value\n});\n```\n```server-python\nfrom appwrite.client import Client\nfrom appwrite.services.tables_db import TablesDB\n\nclient = Client()\nclient.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint\nclient.set_project('') # Your project ID\nclient.set_key('') # Your secret API key\n\ntablesDB = TablesDB(client)\n\nresult = tablesDB.decrement_row_column(\n database_id = '',\n table_id = '',\n row_id = '',\n column = 'credits', # column\n value = 5 # value\n)\n```\n```rust\nuse appwrite::Client;\nuse appwrite::services::tables_db::TablesDB;\n\nlet client = Client::new()\n .set_endpoint(\"https://.cloud.appwrite.io/v1\")\n .set_project(\"\")\n .set_key(\"\");\n\nlet tables_db = TablesDB::new(&client);\n\nlet result = tables_db.decrement_row_column(\n \"\",\n \"\",\n \"\",\n \"credits\", // column\n Some(5.0), // value\n None, // min\n None, // transaction_id\n).await?;\n```\n```graphql\nmutation {\n databasesDecrementRowColumn(\n databaseId: \"\",\n tableId: \"\",\n rowId: \"\",\n column: \"credits\",\n value: 5\n ) {\n _id\n _tableId\n _databaseId\n _createdAt\n _updatedAt\n _permissions\n data\n }\n}\n```\n{% /multicode %}\n\n# Set constraints and bounds {% #set-constraints-and-bounds %}\n\nYou 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:\n\n## Example with constraints {% #example-with-constraints %}\n\n{% multicode %}\n```client-web\n// Increment with maximum constraint\nconst result = await tablesDB.incrementRowColumn({\n databaseId: '',\n tableId: '',\n rowId: '',\n column: 'credits', // column\n value: 100, // value\n max: 1000 // max (optional)\n});\n\n// Decrement with minimum constraint\nconst result2 = await tablesDB.decrementRowColumn({\n databaseId: '',\n tableId: '',\n rowId: '',\n column: 'credits', // column\n value: 50, // value\n min: 0 // min (optional)\n});\n```\n```client-flutter\n// Increment with maximum constraint\nfinal row = await tablesDB.incrementRowColumn(\n databaseId: '',\n tableId: '',\n rowId: '',\n column: 'credits',\n value: 100,\n max: 1000\n);\n\n// Decrement with minimum constraint\nfinal row2 = await tablesDB.decrementRowColumn(\n databaseId: '',\n tableId: '',\n rowId: '',\n column: 'credits',\n value: 50,\n min: 0\n);\n```\n```client-apple\n// Increment with maximum constraint\nlet row = try await tablesDB.incrementRowColumn(\n databaseId: \"\",\n tableId: \"\",\n rowId: \"\",\n column: \"credits\",\n value: 100,\n max: 1000\n)\n\n// Decrement with minimum constraint\nlet row2 = try await tablesDB.decrementRowColumn(\n databaseId: \"\",\n tableId: \"\",\n rowId: \"\",\n column: \"credits\",\n value: 50,\n min: 0\n)\n```\n```client-android-kotlin\n// Increment with maximum constraint\nval row = tablesDB.incrementRowColumn(\n databaseId = \"\",\n tableId = \"\",\n rowId = \"\",\n column = \"credits\",\n value = 100,\n max = 1000\n)\n\n// Decrement with minimum constraint\nval row2 = tablesDB.decrementRowColumn(\n databaseId = \"\",\n tableId = \"\",\n rowId = \"\",\n column = \"credits\",\n value = 50,\n min = 0\n)\n```\n```server-nodejs\n// Increment with maximum constraint\nconst result = await tablesDB.incrementRowColumn({\n databaseId: '',\n tableId: '',\n rowId: '',\n column: 'credits', // column\n value: 100, // value\n max: 1000 // max (optional)\n});\n\n// Decrement with minimum constraint\nconst result2 = await tablesDB.decrementRowColumn({\n databaseId: '',\n tableId: '',\n rowId: '',\n column: 'credits', // column\n value: 50, // value\n min: 0 // min (optional)\n});\n```\n```server-python\n# Increment with maximum constraint\nresult = tablesDB.increment_row_column(\n database_id = '',\n table_id = '',\n row_id = '',\n column = 'credits', # column\n value = 100, # value\n max = 1000 # max (optional)\n)\n\n# Decrement with minimum constraint\nresult2 = tablesDB.decrement_row_column(\n database_id = '',\n table_id = '',\n row_id = '',\n column = 'credits', # column\n value = 50, # value\n min = 0 # min (optional)\n)\n```\n```rust\n// Increment with maximum constraint\nlet result = tables_db.increment_row_column(\n \"\",\n \"\",\n \"\",\n \"credits\", // column\n Some(100.0), // value\n Some(1000.0), // max (optional)\n None, // transaction_id\n).await?;\n\n// Decrement with minimum constraint\nlet result2 = tables_db.decrement_row_column(\n \"\",\n \"\",\n \"\",\n \"credits\", // column\n Some(50.0), // value\n Some(0.0), // min (optional)\n None, // transaction_id\n).await?;\n```\n{% /multicode %}\n\n# Follow best practices {% #follow-best-practices %}\n\n## Use for high-concurrency scenarios {% #use-for-high-concurrency-scenarios %}\n\nAtomic numeric operations are most beneficial when multiple users or processes might update the same numeric field simultaneously.\n\n## Combine with regular updates {% #combine-with-regular-updates %}\n\nFor complex updates that include both atomic operations and regular field changes, you'll need to use separate API calls:\n\n{% multicode %}\n```client-web\n// First, increment the likes atomically\nconst likeResult = await tablesDB.incrementRowColumn(\n '',\n '',\n '',\n 'likes', // column\n 1 // value\n);\n\n// Then, update other fields\nconst updateResult = await tablesDB.updateRow(\n '',\n '',\n '',\n {\n lastLikedBy: userId,\n lastLikedAt: new Date().toISOString()\n }\n);\n```\n```client-flutter\n// First, increment the likes atomically\nfinal likeResult = await tablesDB.incrementRowColumn(\n databaseId: '',\n tableId: '',\n rowId: '',\n column: 'likes',\n value: 1\n);\n\n// Then, update other fields\nfinal updateResult = await tablesDB.updateRow(\n databaseId: '',\n tableId: '',\n rowId: '',\n data: {\n 'lastLikedBy': userId,\n 'lastLikedAt': DateTime.now().toIso8601String()\n }\n);\n```\n```client-apple\n// First, increment the likes atomically\nlet likeResult = try await tablesDB.incrementRowColumn(\n databaseId: \"\",\n tableId: \"\",\n rowId: \"\",\n column: \"likes\",\n value: 1\n)\n\n// Then, update other fields\nlet updateResult = try await tablesDB.updateRow(\n databaseId: \"\",\n tableId: \"\",\n rowId: \"\",\n data: [\n \"lastLikedBy\": userId,\n \"lastLikedAt\": ISO8601DateFormatter().string(from: Date())\n ]\n)\n```\n```client-android-kotlin\n// First, increment the likes atomically\nval likeResult = tablesDB.incrementRowColumn(\n databaseId = \"\",\n tableId = \"\",\n rowId = \"\",\n column = \"likes\",\n value = 1\n)\n\n// Then, update other fields\nval updateResult = tablesDB.updateRow(\n databaseId = \"\",\n tableId = \"\",\n rowId = \"\",\n data = mapOf(\n \"lastLikedBy\" to userId,\n \"lastLikedAt\" to Instant.now().toString()\n )\n)\n```\n```server-nodejs\n// First, increment the likes atomically\nconst likeResult = await tablesDB.incrementRowColumn(\n '',\n '',\n '',\n 'likes', // column\n 1 // value\n);\n\n// Then, update other fields\nconst updateResult = await tablesDB.updateRow(\n '',\n '',\n '',\n {\n lastLikedBy: userId,\n lastLikedAt: new Date().toISOString()\n }\n);\n```\n```server-python\n# First, increment the likes atomically\nlike_result = tablesDB.increment_row_column(\n database_id = '',\n table_id = '',\n row_id = '',\n column = 'likes', # column\n value = 1 # value\n)\n\n# Then, update other fields\nupdate_result = tablesDB.update_row(\n database_id = '',\n table_id = '',\n row_id = '',\n data = {\n 'lastLikedBy': user_id,\n 'lastLikedAt': datetime.now().isoformat()\n }\n)\n```\n```rust\nuse serde_json::json;\n\n// First, increment the likes atomically\nlet like_result = tables_db.increment_row_column(\n \"\",\n \"\",\n \"\",\n \"likes\", // column\n Some(1.0), // value\n None, // max\n None, // transaction_id\n).await?;\n\n// Then, update other fields\nlet update_result = tables_db.update_row(\n \"\",\n \"\",\n \"\",\n Some(json!({\n \"lastLikedBy\": user_id,\n \"lastLikedAt\": chrono::Utc::now().to_rfc3339()\n })),\n None, // permissions\n None, // transaction_id\n).await?;\n```\n{% /multicode %}\n\n# Use transactions {% #use-transactions %}\n\nAtomic numeric operations accept `transactionId`. When provided, increments/decrements are staged and applied on commit.\n\n{% multicode %}\n```client-web\nawait tablesDB.incrementRowColumn({\n databaseId: '',\n tableId: '',\n rowId: '',\n column: 'likes',\n value: 1,\n transactionId: ''\n});\n```\n```client-flutter\nawait tablesDB.incrementRowColumn(\n databaseId: '',\n tableId: '',\n rowId: '',\n column: 'likes',\n value: 1,\n transactionId: ''\n);\n```\n```client-apple\ntry await tablesDB.incrementRowColumn(\n databaseId: \"\",\n tableId: \"\",\n rowId: \"\",\n column: \"likes\",\n value: 1,\n transactionId: \"\"\n)\n```\n```client-android-kotlin\ntablesDB.incrementRowColumn(\n databaseId = \"\",\n tableId = \"\",\n rowId = \"\",\n column = \"likes\",\n value = 1,\n transactionId = \"\"\n)\n```\n```client-android-java\ntablesDB.incrementRowColumn(\n \"\",\n \"\",\n \"\",\n \"likes\",\n 1,\n \"\",\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return null;\n }\n System.out.println(result);\n return null;\n })\n);\n```\n```client-react-native\nawait tablesDB.incrementRowColumn({\n databaseId: '',\n tableId: '',\n rowId: '',\n column: 'likes',\n value: 1,\n transactionId: ''\n});\n```\n```server-nodejs\nawait tablesDB.incrementRowColumn({\n databaseId: '',\n tableId: '',\n rowId: '',\n column: 'likes',\n value: 1,\n transactionId: ''\n});\n```\n```server-deno\nawait tablesDB.incrementRowColumn({\n databaseId: '',\n tableId: '',\n rowId: '',\n column: 'likes',\n value: 1,\n transactionId: ''\n});\n```\n```server-python\ntablesDB.increment_row_column(\n database_id = '',\n table_id = '',\n row_id = '',\n column = 'likes',\n value = 1,\n transaction_id = ''\n)\n```\n```rust\ntables_db.increment_row_column(\n \"\",\n \"\",\n \"\",\n \"likes\",\n Some(1.0),\n None, // max\n Some(\"\"), // transaction_id\n).await?;\n```\n```server-php\n$tablesDB->incrementRowColumn(\n databaseId: '',\n tableId: '',\n rowId: '',\n column: 'likes',\n value: 1,\n transactionId: ''\n);\n```\n```server-ruby\ntablesDB.increment_row_column(\n database_id: '',\n table_id: '',\n row_id: '',\n column: 'likes',\n value: 1,\n transaction_id: ''\n)\n```\n```server-dotnet\nawait tablesDB.IncrementRowColumn(\n databaseId: \"\",\n tableId: \"\",\n rowId: \"\",\n column: \"likes\",\n value: 1,\n transactionId: \"\"\n);\n```\n```server-dart\nawait tablesDB.incrementRowColumn(\n databaseId: '',\n tableId: '',\n rowId: '',\n column: 'likes',\n value: 1,\n transactionId: ''\n);\n```\n```server-swift\ntry await tablesDB.incrementRowColumn(\n databaseId: \"\",\n tableId: \"\",\n rowId: \"\",\n column: \"likes\",\n value: 1,\n transactionId: \"\"\n)\n```\n```server-kotlin\ntablesDB.incrementRowColumn(\n databaseId = \"\",\n tableId = \"\",\n rowId = \"\",\n column = \"likes\",\n value = 1,\n transactionId = \"\"\n)\n```\n```server-java\ntablesDB.incrementRowColumn(\n \"\",\n \"\",\n \"\",\n \"likes\",\n 1,\n \"\",\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return null;\n }\n System.out.println(result);\n return null;\n })\n);\n```\n{% /multicode %}\n\n## Explore related features\n\n- [Bulk operations](/docs/products/databases/bulk-operations) - Update multiple rows at once\n- [Permissions](/docs/products/databases/permissions) - Control access to rows\n- [Queries](/docs/products/databases/queries) - Find rows to update\n- [Relationships](/docs/products/databases/relationships) - Update related rows"}, {"path": "docs/products/databases/backups", "title": "Backups", "description": "Learn how to efficiently back up your databases on Appwrite Cloud, ensuring data security and seamless recovery.", "content": "Appwrite Backups enable seamless, **encrypted** database backups on Cloud.\nAll backups are **hot** backups, ensuring zero downtime and fast recovery.\nLearn how to efficiently back up your databases to ensure data security and smooth recovery.\n\n{% info title=\"Backups are available on Appwrite Cloud for all Pro, Scale, and Enterprise customers.\" %}\n{% /info %}\n\nAppwrite 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.\n\n# Backup policies {% #backup-policies %}\n\nBackup policies allow you to automate your backup process. The Scale and Enterprise plans allow for more customization and offer options like how often backups should occur, how long they should be retained, and when they should run.\n\n## Creating a backup policy {% #creating-backup-policy %}\n\nTo automate your database backups, you need to create backup policies that run at scheduled intervals.\n\n{% only_dark %}\n![Create databases screen](/images/docs/databases/dark/databases.avif)\n{% /only_dark %}\n{% only_light %}\n![Create databases screen](/images/docs/databases/databases.avif)\n{% /only_light %}\n\n1. In the Appwrite Console's sidebar, click **Databases**\n2. Create or select & navigate to your database and click on the **Backups** Tab\n3. Click on **Create Policies** & select a pre-defined policy\\\n \n * On a **Pro** plan, you get access to a Daily backup policy\n {% only_dark %}\n ![Pro plan policy](/images/docs/databases/dark/pro-policy.avif)\n {% /only_dark %}\n {% only_light %}\n ![Pro plan policy](/images/docs/databases/pro-policy.avif)\n {% /only_light %}\n\n * On **Scale** and **Enterprise** plans, you get access to more & custom policies\\\n  \n * Select a pre-defined policy\n {% only_dark %}\n ![Scale plan policies](/images/docs/databases/dark/scale-policies.avif)\n {% /only_dark %}\n {% only_light %}\n ![Scale plan policies](/images/docs/databases/scale-policies.avif)\n {% /only_light %}\n * Or create a custom policy and adjust the settings as you like\n {% only_dark %}\n ![Custom policies for Scale plan](/images/docs/databases/dark/scale-custom-policies.avif)\n {% /only_dark %}\n {% only_light %}\n ![Custom policies for Scale plan](/images/docs/databases/scale-custom-policies.avif)\n {% /only_light %}\n\n4. Click on **Create**\n\nYour database is now set up for automated backups with just a few clicks.\nNote that you can always navigate to the same tab and click **Create Manual** to create a backup on-demand.\n\n# Manual backups {% #manual-backups %}\n\nYou can always create an on-demand backup whenever necessary.\n\n{% only_dark %}\n![Manual backup](/images/docs/databases/dark/manual-backup.avif)\n{% /only_dark %}\n{% only_light %}\n![Manual backup](/images/docs/databases/manual-backup.avif)\n{% /only_light %}\n\n1. In the Appwrite Console's sidebar, click **Databases**\n2. Select & navigate to your database and click on the **Backups** Tab\n3. Click on **Manual Backup**\n\nDepending on the size of your database, the backup process may take some time to complete.\nYou can monitor its progress via the floating status bar at the bottom of your screen.\n\n# Restoring backups {% #restoring-backups %}\n\nTo restore a database, you must have a backup of the database you want to restore.\n\n{% only_dark %}\n![Restore databases](/images/docs/databases/dark/restore.avif)\n{% /only_dark %}\n{% only_light %}\n![Restore databases](/images/docs/databases/restore.avif)\n{% /only_light %}\n\n1. In the Appwrite Console's sidebar, click **Databases**\n2. Select & navigate to your database and click on the **Backups** Tab\n3. Click on the options menu in the far corner of your backup\n4. In the dropdown menu, click **Restore**.\n5. Enter the new database name and an optional database ID\n6. Click **Restore**\n\nDepending on the size of your database, the restoration process may take some time.\nYou can observe its status in a floating bar across your project.\n\n# Backup security & performance {% #backup-security-and-performance %}\n\nAll backups created with Appwrite are:\n\n1. **Encrypted**:\n All backups are securely encrypted to ensure your data remains protected at all times.\n\n2. **Remotely stored**:\n Backups are stored in a remote location, providing an additional layer of security and ensuring your data is always recoverable.\n\n3. **Hot backups**:\n Backups are hot, meaning they occur with zero downtime, allowing you to recover data quickly without interrupting your projects and services.\n\n# Best practices {% #best-practices %}\n\nTo ensure your backups are robust and effective, consider the following best practices:\n\n1. **Schedule regular backups**:\n Add multiple backup policies based on the frequency of database changes. Daily or weekly backups are often sufficient for most use cases.\n\n2. **Retain critical backups longer**:\n Use custom policies with longer retention to keep backups of critical data for extended periods, ensuring historical records are available when needed.\n\n3. **Optimize backup policies based on data sensitivity**:\n Tailor your backup frequency and retention settings according to the sensitivity and importance of the data.\n Critical data may require more frequent backups, while less essential data can have longer retention and fewer backups."}, {"path": "docs/products/databases/bulk-operations", "title": "Bulk operations", "description": "Perform bulk operations on rows within your tables for efficient data handling.", "content": "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.\n\nBulk 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.\n\nFor client applications that need bulk-like functionality, consider using [Appwrite Functions](/docs/products/functions) with proper rate limiting and validation.\n\n{% info title=\"Important notes\" %}\n- 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.\n- Tables that contain relationship columns are not supported via bulk operations. Use individual row operations for tables with relationships.\n{% /info %}\n\n# Atomic behavior {% #atomic-behavior %}\n\nBulk 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.\n\nThis atomicity ensures:\n- **Data consistency**: Your database remains in a consistent state even if some operations would fail.\n- **Race condition prevention**: Multiple clients can safely perform bulk operations simultaneously.\n- **Simplified error handling**: You only need to handle complete success or complete failure scenarios.\n\nFor example, if you attempt to create 100 rows and one fails due to a validation error, none of the 100 rows will be created.\n\n# Plan limits {% #plan-limits %}\n\nBulk operations have different limits based on your Appwrite plan:\n\n| Plan | Rows per request |\n|------|----------------------|\n| Free | 100 |\n| Pro | 1,000 |\n\nThese 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.\n\n# Create rows {% #create-rows %}\n\nYou can create multiple rows in a single request using the `createRows` method.\n\n{% info title=\"Custom timestamps\" %}\nWhen 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.\n{% /info %}\n\n{% multicode %}\n```server-nodejs\nconst sdk = require('node-appwrite');\n\nconst client = new sdk.Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nconst tablesDB = new sdk.TablesDB(client);\n\nconst result = await tablesDB.createRows(\n '',\n '',\n [\n {\n $id: sdk.ID.unique(),\n name: 'Row 1'\n },\n {\n $id: sdk.ID.unique(),\n name: 'Row 2'\n }\n ]\n);\n```\n\n```server-python\nfrom appwrite.client import Client\nfrom appwrite.services.tables_db import TablesDB\n\nclient = Client()\nclient.set_endpoint('https://.cloud.appwrite.io/v1')\nclient.set_project('')\nclient.set_key('')\n\ntablesDB = TablesDB(client)\n\nresult = tablesDB.create_rows(\n database_id = '',\n table_id = '',\n rows = [\n {\n '$id': appwrite.ID.unique(),\n 'name': 'Row 1'\n },\n {\n '$id': appwrite.ID.unique(),\n 'name': 'Row 2'\n }\n ]\n)\n```\n\n```rust\nuse appwrite::Client;\nuse appwrite::services::tables_db::TablesDB;\nuse appwrite::id::ID;\nuse serde_json::json;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box> {\n let client = Client::new()\n .set_endpoint(\"https://.cloud.appwrite.io/v1\")\n .set_project(\"\")\n .set_key(\"\");\n\n let tables_db = TablesDB::new(&client);\n\n let result = tables_db.create_rows(\n \"\",\n \"\",\n vec![\n json!({\n \"$id\": ID::unique(),\n \"name\": \"Row 1\"\n }),\n json!({\n \"$id\": ID::unique(),\n \"name\": \"Row 2\"\n }),\n ],\n None,\n ).await?;\n\n Ok(())\n}\n```\n{% /multicode %}\n\n# Update rows {% #update-rows %}\n\n{% info title=\"Permissions required\" %}\nYou must grant **update** permissions to users at the **table level** before users can update rows.\n[Learn more about permissions](/docs/products/databases/permissions)\n{% /info %}\n\nYou can update multiple rows in a single request using the `updateRows` method.\n\n{% multicode %}\n```server-nodejs\nconst sdk = require('node-appwrite');\n\nconst client = new sdk.Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nconst tablesDB = new sdk.TablesDB(client);\n\nconst result = await tablesDB.updateRows(\n '',\n '',\n {\n status: 'published'\n },\n [\n sdk.Query.equal('status', 'draft')\n ]\n);\n```\n\n```server-python\nfrom appwrite.client import Client\nfrom appwrite.services.tables_db import TablesDB\n\nclient = Client()\nclient.set_endpoint('https://.cloud.appwrite.io/v1')\nclient.set_project('')\nclient.set_key('')\n\ntablesDB = TablesDB(client)\n\nresult = tablesDB.update_rows(\n database_id = '',\n table_id = '',\n data = {\n 'status': 'published'\n },\n queries = [\n Query.equal('status', 'draft')\n ]\n)\n```\n\n```rust\nuse appwrite::Client;\nuse appwrite::services::tables_db::TablesDB;\nuse appwrite::query::Query;\nuse serde_json::json;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box> {\n let client = Client::new()\n .set_endpoint(\"https://.cloud.appwrite.io/v1\")\n .set_project(\"\")\n .set_key(\"\");\n\n let tables_db = TablesDB::new(&client);\n\n let result = tables_db.update_rows(\n \"\",\n \"\",\n Some(json!({\n \"status\": \"published\"\n })),\n Some(vec![\n Query::equal(\"status\", \"draft\").to_string(),\n ]),\n None,\n ).await?;\n\n Ok(())\n}\n```\n{% /multicode %}\n\n# Upsert rows {% #upsert-rows %}\n\n{% info title=\"Permissions required\" %}\nYou must grant **create** and **update** permissions to users at the **table level** before users can create rows.\n[Learn more about permissions](/docs/products/databases/permissions)\n{% /info %}\n\nYou can upsert multiple rows in a single request using the `upsertRows(` method.\n\n{% multicode %}\n```server-nodejs\nconst sdk = require('node-appwrite');\n\nconst client = new sdk.Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nconst tablesDB = new sdk.TablesDB(client);\n\nconst result = await tablesDB.upsertRows(\n '',\n '',\n [\n {\n $id: sdk.ID.unique(),\n name: 'New Row 1'\n },\n {\n $id: 'row-id-2', // Existing row ID\n name: 'New Row 2'\n }\n ]\n);\n```\n\n```server-python\nfrom appwrite.client import Client\nfrom appwrite.services.tables_db import TablesDB\nfrom appwrite.id import ID\n\nclient = Client()\nclient.set_endpoint('https://.cloud.appwrite.io/v1')\nclient.set_project('')\nclient.set_key('')\n\ntablesDB = TablesDB(client)\n\nresult = tablesDB.upsert_rows(\n database_id = '',\n table_id = '',\n rows = [\n {\n '$id': appwrite.ID.unique(),\n 'name': 'Row 1'\n },\n {\n '$id': 'row-id-2', # Existing row ID\n 'name': 'New Row 2'\n }\n ]\n)\n```\n\n```rust\nuse appwrite::Client;\nuse appwrite::services::tables_db::TablesDB;\nuse appwrite::id::ID;\nuse serde_json::json;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box> {\n let client = Client::new()\n .set_endpoint(\"https://.cloud.appwrite.io/v1\")\n .set_project(\"\")\n .set_key(\"\");\n\n let tables_db = TablesDB::new(&client);\n\n let result = tables_db.upsert_rows(\n \"\",\n \"\",\n vec![\n json!({\n \"$id\": ID::unique(),\n \"name\": \"New Row 1\"\n }),\n json!({\n \"$id\": \"row-id-2\", // Existing row ID\n \"name\": \"New Row 2\"\n }),\n ],\n None,\n ).await?;\n\n Ok(())\n}\n```\n{% /multicode %}\n\n# Delete rows {% #delete-rows %}\n\n{% info title=\"Permissions required\" %}\nYou must grant **delete** permissions to users at the **table level** before users can delete rows.\n[Learn more about permissions](/docs/products/databases/permissions)\n{% /info %}\n\nYou can delete multiple rows in a single request using the `deleteRows` method.\n\n{% multicode %}\n```server-nodejs\nconst sdk = require('node-appwrite');\n\nconst client = new sdk.Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nconst tablesDB = new sdk.TablesDB(client);\n\nconst result = await tablesDB.deleteRows(\n '',\n '',\n [\n sdk.Query.equal('status', 'archived')\n ]\n);\n```\n\n```server-python\nfrom appwrite.client import Client\nfrom appwrite.services.tables_db import TablesDB\nfrom appwrite.query import Query\n\nclient = Client()\nclient.set_endpoint('https://.cloud.appwrite.io/v1')\nclient.set_project('')\nclient.set_key('')\n\ntablesDB = TablesDB(client)\n\nresult = tablesDB.delete_rows(\n database_id = '',\n table_id = '',\n queries = [\n Query.equal('status', 'archived')\n ]\n)\n```\n\n```rust\nuse appwrite::Client;\nuse appwrite::services::tables_db::TablesDB;\nuse appwrite::query::Query;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box> {\n let client = Client::new()\n .set_endpoint(\"https://.cloud.appwrite.io/v1\")\n .set_project(\"\")\n .set_key(\"\");\n\n let tables_db = TablesDB::new(&client);\n\n let result = tables_db.delete_rows(\n \"\",\n \"\",\n Some(vec![\n Query::equal(\"status\", \"archived\").to_string(),\n ]),\n None,\n ).await?;\n\n Ok(())\n}\n```\n{% /multicode %}\n\n{% info title=\"Queries for deletion\" %}\n\nWhen deleting rows, you must specify queries to filter which rows to delete.\nIf no queries are provided, all rows in the table will be deleted.\n[Learn more about queries](/docs/products/databases/queries).\n\n{% /info %}\n\n# Use transactions {% #use-transactions %}\n\nAll bulk operations accept `transactionId`. When provided, Appwrite stages the bulk request and applies it on commit. See [Transactions](/docs/products/databases/transactions).\n\n{% multicode %}\n```server-nodejs\nawait tablesDB.createRows({\n databaseId: '',\n tableId: '',\n rows: [\n { $id: sdk.ID.unique(), name: 'One' },\n { $id: sdk.ID.unique(), name: 'Two' }\n ],\n transactionId: ''\n});\n```\n```server-python\ntablesDB.create_rows(\n database_id = '',\n table_id = '',\n rows = [\n { '$id': appwrite.ID.unique(), 'name': 'One' },\n { '$id': appwrite.ID.unique(), 'name': 'Two' }\n ],\n transaction_id = ''\n)\n```\n```server-deno\nawait tablesDB.createRows({\n databaseId: '',\n tableId: '',\n rows: [\n { $id: sdk.ID.unique(), name: 'One' },\n { $id: sdk.ID.unique(), name: 'Two' }\n ],\n transactionId: ''\n});\n```\n```server-php\n$tablesDB->createRows(\n databaseId: '',\n tableId: '',\n rows: [\n [ '$id' => ID::unique(), 'name' => 'One' ],\n [ '$id' => ID::unique(), 'name' => 'Two' ]\n ],\n transactionId: ''\n);\n```\n```server-ruby\ntablesDB.create_rows(\n database_id: '',\n table_id: '',\n rows: [\n { '$id' => ID.unique(), 'name' => 'One' },\n { '$id' => ID.unique(), 'name' => 'Two' }\n ],\n transaction_id: ''\n)\n```\n```server-dotnet\nawait tablesDB.CreateRows(\n databaseId: \"\",\n tableId: \"\",\n rows: new List>\n {\n new Dictionary\n {\n [\"$id\"] = ID.Unique(),\n [\"name\"] = \"One\"\n },\n new Dictionary\n {\n [\"$id\"] = ID.Unique(),\n [\"name\"] = \"Two\"\n }\n },\n transactionId: \"\"\n);\n```\n```server-dart\nawait tablesDB.createRows(\n databaseId: '',\n tableId: '',\n rows: [\n { '\\$id': ID.unique(), 'name': 'One' },\n { '\\$id': ID.unique(), 'name': 'Two' }\n ],\n transactionId: ''\n);\n```\n```server-swift\ntry await tablesDB.createRows(\n databaseId: \"\",\n tableId: \"\",\n rows: [\n [\"$id\": ID.unique(), \"name\": \"One\"],\n [\"$id\": ID.unique(), \"name\": \"Two\"]\n ],\n transactionId: \"\"\n)\n```\n```server-kotlin\ntablesDB.createRows(\n databaseId = \"\",\n tableId = \"\",\n rows = listOf(\n mapOf(\"\\$id\" to ID.unique(), \"name\" to \"One\"),\n mapOf(\"\\$id\" to ID.unique(), \"name\" to \"Two\")\n ),\n transactionId = \"\"\n)\n```\n```server-java\ntablesDB.createRows(\n \"\",\n \"\",\n Arrays.asList(\n Map.of(\n \"$id\", ID.unique(),\n \"name\", \"One\"\n ),\n Map.of(\n \"$id\", ID.unique(),\n \"name\", \"Two\"\n )\n ),\n \"\",\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return null;\n }\n System.out.println(result);\n return null;\n })\n);\n```\n```rust\nlet result = tables_db.create_rows(\n \"\",\n \"\",\n vec![\n json!({\n \"$id\": ID::unique(),\n \"name\": \"One\"\n }),\n json!({\n \"$id\": ID::unique(),\n \"name\": \"Two\"\n }),\n ],\n Some(\"\"),\n).await?;\n```\n{% /multicode %}"}, {"path": "docs/products/databases/csv-exports", "title": "CSV exports", "description": "Export table data to CSV files from the Console. Share clean datasets with your team without writing custom scripts.", "content": "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.\n\nThis feature is available in both Appwrite Cloud and the self-hosted version.\n\n# Export configuration {% #export-configuration %}\n\nBefore 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.\n\n## Apply filters {% #apply-filters %}\n\nYou 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.\n\n## Select columns {% #select-columns %}\n\nYou 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.\n\n{% info title=\"Good to know\" %}\nSystem columns like `$id`, `$createdAt`, and `$updatedAt` are automatically included in the export.\n{% /info %}\n\n## Custom delimiter {% #custom-delimiter %}\n\nYou 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.\n\nCommon delimiters:\n- **Comma (`,`)**: Standard format, compatible with most tools\n- **Tab**: Useful when your data contains many commas\n- **Semicolon (`;`)**: Common in European Excel versions\n- **Pipe (`|`)**: Useful when your data contains many semicolons\n\n## Header row {% #header-row %}\n\nYou 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.\n\n# Relationship handling {% #relationship-handling %}\n\nWhen 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.\n\nFor 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.\n\nAn example of exported data with relationships:\n\n```text\n$id,title,author_id,published\npost-1,Getting started with Appwrite,user-123,true\npost-2,Advanced queries,user-456,true\npost-3,CSV exports guide,user-123,false\n```\n\n# Timestamps {% #timestamps %}\n\nThe `$createdAt` and `$updatedAt` columns are exported in ISO 8601 format, making them compatible with most spreadsheet and database tools.\n\n# Permissions {% #permissions %}\n\nIf 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.\n\n```text\n$id,title,$permissions\npost-1,Public post,\"read(\"\"any\"\"),update(\"\"user:user-123\"\")\"\npost-2,Team post,\"read(\"\"team:team-456\"\"),update(\"\"team:team-456\"\")\"\n```\n\nThe roles used are API strings that can be found in the [permissions documentation](/docs/products/databases/permissions).\n\n# Background processing {% #background-processing %}\n\nLarge 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.\n\nThis 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.\n\n# Export from the Console {% #export-console %}\n\nTo export rows using the Appwrite Console:\n\n1. Go to your project and navigate to **Databases**\n2. Select your target database and navigate to your target table\n3. Click on the **Export CSV** button in the action area\n4. Configure your export options:\n - Choose which columns to include (optional)\n - Set your preferred delimiter\n - Choose whether to include headers\n5. Click **Export**\n\n{% only_dark %}\n![CSV export screen](/images/docs/databases/dark/csv-export.avif)\n{% /only_dark %}\n{% only_light %}\n![CSV export screen](/images/docs/databases/csv-export.avif)\n{% /only_light %}\n\nThe export will begin processing in the background. You'll see a progress indicator and receive an email when the export is ready to download.\n\n# Use cases {% #use-cases %}\n\nCSV exports are useful for many common workflows:\n\n- **Reporting**: Generate reports for stakeholders who need data in spreadsheet format\n- **Data sharing**: Share clean datasets with non-technical team members\n- **Analytics hand-off**: Provide datasets to analysts using BI tools\n- **Compliance exports**: Create audit trails and compliance records\n- **Custom backups**: Archive specific data subsets for record-keeping\n- **Migration preparation**: Extract data for migration to other systems\n\n# Best practices {% #best-practices %}\n\nTo get the most out of CSV exports:\n\n1. **Use filters to filter data**: Export only the rows you need to reduce file size and processing time\n2. **Select specific columns**: Choose relevant columns to create cleaner, more focused datasets\n4. **Choose appropriate delimiters**: Use tabs or semicolons if your data contains many commas\n5. **Consider header requirements**: Include headers for human readability, exclude them for automated imports\n\n# Additional resources {% #additional-resources %}\n\n- [CSV Imports](/docs/products/databases/csv-imports) - Import data from CSV files\n- [Relationships](/docs/products/databases/relationships) - Define connections between tables\n- [Database Permissions](/docs/products/databases/permissions) - Configure row-level security\n- [Database Backups](/docs/products/databases/backups) - Automated backup policies"}, {"path": "docs/products/databases/csv-imports", "title": "CSV imports", "description": "Master row imports with Appwrite's CSV Import feature. Learn how to create rows within your tables by uploading a CSV file.", "content": "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.\n\nThis feature is available in both Appwrite Cloud and the self-hosted version.\n\n# Prepare your table {% #prepare-table %}\n\nTo 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.\n\nEach column in the CSV should map to a column key in your table, and each row should represent a new row.\n\n{% info title=\"Good to know\" %}\nYou can optionally include the `$id` column to define custom row IDs. If not provided, Appwrite will generate unique IDs for each row automatically.\n\nAppwrite 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.\n{% /info %}\n\nAn example of a valid CSV file for a table with the following columns:\n- `title` (string)\n- `author` (string)\n- `year` (integer)\n- `available` (boolean)\n\n```text\n$id,title,author,year,available\nf3k91x8b2q,Harry Potter and the Sorcerer's Stone,J.K. Rowling,1997,true\nmz7lq3dp5c,The Fellowship of the Ring,J.R.R. Tolkien,1954,true\nx0v4p8ncq2,To Kill a Mockingbird,Harper Lee,1960,false\n```\n\n# Empty values {% #empty-values %}\n\nDifferent column types handle empty values differently:\n\n- **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.\n- **Integer columns:** Empty values are interpreted as `null`.\n- **Boolean columns:** Empty values are interpreted as `null`.\n- **Array of any type:** Empty values are interpreted as empty arrays.\n\n# Create and update timestamps {% #created-at-and-updated-at %}\n\nYou can also optionally include `$createdAt` and `$updatedAt` columns to set custom timestamps for imported rows. If omitted, Appwrite sets these automatically during import.\n\nAn example of a valid CSV file with `$createdAt` and `$updatedAt` timestamps:\n\n```text\n$id,$createdAt,$updatedAt,title,author,year,available\nf3k91x8b2q,2025-08-10T12:34:56.000Z,2025-08-10T12:34:56.000Z,Harry Potter and the Sorcerer's Stone,J.K. Rowling,1997,true\nmz7lq3dp5c,2025-08-11T09:15:00.000Z,2025-08-11T10:00:00.000Z,The Fellowship of the Ring,J.R.R. Tolkien,1954,true\nx0v4p8ncq2,2025-08-12T08:00:00.000Z,2025-08-12T08:30:00.000Z,To Kill a Mockingbird,Harper Lee,1960,false\n```\n\n{% info title=\"Timestamps format\" %}\n`$createdAt` and `$updatedAt` must be valid ISO 8601 date-time strings, for example: `2025-08-10T12:34:56.000Z`.\n{% /info %}\n\n# Arrays {% #arrays %}\n\nYou 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\"`).\n\nAn example of a valid CSV file with an array column:\n\n```text\n$id,title,author,year,available,categories\nf3k91x8b2q,Harry Potter and the Sorcerer's Stone,J.K. Rowling,1997,true,\"fiction,fantasy\"\nmz7lq3dp5c,The Fellowship of the Ring,J.R.R. Tolkien,1954,true,\"fiction,fantasy\"\nx0v4p8ncq2,To Kill a Mockingbird,Harper Lee,1960,false,\"fiction,nonfiction\"\n```\n\n# Relationships {% #relationships %}\n\nIf you want to create relationships between rows in different tables, you can provide the `$id` of the related row in the related table.\n\nAn example of a valid CSV file with relationships between tables where `related_id` is the `$id` of the related row in the related table:\n\n```text\n$id,title,author,year,related_id\nf3k91x8b2q,Harry Potter and the Sorcerer's Stone,J.K. Rowling,1997,id_1\nmz7lq3dp5c,The Fellowship of the Ring,J.R.R. Tolkien,1954,id_2\nx0v4p8ncq2,To Kill a Mockingbird,Harper Lee,1960,id_3\n```\n\n# Permissions {% #permissions %}\n\nYou 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.\n\nAn example of a valid permissions string:\n\n```text\n\"read(\"\"any\"\"),update(\"\"users\"\"),delete(\"\"user:user_id\"\")\"\n```\n\nThe roles used are API strings that can be found in the [permissions documentation](/docs/apis/rest#roles).\n\nA full example of a valid CSV file with row permissions:\n\n```text\nname,$id,$permissions\nblog-post-1,post-1,\"read(\"\"any\"\"),update(\"\"user:user_id\"\"),delete(\"\"user:user_id\"\")\"\nblog-post-2,post-2,\"read(\"\"guests\"\"),update(\"\"user:user_id\"\")\"\nblog-post-3,post-3,\"read(\"\"users\"\"),update(\"\"user:user_id\"\"),delete(\"\"user:user_id\"\")\"\nblog-post-4,post-4,\"read(\"\"any\"\"),update(\"\"team:team_id\"\"),delete(\"\"team:team_id\"\")\"\n```\n\n# Special characters {% #special-characters %}\n\nIf your CSV file contains characters like double quotes (`\"`) or commas (`,`), you need to escape them. \n\n## Comma\n\nThere are different ways to escape commas based on different scenarios.\n\n- If your column type is a **text type** (varchar, text, mediumtext, or longtext), you can enclose the value in double quotes (`\"comma,used,here\"`).\n- 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\"\"\"`).\n\n\n## Double quotes\n\nIf 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.\n\n# Import rows from the Console {% #import-console %}\n\nTo import rows using the Appwrite Console:\n\n1. Go to your project -> Databases\n2. Navigate to your target Table\n3. Click on the **Import CSV** button in the action area\n4. Upload a new CSV file or choose an existing file from your Storage bucket\n\n{% only_dark %}\n![CSV import screen](/images/docs/databases/dark/csv-import.avif)\n{% /only_dark %}\n{% only_light %}\n![CSV import screen](/images/docs/databases/csv-import.avif)\n{% /only_light %}\n\nCSV imports run as background tasks. The Console displays a floating progress bar while the import is active.\n\n# Additional resources {% #additional-resources %}\n\n- [Appwrite CLI](/docs/command-line)\n- [Database Permissions](/docs/products/databases/permissions)\n- [Database API Reference](/docs/references/cloud/client-web/databases)"}, {"path": "docs/products/databases/databases", "title": "Databases", "description": "Dive deeper into Appwrite Databases and their configuration. Learn how to create, manage, and optimize multiple databases for your application.", "content": "Databases are the largest organizational unit in Appwrite.\nEach database contains a group of [tables](/docs/products/databases/tables).\nIn future versions, different databases may be backed by a different database technology of your choosing.\n\n# Create in Console {% #create-in-console %}\nThe easiest way to create a database using the Appwrite Console.\nYou can create a database by navigating to the **Databases** page and clicking **Create database**.\n\n\n\n# Create using Server SDKs {% #create-using-server-sdks %}\nYou can programmatically create databases using a [Server SDK](/docs/sdks#server). Appwrite [Server SDKs](/docs/sdks#server) require an [API key](/docs/advanced/security/api-keys).\n\n{% multicode %}\n```server-nodejs\nconst sdk = require('node-appwrite');\n\n// Init SDK\nconst client = new sdk.Client();\n\nconst tablesDB = new sdk.TablesDB(client);\n\nclient\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n;\n\nconst promise = tablesDB.create({\n databaseId: '',\n name: '[NAME]'\n});\n\npromise.then(function (response) {\n console.log(response);\n}, function (error) {\n console.log(error);\n});\n```\n```deno\nimport * as sdk from \"npm:node-appwrite\";\n\n// Init SDK\nlet client = new sdk.Client();\n\nlet tablesDB = new sdk.TablesDB(client);\n\nclient\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n;\n\n\nlet promise = tablesDB.create({\n databaseId: '',\n name: '[NAME]'\n});\n\npromise.then(function (response) {\n console.log(response);\n}, function (error) {\n console.log(error);\n});\n```\n```php\nsetEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n ->setProject('') // Your project ID\n ->setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n;\n\n$tablesDB = new TablesDB($client);\n\n$result = $tablesDB->create('', '');\n```\n```python\nfrom appwrite.client import Client\nfrom appwrite.services.tables_db import TablesDB\n\nclient = Client()\n\n(client\n .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('') # Your project ID\n .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key\n)\n\ntablesDB = TablesDB(client)\n\nresult = tablesDB.create('', '')\n```\n```ruby\nrequire 'Appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('') # Your project ID\n .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key\n\ntablesDB = TablesDB.new(client)\n\nresponse = tablesDB.create(database_id: '', name: '')\n\nputs response.inspect\n```\n```csharp\nusing Appwrite;\nusing Appwrite.Services;\nusing Appwrite.Models;\n\nvar client = new Client()\n .SetEndPoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .SetProject(\"\") // Your project ID\n .SetKey(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\nvar tablesDB = new TablesDB(client);\n\nDatabase result = await tablesDB.Create(\n databaseId: \"\",\n name: \"\");\n```\n```dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\n\nvoid main() { // Init SDK\n Client client = Client();\n TablesDB tablesDB = TablesDB(client);\n\n client\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n ;\n\n Future result = tablesDB.create(\n databaseId: '',\n name: '',\n );\n\n result\n .then((response) {\n print(response);\n }).catchError((error) {\n print(error.response);\n });\n}\n```\n```kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.TablesDB\n\nval client = Client(context)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\") // Your secret API key\n\nval tablesDB = TablesDB(client)\n\nval response = tablesDB.create(\n databaseId = \"\",\n name = \"\",\n)\n```\n```java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.TablesDB;\n\nClient client = new Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\nTablesDB tablesDB = new TablesDB(client);\n\ntablesDB.create(\n \"\",\n \"\",\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(result);\n })\n);\n```\n```swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\") // Your secret API key\n\nlet tablesDB = TablesDB(client)\n\nlet response = try await tablesDB.create(\n databaseId: \"\",\n name: \"\"\n)\n```\n\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::databases::Databases;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box> {\n let client = Client::new()\n .set_endpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .set_project(\"\") // Your project ID\n .set_key(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\n let databases = Databases::new(&client);\n\n let response = databases.create(\n \"\",\n \"\",\n None, // enabled (optional)\n ).await?;\n\n println!(\"{:?}\", response);\n Ok(())\n}\n```\n\n{% /multicode %}"}, {"path": "docs/products/databases/geo-queries", "title": "Geo queries", "description": "Query geographic data with distance, intersects, overlaps, and other location-based operations using spatial columns.", "content": "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.\n\nAppwrite supports geo queries through spatial columns that store coordinates, shapes, and areas as first-class data types.\n\n{% info title=\"Also called spatial queries\" %}\nIn database terminology, these could also be known as **spatial queries**.\n{% /info %}\n\nCoordinates are specified as `[longitude, latitude]` arrays. Distance measurements can be specified in meters or degrees.\n\n# Use cases {% #use-cases %}\n\nUse geo queries for location-based features:\n\n- **Search nearby**: Find all bus stops within 200 meters of a location\n- **Geofencing**: Check if delivery vehicles enter or exit designated zones\n- **Routing coverage**: Determine service areas and delivery radiuses\n- **Region lookups**: Match addresses to administrative boundaries\n- **Asset tracking**: Monitor equipment locations and movements\n- **Compliance zones**: Verify operations within permitted areas\n\n# Spatial columns {% #spatial-columns %}\n\nAppwrite provides first-class geo types that can be stored as table columns:\n\n- **Point**: Represents a single geographic coordinate as `[longitude, latitude]`\n- **Line**: Represents a series of connected geographic coordinates forming a path or route\n- **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.\n\n# Spatial index {% #spatial-index %}\n\nFor 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.\n\nCreate spatial indexes through the [Appwrite Console](/docs/products/databases/tables#indexes), [Server SDK](/docs/sdks#server), or [CLI](/docs/tooling/command-line/tables#commands).\n\n# Query operations {% #geo-queries %}\n\nAppwrite supports these geo query operations:\n\n- **Distance queries**: Find locations within, beyond, or exactly at a specified distance\n- **Geometric relationships**: Check if shapes intersect, overlap, touch, or cross each other\n- **Boundary queries**: Determine if points fall within defined areas\n\nFor complete documentation and examples of all geo query operations, see [Queries](/docs/products/databases/queries#geo-queries).\n\n{% info title=\"Self-hosted installations\" %}\nIf 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.\n{% /info %}"}, {"path": "docs/products/databases/legacy/atomic-numeric-operations", "title": "Atomic numeric operations", "description": "Safely increment and decrement numeric fields without race conditions. Perfect for counters, quotas, inventory, and usage metrics in high-concurrency applications.", "content": "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.\n\n# How atomic operations work {% #how-atomic-operations-work %}\n\nInstead 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.\n\n**Traditional approach:**\n1. Fetch document → `{ likes: 42 }`\n2. Update client-side → `likes: 43`\n3. Write back → `{ likes: 43 }`\n\n**Atomic approach:**\n1. Call `incrementDocumentColumn()` with the column name and the value to increment by\n2. Server applies atomically → `likes: 43`\n\n# When to use atomic operations {% #when-to-use-atomic-operations %}\n\nAtomic numeric operations work well for:\n\n- **Social features**: Likes, follows, comment counts\n- **Usage metering**: API credits, storage quotas, request limits\n- **Game state**: Scores, lives, currency, experience points\n- **E-commerce**: Stock counts, inventory levels\n- **Workflow tracking**: Retry counts, progress indicators\n- **Rate limiting**: Request counters, usage tracking\n\n# Perform atomic operations {% #perform-atomic-operations %}\n\nUse the `incrementDocumentColumn` and `decrementDocumentColumn` methods to perform atomic numeric operations. The server will apply these changes atomically under concurrency control.\n\n## Increment a field {% #increment-field %}\n\n{% multicode %}\n```client-web\nimport { Client, Databases } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject(''); // Your project ID\n\nconst databases = new Databases(client);\n\nconst result = await databases.incrementDocumentColumn(\n '',\n '',\n '',\n 'likes', // column\n 1 // value\n);\n```\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nfinal databases = Databases(client);\n\nfinal document = await databases.incrementDocumentColumn(\n databaseId: '',\n collectionId: '',\n documentId: '',\n column: 'likes',\n value: 1\n);\n```\n```client-apple\nimport Appwrite\nimport AppwriteModels\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nlet databases = Databases(client)\n\nlet document = try await databases.incrementDocumentColumn(\n databaseId: \"\",\n collectionId: \"\",\n documentId: \"\",\n column: \"likes\",\n value: 1\n)\n```\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Databases\n\nval client = Client(applicationContext)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nval databases = Databases(client)\n\nval document = databases.incrementDocumentColumn(\n databaseId = \"\",\n collectionId = \"\",\n documentId = \"\",\n column = \"likes\",\n value = 1\n)\n```\n```server-nodejs\nconst sdk = require('node-appwrite');\n\nconst client = new sdk.Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n .setKey(''); // Your secret API key\n\nconst databases = new sdk.Databases(client);\n\nconst result = await databases.incrementDocumentColumn(\n '',\n '',\n '',\n 'likes', // column\n 1 // value\n);\n```\n```server-python\nfrom appwrite.client import Client\nfrom appwrite.services.databases import Databases\n\nclient = Client()\nclient.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint\nclient.set_project('') # Your project ID\nclient.set_key('') # Your secret API key\n\ndatabases = Databases(client)\n\nresult = databases.increment_document_column(\n database_id = '',\n collection_id = '',\n document_id = '',\n column = 'likes', # column\n value = 1 # value\n)\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::databases::Databases;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box> {\n let client = Client::new()\n .set_endpoint(\"https://.cloud.appwrite.io/v1\")\n .set_project(\"\")\n .set_key(\"\");\n\n let databases = Databases::new(&client);\n\n let result = databases.increment_document_attribute(\n \"\",\n \"\",\n \"\",\n \"likes\", // attribute\n Some(1.0), // value\n None, // max (optional)\n None, // transaction_id (optional)\n ).await?;\n\n println!(\"{:?}\", result);\n Ok(())\n}\n```\n```graphql\nmutation {\n databasesIncrementDocumentColumn(\n databaseId: \"\",\n collectionId: \"\",\n documentId: \"\",\n column: \"likes\",\n value: 1\n ) {\n _id\n _collectionId\n _databaseId\n _createdAt\n _updatedAt\n _permissions\n data\n }\n}\n```\n{% /multicode %}\n\n## Decrement a field {% #decrement-field %}\n\nUse the `decrementDocumentColumn` method to decrease numeric fields:\n\n{% multicode %}\n```client-web\nimport { Client, Databases } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject(''); // Your project ID\n\nconst databases = new Databases(client);\n\nconst result = await databases.decrementDocumentColumn(\n '',\n '',\n '',\n 'credits', // column\n 5 // value\n);\n```\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nfinal databases = Databases(client);\n\nfinal document = await databases.decrementDocumentColumn(\n databaseId: '',\n collectionId: '',\n documentId: '',\n column: 'credits',\n value: 5\n);\n```\n```client-apple\nimport Appwrite\nimport AppwriteModels\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nlet databases = Databases(client)\n\nlet document = try await databases.decrementDocumentColumn(\n databaseId: \"\",\n collectionId: \"\",\n documentId: \"\",\n column: \"credits\",\n value: 5\n)\n```\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Databases\n\nval client = Client(applicationContext)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nval databases = Databases(client)\n\nval document = databases.decrementDocumentColumn(\n databaseId = \"\",\n collectionId = \"\",\n documentId = \"\",\n column = \"credits\",\n value = 5\n)\n```\n```server-nodejs\nconst sdk = require('node-appwrite');\n\nconst client = new sdk.Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n .setKey(''); // Your secret API key\n\nconst databases = new sdk.Databases(client);\n\nconst result = await databases.decrementDocumentColumn(\n '',\n '',\n '',\n 'credits', // column\n 5 // value\n);\n```\n```server-python\nfrom appwrite.client import Client\nfrom appwrite.services.databases import Databases\n\nclient = Client()\nclient.set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint\nclient.set_project('') # Your project ID\nclient.set_key('') # Your secret API key\n\ndatabases = Databases(client)\n\nresult = databases.decrement_document_column(\n database_id = '',\n collection_id = '',\n document_id = '',\n column = 'credits', # column\n value = 5 # value\n)\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::databases::Databases;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box> {\n let client = Client::new()\n .set_endpoint(\"https://.cloud.appwrite.io/v1\")\n .set_project(\"\")\n .set_key(\"\");\n\n let databases = Databases::new(&client);\n\n let result = databases.decrement_document_attribute(\n \"\",\n \"\",\n \"\",\n \"credits\", // attribute\n Some(5.0), // value\n None, // min (optional)\n None, // transaction_id (optional)\n ).await?;\n\n println!(\"{:?}\", result);\n Ok(())\n}\n```\n```graphql\nmutation {\n databasesDecrementDocumentColumn(\n databaseId: \"\",\n collectionId: \"\",\n documentId: \"\",\n column: \"credits\",\n value: 5\n ) {\n _id\n _collectionId\n _databaseId\n _createdAt\n _updatedAt\n _permissions\n data\n }\n}\n```\n{% /multicode %}\n\n# Set constraints and bounds {% #set-constraints-and-bounds %}\n\nYou 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:\n\n## Example with constraints {% #example-with-constraints %}\n\n{% multicode %}\n```client-web\n// Increment with maximum constraint\nconst result = await databases.incrementDocumentColumn(\n '',\n '',\n '',\n 'credits', // column\n 100, // value\n 1000 // max (optional)\n);\n\n// Decrement with minimum constraint\nconst result2 = await databases.decrementDocumentColumn(\n '',\n '',\n '',\n 'credits', // column\n 50, // value\n 0 // min (optional)\n);\n```\n```client-flutter\n// Increment with maximum constraint\nfinal document = await databases.incrementDocumentColumn(\n databaseId: '',\n collectionId: '',\n documentId: '',\n column: 'credits',\n value: 100,\n max: 1000\n);\n\n// Decrement with minimum constraint\nfinal document2 = await databases.decrementDocumentColumn(\n databaseId: '',\n collectionId: '',\n documentId: '',\n column: 'credits',\n value: 50,\n min: 0\n);\n```\n```client-apple\n// Increment with maximum constraint\nlet document = try await databases.incrementDocumentColumn(\n databaseId: \"\",\n collectionId: \"\",\n documentId: \"\",\n column: \"credits\",\n value: 100,\n max: 1000\n)\n\n// Decrement with minimum constraint\nlet document2 = try await databases.decrementDocumentColumn(\n databaseId: \"\",\n collectionId: \"\",\n documentId: \"\",\n column: \"credits\",\n value: 50,\n min: 0\n)\n```\n```client-android-kotlin\n// Increment with maximum constraint\nval document = databases.incrementDocumentColumn(\n databaseId = \"\",\n collectionId = \"\",\n documentId = \"\",\n column = \"credits\",\n value = 100,\n max = 1000\n)\n\n// Decrement with minimum constraint\nval document2 = databases.decrementDocumentColumn(\n databaseId = \"\",\n collectionId = \"\",\n documentId = \"\",\n column = \"credits\",\n value = 50,\n min = 0\n)\n```\n```server-nodejs\n// Increment with maximum constraint\nconst result = await databases.incrementDocumentColumn(\n '',\n '',\n '',\n 'credits', // column\n 100, // value\n 1000 // max (optional)\n);\n\n// Decrement with minimum constraint\nconst result2 = await databases.decrementDocumentColumn(\n '',\n '',\n '',\n 'credits', // column\n 50, // value\n 0 // min (optional)\n);\n```\n```server-python\n# Increment with maximum constraint\nresult = databases.increment_document_column(\n database_id = '',\n collection_id = '',\n document_id = '',\n column = 'credits', # column\n value = 100, # value\n max = 1000 # max (optional)\n)\n\n# Decrement with minimum constraint\nresult2 = databases.decrement_document_column(\n database_id = '',\n collection_id = '',\n document_id = '',\n column = 'credits', # column\n value = 50, # value\n min = 0 # min (optional)\n)\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::databases::Databases;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box> {\n let client = Client::new()\n .set_endpoint(\"https://.cloud.appwrite.io/v1\")\n .set_project(\"\")\n .set_key(\"\");\n\n let databases = Databases::new(&client);\n\n // Increment with maximum constraint\n let result = databases.increment_document_attribute(\n \"\",\n \"\",\n \"\",\n \"credits\", // attribute\n Some(100.0), // value\n Some(1000.0), // max (optional)\n None, // transaction_id (optional)\n ).await?;\n\n // Decrement with minimum constraint\n let result2 = databases.decrement_document_attribute(\n \"\",\n \"\",\n \"\",\n \"credits\", // attribute\n Some(50.0), // value\n Some(0.0), // min (optional)\n None, // transaction_id (optional)\n ).await?;\n\n println!(\"{:?}\", result);\n println!(\"{:?}\", result2);\n Ok(())\n}\n```\n{% /multicode %}\n\n# Follow best practices {% #follow-best-practices %}\n\n## Use for high-concurrency scenarios {% #use-for-high-concurrency-scenarios %}\n\nAtomic numeric operations are most beneficial when multiple users or processes might update the same numeric field simultaneously.\n\n## Combine with regular updates {% #combine-with-regular-updates %}\n\nFor complex updates that include both atomic operations and regular field changes, you'll need to use separate API calls:\n\n{% multicode %}\n```client-web\n// First, increment the likes atomically\nconst likeResult = await databases.incrementDocumentColumn(\n '',\n '',\n '',\n 'likes', // column\n 1 // value\n);\n\n// Then, update other fields\nconst updateResult = await databases.updateDocument(\n '',\n '',\n '',\n {\n lastLikedBy: userId,\n lastLikedAt: new Date().toISOString()\n }\n);\n```\n```client-flutter\n// First, increment the likes atomically\nfinal likeResult = await databases.incrementDocumentColumn(\n databaseId: '',\n collectionId: '',\n documentId: '',\n column: 'likes',\n value: 1\n);\n\n// Then, update other fields\nfinal updateResult = await databases.updateDocument(\n databaseId: '',\n collectionId: '',\n documentId: '',\n data: {\n 'lastLikedBy': userId,\n 'lastLikedAt': DateTime.now().toIso8601String()\n }\n);\n```\n```client-apple\n// First, increment the likes atomically\nlet likeResult = try await databases.incrementDocumentColumn(\n databaseId: \"\",\n collectionId: \"\",\n documentId: \"\",\n column: \"likes\",\n value: 1\n)\n\n// Then, update other fields\nlet updateResult = try await databases.updateDocument(\n databaseId: \"\",\n collectionId: \"\",\n documentId: \"\",\n data: [\n \"lastLikedBy\": userId,\n \"lastLikedAt\": ISO8601DateFormatter().string(from: Date())\n ]\n)\n```\n```client-android-kotlin\n// First, increment the likes atomically\nval likeResult = databases.incrementDocumentColumn(\n databaseId = \"\",\n collectionId = \"\",\n documentId = \"\",\n column = \"likes\",\n value = 1\n)\n\n// Then, update other fields\nval updateResult = databases.updateDocument(\n databaseId = \"\",\n collectionId = \"\",\n documentId = \"\",\n data = mapOf(\n \"lastLikedBy\" to userId,\n \"lastLikedAt\" to Instant.now().toString()\n )\n)\n```\n```server-nodejs\n// First, increment the likes atomically\nconst likeResult = await databases.incrementDocumentColumn(\n '',\n '',\n '',\n 'likes', // column\n 1 // value\n);\n\n// Then, update other fields\nconst updateResult = await databases.updateDocument(\n '',\n '',\n '',\n {\n lastLikedBy: userId,\n lastLikedAt: new Date().toISOString()\n }\n);\n```\n```server-python\n# First, increment the likes atomically\nlike_result = databases.increment_document_column(\n database_id = '',\n collection_id = '',\n document_id = '',\n column = 'likes', # column\n value = 1 # value\n)\n\n# Then, update other fields\nupdate_result = databases.update_document(\n database_id = '',\n collection_id = '',\n document_id = '',\n data = {\n 'lastLikedBy': user_id,\n 'lastLikedAt': datetime.now().isoformat()\n }\n)\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::databases::Databases;\nuse serde_json::json;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box> {\n let client = Client::new()\n .set_endpoint(\"https://.cloud.appwrite.io/v1\")\n .set_project(\"\")\n .set_key(\"\");\n\n let databases = Databases::new(&client);\n\n // First, increment the likes atomically\n let like_result = databases.increment_document_attribute(\n \"\",\n \"\",\n \"\",\n \"likes\", // attribute\n Some(1.0), // value\n None, // max (optional)\n None, // transaction_id (optional)\n ).await?;\n\n // Then, update other fields\n let update_result = databases.update_document(\n \"\",\n \"\",\n \"\",\n Some(json!({\n \"lastLikedBy\": \"\",\n \"lastLikedAt\": \"\"\n })),\n None, // permissions (optional)\n None, // transaction_id (optional)\n ).await?;\n\n println!(\"{:?}\", like_result);\n println!(\"{:?}\", update_result);\n Ok(())\n}\n```\n{% /multicode %}\n\n## Explore related features\n\n- [Bulk operations](/docs/products/databases/legacy/bulk-operations) - Update multiple documents at once\n- [Permissions](/docs/products/databases/legacy/permissions) - Control access to documents\n- [Queries](/docs/products/databases/legacy/queries) - Find documents to update\n- [Relationships](/docs/products/databases/legacy/relationships) - Update related documents"}, {"path": "docs/products/databases/legacy/bulk-operations", "title": "Bulk operations", "description": "Perform bulk operations on documents within your collections for efficient data handling.", "content": "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.\n\nBulk 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.\n\nFor client applications that need bulk-like functionality, consider using [Appwrite Functions](/docs/products/functions) with proper rate limiting and validation.\n\n{% info title=\"Important notes\" %}\n- 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.\n- Collections that contain relationship attributes are not supported via bulk operations. Use individual document operations for collections with relationships.\n{% /info %}\n\n# Atomic behavior {% #atomic-behavior %}\nBulk 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.\n\nThis atomicity ensures:\n- **Data consistency**: Your database remains in a consistent state even if some operations would fail.\n- **Race condition prevention**: Multiple clients can safely perform bulk operations simultaneously.\n- **Simplified error handling**: You only need to handle complete success or complete failure scenarios.\n\nFor example, if you attempt to create 100 documents and one fails due to a validation error, none of the 100 documents will be created.\n\n# Plan limits {% #plan-limits %}\n\nBulk operations have different limits based on your Appwrite plan:\n\n| Plan | Columns per request |\n|------|----------------------|\n| Free | 100 |\n| Pro | 1,000 |\n\nThese 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.\n\n# Create documents {% #create-documents %}\n\nYou can create multiple documents in a single request using the `createDocuments` method.\n\n{% info title=\"Custom timestamps\" %}\nWhen 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.\n{% /info %}\n\n{% multicode %}\n```server-nodejs\nconst sdk = require('node-appwrite');\n\nconst client = new sdk.Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nconst databases = new sdk.Databases(client);\n\nconst result = await databases.createDocuments(\n '',\n '',\n [\n {\n $id: sdk.ID.unique(),\n name: 'Document 1'\n },\n {\n $id: sdk.ID.unique(),\n name: 'Document 2'\n }\n ]\n);\n```\n\n```server-python\nfrom appwrite.client import Client\nfrom appwrite.services.databases import Databases\nfrom appwrite.id import ID\n\nclient = Client()\nclient.set_endpoint('https://.cloud.appwrite.io/v1')\nclient.set_project('')\nclient.set_key('')\n\ndatabases = Databases(client)\n\nresult = databases.create_documents(\n database_id = '',\n collection_id = '',\n documents = [\n {\n '$id': ID.unique(),\n 'name': 'Document 1'\n },\n {\n '$id': ID.unique(),\n 'name': 'Document 2'\n }\n ]\n)\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::databases::Databases;\nuse appwrite::id::ID;\nuse serde_json::json;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box> {\n let client = Client::new()\n .set_endpoint(\"https://.cloud.appwrite.io/v1\")\n .set_project(\"\")\n .set_key(\"\");\n\n let databases = Databases::new(&client);\n\n let result = databases.create_documents(\n \"\",\n \"\",\n vec![\n json!({\n \"$id\": ID::unique(),\n \"name\": \"Document 1\"\n }),\n json!({\n \"$id\": ID::unique(),\n \"name\": \"Document 2\"\n }),\n ],\n None, // transaction_id (optional)\n ).await?;\n\n println!(\"{:?}\", result);\n Ok(())\n}\n```\n{% /multicode %}\n\n# Update documents {% #update-documents %}\n\n{% info title=\"Permissions required\" %}\nYou must grant **update** permissions to users at the **collection level** before users can update documents.\n[Learn more about permissions](/docs/products/databases/legacy/permissions)\n{% /info %}\n\nYou can update multiple documents in a single request using the `updateDocuments` method.\n\n{% multicode %}\n```server-nodejs\nconst sdk = require('node-appwrite');\n\nconst client = new sdk.Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nconst databases = new sdk.Databases(client);\n\nconst result = await databases.updateDocuments(\n '',\n '',\n {\n status: 'published'\n },\n [\n sdk.Query.equal('status', 'draft')\n ]\n);\n```\n\n```server-python\nfrom appwrite.client import Client\nfrom appwrite.services.databases import Databases\nfrom appwrite.query import Query\n\nclient = Client()\nclient.set_endpoint('https://.cloud.appwrite.io/v1')\nclient.set_project('')\nclient.set_key('')\n\ndatabases = Databases(client)\n\nresult = databases.update_documents(\n database_id = '',\n collection_id = '',\n data = {\n 'status': 'published'\n },\n queries = [\n Query.equal('status', 'draft')\n ]\n)\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::databases::Databases;\nuse appwrite::query::Query;\nuse serde_json::json;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box> {\n let client = Client::new()\n .set_endpoint(\"https://.cloud.appwrite.io/v1\")\n .set_project(\"\")\n .set_key(\"\");\n\n let databases = Databases::new(&client);\n\n let result = databases.update_documents(\n \"\",\n \"\",\n Some(json!({\n \"status\": \"published\"\n })),\n Some(vec![\n Query::equal(\"status\", \"draft\").to_string()\n ]),\n None, // transaction_id (optional)\n ).await?;\n\n println!(\"{:?}\", result);\n Ok(())\n}\n```\n{% /multicode %}\n\n# Upsert documents {% #upsert-documents %}\n\n{% info title=\"Permissions required\" %}\nYou must grant **create** and **update** permissions to users at the **collection level** before users can create documents.\n[Learn more about permissions](/docs/products/databases/legacy/permissions)\n{% /info %}\n\nYou can upsert multiple documents in a single request using the `upsertDocuments` method.\n\n{% multicode %}\n```server-nodejs\nconst sdk = require('node-appwrite');\n\nconst client = new sdk.Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nconst databases = new sdk.Databases(client);\n\nconst result = await databases.upsertDocuments(\n '',\n '',\n [\n {\n $id: sdk.ID.unique(),\n name: 'New Document 1'\n },\n {\n $id: 'document-id-2', // Existing document ID\n name: 'New Document 2'\n }\n ]\n);\n```\n\n```server-python\nfrom appwrite.client import Client\nfrom appwrite.services.databases import Databases\nfrom appwrite.id import ID\n\nclient = Client()\nclient.set_endpoint('https://.cloud.appwrite.io/v1')\nclient.set_project('')\nclient.set_key('')\n\ndatabases = Databases(client)\n\nresult = databases.upsert_documents(\n database_id = '',\n collection_id = '',\n documents = [\n {\n '$id': ID.unique(),\n 'name': 'New Document 1'\n },\n {\n '$id': 'document-id-2', # Existing document ID\n 'name': 'New Document 2'\n }\n ]\n)\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::databases::Databases;\nuse appwrite::id::ID;\nuse serde_json::json;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box> {\n let client = Client::new()\n .set_endpoint(\"https://.cloud.appwrite.io/v1\")\n .set_project(\"\")\n .set_key(\"\");\n\n let databases = Databases::new(&client);\n\n let result = databases.upsert_documents(\n \"\",\n \"\",\n vec![\n json!({\n \"$id\": ID::unique(),\n \"name\": \"New Document 1\"\n }),\n json!({\n \"$id\": \"document-id-2\",\n \"name\": \"New Document 2\"\n }),\n ],\n None, // transaction_id (optional)\n ).await?;\n\n println!(\"{:?}\", result);\n Ok(())\n}\n```\n{% /multicode %}\n\n# Delete documents {% #delete-documents %}\n\n{% info title=\"Permissions required\" %}\nYou must grant **delete** permissions to users at the **collection level** before users can delete documents.\n[Learn more about permissions](/docs/products/databases/legacy/permissions)\n{% /info %}\n\nYou can delete multiple documents in a single request using the `deleteDocuments` method.\n\n{% multicode %}\n```server-nodejs\nconst sdk = require('node-appwrite');\n\nconst client = new sdk.Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nconst databases = new sdk.Databases(client);\n\nconst result = await databases.deleteDocuments(\n '',\n '',\n [\n sdk.Query.equal('status', 'archived')\n ]\n);\n```\n\n```server-python\nfrom appwrite.client import Client\nfrom appwrite.services.databases import Databases\nfrom appwrite.query import Query\n\nclient = Client()\nclient.set_endpoint('https://.cloud.appwrite.io/v1')\nclient.set_project('')\nclient.set_key('')\n\ndatabases = Databases(client)\n\nresult = databases.delete_documents(\n database_id = '',\n collection_id = '',\n queries = [\n Query.equal('status', 'archived')\n ]\n)\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::databases::Databases;\nuse appwrite::query::Query;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box> {\n let client = Client::new()\n .set_endpoint(\"https://.cloud.appwrite.io/v1\")\n .set_project(\"\")\n .set_key(\"\");\n\n let databases = Databases::new(&client);\n\n let result = databases.delete_documents(\n \"\",\n \"\",\n Some(vec![\n Query::equal(\"status\", \"archived\").to_string()\n ]),\n None, // transaction_id (optional)\n ).await?;\n\n println!(\"{:?}\", result);\n Ok(())\n}\n```\n{% /multicode %}\n\n{% info title=\"Queries for deletion\" %}\n\nWhen deleting documents, you must specify queries to filter which documents to delete.\n\nIf no queries are provided, all documents in the collection will be deleted.\n\n[Learn more about queries](/docs/products/databases/legacy/queries).\n\n{% /info %}"}, {"path": "docs/products/databases/legacy/collections", "title": "Collections", "description": "Organize your data with Appwrite Collections. Explore how to create and configure collections to store and structure your data effectively.", "content": "Appwrite uses collections as containers of documents. Each collection contains many documents identical in structure.\nThe 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.\n\nThat said, Appwrite is designed to support both SQL and NoSQL database adapters like MariaDB, MySQL, or MongoDB in future versions.\n\n# Create collection {% #create-collection %}\nYou can create collections using the Appwrite Console, a [Server SDK](/docs/sdks#server), or using the [CLI](/docs/tooling/command-line/installation).\n{% tabs %}\n\n{% tabsitem #console title=\"Console\" %}\nYou can create a collection by heading to the **Databases** page, navigate to a [database](/docs/products/databases/legacy/databases), and click **Create collection**.\n\n{% /tabsitem %}\n\n{% tabsitem #server-sdk title=\"Server SDK\" %}\nYou can also create collections programmatically using a [Server SDK](/docs/sdks#server). Appwrite [Server SDKs](/docs/sdks#server) require an [API key](/docs/advanced/security/api-keys).\n\n{% multicode %}\n\n```server-nodejs\nconst sdk = require('node-appwrite');\n\n// Init SDK\nconst client = new sdk.Client();\n\nconst databases = new sdk.Databases(client);\n\nclient\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n;\n\nconst promise = databases.createCollection({\n databaseId: '',\n collectionId: '[COLLECTION_ID]',\n name: '[NAME]'\n});\n\npromise.then(function (response) {\n console.log(response);\n}, function (error) {\n console.log(error);\n});\n```\n```deno\nimport * as sdk from \"npm:node-appwrite\";\n\n// Init SDK\nlet client = new sdk.Client();\n\nlet databases = new sdk.Databases(client);\n\nclient\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n;\n\n\nlet promise = databases.createCollection({\n databaseId: '',\n collectionId: '[COLLECTION_ID]',\n name: '[NAME]'\n});\n\npromise.then(function (response) {\n console.log(response);\n}, function (error) {\n console.log(error);\n});\n```\n```php\nsetEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n ->setProject('') // Your project ID\n ->setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n;\n\n$databases = new Databases($client);\n\n$result = $databases->createCollection('', '', '');\n```\n```python\nfrom appwrite.client import Client\nfrom appwrite.services.databases import Databases\n\nclient = Client()\n\n(client\n .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('') # Your project ID\n .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key\n)\n\ndatabases = Databases(client)\n\nresult = databases.create_collection('', '', '')\n```\n```ruby\nrequire 'Appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('') # Your project ID\n .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key\n\ndatabases = Databases.new(client)\n\nresponse = databases.create_collection(database_id: '', collection_id: '', name: '')\n\nputs response.inspect\n```\n```csharp\nusing Appwrite;\nusing Appwrite.Services;\nusing Appwrite.Models;\n\nvar client = new Client()\n .SetEndPoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .SetProject(\"\") // Your project ID\n .SetKey(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\nvar databases = new Databases(client);\n\nCollection result = await databases.CreateCollection(\n databaseId: \"\",\n collectionId: \"\",\n name: \"[NAME]\");\n```\n```dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\n\nvoid main() { // Init SDK\n Client client = Client();\n Databases databases = Databases(client);\n\n client\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n ;\n\n Future result = databases.createCollection(\n databaseId: '',\n collectionId: '',\n name: '',\n );\n\n result\n .then((response) {\n print(response);\n }).catchError((error) {\n print(error.response);\n });\n}\n```\n```kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Databases\n\nval client = Client(context)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\") // Your secret API key\n\nval databases = Databases(client)\n\nval response = databases.createCollection(\n databaseId = \"\",\n collectionId = \"\",\n name = \"[NAME]\",\n)\n```\n```java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Databases;\n\nClient client = new Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\nDatabases databases = new Databases(client);\n\ndatabases.createCollection(\n \"\",\n \"\",\n \"[NAME]\",\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(result);\n })\n);\n```\n```swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\") // Your secret API key\n\nlet databases = Databases(client)\n\nlet collection = try await databases.createCollection(\n databaseId: \"\",\n collectionId: \"\",\n name: \"[NAME]\"\n)\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::databases::Databases;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box> {\n let client = Client::new()\n .set_endpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .set_project(\"\") // Your project ID\n .set_key(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\n let databases = Databases::new(&client);\n\n let collection = databases.create_collection(\n \"\",\n \"\",\n \"[NAME]\",\n None, // permissions\n None, // document_security\n None, // enabled\n None, // attributes\n None, // indexes\n ).await?;\n\n println!(\"{:?}\", collection);\n Ok(())\n}\n```\n{% /multicode %}\n\nYou can also configure **permissions** in the `createCollection` method, learn more about the `createCollection` in the [API references](/docs/references).\n{% /tabsitem %}\n\n{% tabsitem #cli title=\"CLI\" %}\n\n{% partial file=\"cli-disclaimer.md\" /%}\n\nTo create your collection using the CLI, first use the `appwrite init collections` command to initialize your collection.\n\n```sh\nappwrite init collections\n```\n\nThen push your collection using the `appwrite push collections` command.\n\n```sh\nappwrite push collections\n```\n\nThis will create your collection in the Console with all of your `appwrite.config.json` configurations.\n\n{% arrow_link href=\"/docs/tooling/command-line/collections#commands\" %}\nLearn more about the CLI collections commands\n{% /arrow_link %}\n\n{% /tabsitem %}\n\n{% /tabs %}\n\n# Permissions {% #permissions %}\nAppwrite uses permissions to control data access.\nFor security, only users that are granted permissions can access a resource.\nThis helps prevent accidental data leaks by forcing you to make more concious decisions around permissions.\n\nBy default, Appwrite doesn't grant permissions to any users when a new collection is created.\nThis means users can't create new documents or read, update, and delete existing documents.\n\n[Learn about configuring permissions](/docs/products/databases/legacy/permissions).\n\n# Attributes {% #attributes %}\nAll documents in a collection follow the same structure.\nAttributes are used to define the structure of your documents and help the Appwrite's API validate your users' input.\nAdd your first attribute by clicking the **Add attribute** button.\n\nYou can choose between the following types.\n\n| Attribute | Description |\n|--------------|------------------------------------------------------------------|\n| `string` | String attribute. |\n| `integer` | Integer attribute. |\n| `float` | Float attribute. |\n| `boolean` | Boolean attribute. |\n| `datetime` | Datetime attribute formatted as an ISO 8601 string. |\n| `enum` | Enum attribute. |\n| `ip` | IP address attribute for IPv4 and IPv6. |\n| `email` | Email address attribute. |\n| `url` | URL attribute. |\n| `relationship` | Relationship attribute relates one collection to another. [Learn more about relationships.](/docs/products/databases/legacy/relationships) |\n\nIf an attribute must be populated in all documents, set it as `required`.\nIf not, you may optionally set a default value.\nAdditionally, decide if the attribute should be a single value or an array of values.\n\nIf needed, you can change an attribute's key, default value, size (for strings), and whether it is required or not after creation.\n\nYou 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.\n\n# Indexes {% #indexes %}\n\nDatabases use indexes to quickly locate data without having to search through every document for matches.\nTo ensure the best performance, Appwrite recommends an index for every attribute queried.\nIf you plan to query multiple attributes in a single query, creating an index with **all** queried attributes will yield optimal performance.\n\nThe following indexes are currently supported:\n\n| Type | Description |\n|------------|--------------------------------------------------------------------------------------------------------------|\n| `key` | Plain Index to allow queries. |\n| `unique` | Unique Index to disallow duplicates. |\n| `fulltext` | For searching within string attributes. Required for the [search query method](/docs/products/databases/legacy/queries#query-class). |\n\nYou can create an index by navigating to your collection's **Indexes** tab or by using your favorite [Server SDK](/docs/sdks#server)."}, {"path": "docs/products/databases/legacy/databases", "title": "Databases", "description": "Dive deeper into Appwrite Databases and their configuration. Learn how to create, manage, and optimize multiple databases for your application.", "content": "Databases are the largest organizational unit in Appwrite.\nEach database contains a group of [collections](/docs/products/databases/legacy/collections).\nIn future versions, different databases may be backed by a different database technology of your choosing.\n\n# Create in Console {% #create-in-console %}\nThe easiest way to create a database using the Appwrite Console.\nYou can create a database by navigating to the **Databases** page and clicking **Create database**.\n\n\n\n# Create using Server SDKs {% #create-using-server-sdks %}\nYou can programmatically create databases using a [Server SDK](/docs/sdks#server). Appwrite [Server SDKs](/docs/sdks#server) require an [API key](/docs/advanced/security/api-keys).\n\n{% multicode %}\n```server-nodejs\nconst sdk = require('node-appwrite');\n\n// Init SDK\nconst client = new sdk.Client();\n\nconst databases = new sdk.Databases(client);\n\nclient\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n;\n\nconst promise = databases.create('', '');\n\npromise.then(function (response) {\n console.log(response);\n}, function (error) {\n console.log(error);\n});\n```\n```deno\nimport * as sdk from \"npm:node-appwrite\";\n\n// Init SDK\nlet client = new sdk.Client();\n\nlet databases = new sdk.Databases(client);\n\nclient\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n;\n\n\nlet promise = databases.create('', '');\n\npromise.then(function (response) {\n console.log(response);\n}, function (error) {\n console.log(error);\n});\n```\n```php\nsetEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n ->setProject('') // Your project ID\n ->setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n;\n\n$databases = new Databases($client);\n\n$result = $databases->create('', '');\n```\n```python\nfrom appwrite.client import Client\nfrom appwrite.services.databases import Databases\n\nclient = Client()\n\n(client\n .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('') # Your project ID\n .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key\n)\n\ndatabases = Databases(client)\n\nresult = databases.create('', '')\n```\n```ruby\nrequire 'Appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('') # Your project ID\n .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key\n\ndatabases = Databases.new(client)\n\nresponse = databases.create(database_id: '', name: '')\n\nputs response.inspect\n```\n```csharp\nusing Appwrite;\nusing Appwrite.Services;\nusing Appwrite.Models;\n\nvar client = new Client()\n .SetEndPoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .SetProject(\"\") // Your project ID\n .SetKey(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\nvar databases = new Databases(client);\n\nDatabase result = await databases.Create(\n databaseId: \"\",\n name: \"\");\n```\n```dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\n\nvoid main() { // Init SDK\n Client client = Client();\n Databases databases = Databases(client);\n\n client\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n ;\n\n Future result = databases.create(\n databaseId: '',\n name: '',\n );\n\n result\n .then((response) {\n print(response);\n }).catchError((error) {\n print(error.response);\n });\n}\n```\n```kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Databases\n\nval client = Client(context)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\") // Your secret API key\n\nval databases = Databases(client)\n\nval response = databases.create(\n databaseId = \"\",\n name = \"\",\n)\n```\n```java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Databases;\n\nClient client = new Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\nDatabases databases = new Databases(client);\n\ndatabases.create(\n \"\",\n \"\",\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(result);\n })\n);\n```\n```swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\") // Your secret API key\n\nlet databases = Databases(client)\n\nlet response = try await databases.create(\n databaseId: \"\",\n name: \"\"\n)\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::databases::Databases;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box> {\n let client = Client::new()\n .set_endpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .set_project(\"\") // Your project ID\n .set_key(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\n let databases = Databases::new(&client);\n\n let response = databases.create(\n \"\",\n \"\",\n None, // enabled\n ).await?;\n\n println!(\"{:?}\", response);\n Ok(())\n}\n```\n\n{% /multicode %}"}, {"path": "docs/products/databases/legacy/documents", "title": "Documents", "description": "Master document management with Appwrite Databases. Learn how to create, update, and query documents within your collections for dynamic data storage.", "content": "Each piece of data or information in Appwrite Databases is a document.\nDocuments have a structure defined by the parent collection.\n\n# Create documents {% #create-documents %}\n{% info title=\"Permissions required\" %}\nYou must grant **create** permissions to users at the **collection level** before users can create documents.\n[Learn more about permissions](#permissions)\n{% /info %}\n\nIn most use cases, you will create documents programmatically.\n\n{% multicode %}\n```client-web\nimport { Client, Databases, ID } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nconst databases = new Databases(client);\n\nconst promise = databases.createDocument(\n '',\n '',\n ID.unique(),\n {}\n);\n\npromise.then(function (response) {\n console.log(response);\n}, function (error) {\n console.log(error);\n});\n```\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nvoid main() async {\n final client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\n final databases = Databases(client);\n\n try {\n final document = databases.createDocument(\n databaseId: '',\n collectionId: '',\n documentId: ID.unique(),\n data: {}\n );\n } on AppwriteException catch(e) {\n print(e);\n }\n}\n```\n```client-apple\nimport Appwrite\nimport AppwriteModels\n\nfunc main() async throws {\n let client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\n let databases = Databases(client)\n\n do {\n let document = try await databases.createDocument(\n databaseId: \"\",\n collectionId: \"\",\n documentId: ID.unique(),\n data: [:]\n )\n } catch {\n print(error.localizedDescription)\n }\n}\n```\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Databases\n\nsuspend fun main() {\n val client = Client(applicationContext)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\n val databases = Databases(client)\n\n try {\n val document = databases.createDocument(\n databaseId = \"\",\n collectionId = \"\",\n documentId = ID.unique(),\n data = mapOf(\"a\" to \"b\"),\n )\n } catch (e: Exception) {\n Log.e(\"Appwrite\", \"Error: \" + e.message)\n }\n}\n```\n```graphql\nmutation {\n databasesCreateDocument(\n databaseId: \"\",\n collectionId: \"\",\n documentId: \"\",\n data: \"{}\"\n ) {\n _id\n _collectionId\n _databaseId\n _createdAt\n _updatedAt\n _permissions\n data\n }\n}\n```\n{% /multicode %}\n\nDuring testing, you might prefer to create documents in the Appwrite Console.\nTo do so, navigate to the **Documents** tab of your collection and click the **Add document** button.\n\n# List documents {% #list-documents %}\n\n{% info title=\"Permissions required\" %}\nYou must grant **read** permissions to users at the **collection level** before users can read documents.\n[Learn more about permissions](#permissions)\n{% /info %}\n\nDocuments can be retrieved using the [List Document](/docs/references/cloud/client-web/databases#listDocuments) endpoint.\n\nResults can be filtered, sorted, and paginated using Appwrite's shared set of query methods.\nYou can find a full guide on querying in the [Queries Guide](/docs/products/databases/legacy/queries).\n\nBy default, results are limited to the **first 25 items**.\nYou can change this through [pagination](/docs/products/databases/legacy/pagination).\n\n{% multicode %}\n```client-web\nimport { Client, Databases, Query } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\");\n\nconst databases = new Databases(client);\n\nlet promise = databases.listDocuments(\n \"\",\n \"\",\n [\n Query.equal('title', 'Avatar')\n ]\n);\n\npromise.then(function (response) {\n console.log(response);\n}, function (error) {\n console.log(error);\n});\n```\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nvoid main() async {\n final client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\n final databases = Databases(client);\n\n try {\n final documents = await databases.listDocuments(\n databaseId: '',\n collectionId: '',\n queries: [\n Query.equal('title', 'Avatar')\n ]\n );\n } on AppwriteException catch(e) {\n print(e);\n }\n}\n```\n```client-apple\nimport Appwrite\nimport AppwriteModels\n\nfunc main() async throws {\n let client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\n let databases = Databases(client)\n\n do {\n let documents = try await databases.listDocuments(\n databaseId: \"\",\n collectionId: \"\",\n queries: [\n Query.equal(\"title\", value: \"Avatar\")\n ]\n )\n } catch {\n print(error.localizedDescription)\n }\n}\n```\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.Query\nimport io.appwrite.services.Databases\n\nsuspend fun main() {\n val client = Client(applicationContext)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\n val databases = Databases(client)\n\n try {\n val documents = databases.listDocuments(\n databaseId = \"\",\n collectionId = \"\",\n queries = listOf(\n Query.equal(\"title\", \"Avatar\")\n )\n )\n } catch (e: AppwriteException) {\n Log.e(\"Appwrite\", \"Error: \" + e.message)\n }\n}\n```\n```graphql\nquery {\n databasesListDocuments(\n databaseId: \"\",\n collectionId: \"\",\n queries: [\"equal(\\\"title\\\", [\\\"Avatar\\\"])\"]\n ) {\n total\n documents {\n _id\n data\n }\n }\n}\n```\n{% /multicode %}\n\n# Upsert documents {% #upsert-documents %}\n{% info title=\"Permissions required\" %}\nYou 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.\n[Learn more about permissions](#permissions)\n{% /info %}\n\nIn most use cases, you will upsert documents programmatically.\n\n{% multicode %}\n```client-web\nimport { Client, Databases, ID } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nconst databases = new Databases(client);\n\nconst promise = databases.upsertDocument(\n '',\n '',\n ID.unique(),\n {}\n);\n\npromise.then(function (response) {\n console.log(response);\n}, function (error) {\n console.log(error);\n});\n```\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nvoid main() async {\n final client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\n final databases = Databases(client);\n\n try {\n final document = databases.upsertDocument(\n databaseId: '',\n collectionId: '',\n documentId: ID.unique(),\n data: {}\n );\n } on AppwriteException catch(e) {\n print(e);\n }\n}\n```\n```client-apple\nimport Appwrite\nimport AppwriteModels\n\nfunc main() async throws {\n let client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\n let databases = Databases(client)\n\n do {\n let document = try await databases.upsertDocument(\n databaseId: \"\",\n collectionId: \"\",\n documentId: ID.unique(),\n data: [:]\n )\n } catch {\n print(error.localizedDescription)\n }\n}\n```\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Databases\n\nsuspend fun main() {\n val client = Client(applicationContext)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\n val databases = Databases(client)\n\n try {\n val document = databases.upsertDocument(\n databaseId = \"\",\n collectionId = \"\",\n documentId = ID.unique(),\n data = mapOf(\"a\" to \"b\"),\n )\n } catch (e: Exception) {\n Log.e(\"Appwrite\", \"Error: \" + e.message)\n }\n}\n```\n```graphql\nmutation {\n databasesUpsertDocument(\n databaseId: \"\",\n collectionId: \"\",\n documentId: \"\",\n data: \"{}\"\n ) {\n _id\n _collectionId\n _databaseId\n _createdAt\n _updatedAt\n _permissions\n data\n }\n}\n```\n{% /multicode %}\n\n\n\n# Permissions {% #permissions %}\nIn Appwrite, permissions can be granted at the collection level and the document level.\nBefore a user can create a document, you need to grant create permissions to the user.\n\nRead, update, and delete permissions can be granted at both the collection and document level.\nUsers only need to be granted access at either the collection or document level to access documents.\n\n[Learn about configuring permissions](/docs/products/databases/legacy/permissions).\n\n# Next steps {% #next-steps %}\n\nContinue learning with these related guides:\n\n{% cards %}\n{% cards_item href=\"/docs/products/databases/queries\" title=\"Queries\" %}\nLearn how to filter, sort, and search your documents with various query operators.\n{% /cards_item %}\n\n{% cards_item href=\"/docs/products/databases/pagination\" title=\"Pagination\" %}\nHandle large datasets by implementing pagination in your document queries.\n{% /cards_item %}\n\n{% cards_item href=\"/docs/products/databases/bulk-operations\" title=\"Bulk operations\" %}\nPerform create, update, and delete operations on multiple documents simultaneously.\n{% /cards_item %}\n\n{% cards_item href=\"/docs/products/databases/timestamp-overrides\" title=\"Timestamp overrides\" %}\nSet custom creation and update timestamps when migrating data or backdating records.\n{% /cards_item %}\n{% /cards %}\n\n[Learn more about bulk operations](/docs/products/databases/bulk-operations)."}, {"path": "docs/products/databases/legacy/order", "title": "Order", "description": "Understand how to do data ordering in Appwrite Databases. Learn how to order and sort your database records for efficient data retrieval.\"", "content": "You can order results returned by Appwrite Databases by using an order query.\nFor best performance, create an [index](/docs/products/databases/legacy/collections#indexes) on the column you plan to order by.\n\n# Ordering one column {% #one-column %}\n\nWhen querying using the [listDocuments](/docs/references/cloud/client-web/databases#listDocuments) endpoint,\nyou can specify the order of the documents returned using the `Query.orderAsc()` and `Query.orderDesc()` query methods.\n\n{% multicode %}\n```client-web\nimport { Client, Databases, Query } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nconst databases = new Databases(client);\n\ndatabases.listDocuments(\n '',\n '',\n [\n Query.orderAsc('title'),\n ]\n);\n```\n\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nvoid main() async {\n final client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\n final databases = Databases(client);\n\n try {\n final documents = await databases.listDocuments(\n databaseId: '',\n collectionId: '',\n queries: [\n Query.orderAsc('title')\n ]\n );\n } on AppwriteException catch(e) {\n print(e);\n }\n}\n```\n\n```client-apple\nimport Appwrite\nimport AppwriteModels\n\nfunc main() async throws {\n let client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\n let databases = Databases(client)\n\n do {\n let documents = try await databases.listDocuments(\n databaseId: \"\",\n collectionId: \"\",\n queries: [\n Query.orderAsc(\"title\")\n ]\n )\n } catch {\n print(error.localizedDescription)\n }\n}\n```\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.Query\nimport io.appwrite.services.Databases\n\nsuspend fun main() {\n val client = Client(applicationContext)\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\n val databases = Databases(client)\n\n try {\n val documents = databases.listDocuments(\n databaseId = \"\",\n collectionId = \"\",\n queries = [\n Query.orderAsc(\"title\")\n ]\n )\n } catch (e: AppwriteException) {\n Log.e(\"Appwrite\", e.message)\n }\n}\n```\n\n```graphql\nquery {\n databasesListDocuments(\n databaseId: \"\",\n collectionId: \"\"\n queries: [\"orderAsc(\\\"title\\\")\"]\n ) {\n total\n documents {\n _id\n data\n }\n }\n}\n```\n\n{% /multicode %}\n\n# Multiple columns {% #multiple-columns %}\nTo sort based on multiple attributes, simply provide multiple query methods.\nFor better performance, create an index on the first attribute that you order by.\n\nIn the example below, the movies returned will be first sorted by `title` in ascending order, then sorted by `year` in descending order.\n{% multicode %}\n\n```js\n// Web SDK code example for sorting based on multiple attributes\n// ...\n\n// List documents and sort based on multiple attributes\ndatabases.listDocuments(\n '',\n '',\n [\n Query.orderAsc('title'), // Order first by title in ascending order\n Query.orderDesc('year'), // Then, order by year in descending order\n ]\n);\n```\n```dart\n// Flutter SDK code example for sorting based on multiple attributes\n// ...\n\n// List documents and sort based on multiple attributes\ntry {\n final documents = await databases.listDocuments(\n databaseId: '',\n collectionId: '',\n queries: [\n Query.orderAsc('title'), // Order by title in ascending order\n Query.orderDesc('year') // Order by year in descending order\n ]\n );\n} on AppwriteException catch(e) {\n print(e);\n}\n```\n```kotlin\n// Android SDK code example for sorting based on multiple attributes\n// ...\n\n// List documents and sort based on multiple attributes\ntry {\n val documents = databases.listDocuments(\n databaseId = \"\",\n collectionId = \"\",\n queries = [\n Query.orderAsc(\"title\"), // Order by title in ascending order\n Query.orderDesc(\"year\") // Order by year in descending order\n ]\n );\n} catch (e: AppwriteException) {\n Log.e(\"Appwrite\", e.message);\n}\n```\n```swift\n// Apple SDK code example for sorting based on multiple attributes\n// ...\n\n// List documents and sort based on multiple attributes\ndo {\n let documents = try await databases.listDocuments(\n databaseId: \"\",\n collectionId: \"\",\n queries: [\n Query.orderAsc(\"title\"), // Order by title in ascending order\n Query.orderDesc(\"year\") // Order by year in descending order\n ]\n );\n} catch {\n print(error.localizedDescription);\n}\n```\n```graphql\nquery {\n databasesListDocuments(\n databaseId: \"\",\n collectionId: \"\",\n queries: [\"orderAsc(\\\"title\\\")\", \"orderDesc(\\\"year\\\")\"]\n ) {\n total\n documents {\n _id\n data\n }\n }\n}\n```\n{% /multicode %}\n\n# Ordering by sequence {% #sequence-ordering %}\n\nFor 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.\n\n{% multicode %}\n```client-web\nimport { Client, Databases, Query } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nconst databases = new Databases(client);\n\ndatabases.listDocuments(\n '',\n '',\n [\n Query.orderAsc('$sequence'),\n ]\n);\n```\n\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nvoid main() async {\n final client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\n final databases = Databases(client);\n\n try {\n final documents = await databases.listDocuments(\n databaseId: '',\n collectionId: '',\n queries: [\n Query.orderAsc('\\$sequence')\n ]\n );\n } on AppwriteException catch(e) {\n print(e);\n }\n}\n```\n\n```client-apple\nimport Appwrite\nimport AppwriteModels\n\nfunc main() async throws {\n let client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\n let databases = Databases(client)\n\n do {\n let documents = try await databases.listDocuments(\n databaseId: \"\",\n collectionId: \"\",\n queries: [\n Query.orderAsc(\"$sequence\")\n ]\n )\n } catch {\n print(error.localizedDescription)\n }\n}\n```\n\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.Query\nimport io.appwrite.services.Databases\n\nsuspend fun main() {\n val client = Client(applicationContext)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\n val databases = Databases(client)\n\n try {\n val documents = databases.listDocuments(\n databaseId = \"\",\n collectionId = \"\",\n queries = listOf(\n Query.orderAsc(\"\\$sequence\")\n )\n )\n } catch (e: AppwriteException) {\n Log.e(\"Appwrite\", e.message)\n }\n}\n```\n\n```graphql\nquery {\n databasesListDocuments(\n databaseId: \"\",\n collectionId: \"\"\n queries: [\"orderAsc(\\\"$sequence\\\")\"]\n ) {\n total\n documents {\n _id\n data\n }\n }\n}\n```\n{% /multicode %}\n\nThe `$sequence` field is useful when you need:\n- Consistent ordering for pagination, especially with high-frequency inserts\n- Reliable insertion order tracking when timestamps might not be precise enough\n- Simple numeric ordering without managing custom counter fields"}, {"path": "docs/products/databases/legacy/pagination", "title": "Pagination", "description": "Implement pagination for large data sets in Appwrite Databases. Explore techniques for splitting and displaying data across multiple pages.", "content": "As your database grows in size, you'll need to paginate results returned.\nPagination improves performance by returning a subset of results that match a query at a time, called a page.\n\nBy default, list operations return 25 items per page, which can be changed using the `Query.limit(25)` operator.\nThere is no hard limit on the number of items you can request. However, beware that **large pages can degrade performance**.\n\n# Offset pagination {% #offset-pagination %}\n\nOffset pagination works by dividing documents into `M` pages containing `N` documents.\nEvery page is retrieved by skipping `offset = M * (N - 1)` items and reading the following `M` pages.\n\nUsing `Query.limit()` and `Query.offset()` you can achieve offset pagination.\nWith `Query.limit()` you can define how many documents can be returned from one request.\nThe `Query.offset()` is number of records you wish to skip before selecting records.\n\n{% multicode %}\n```client-web\nimport { Client, Databases, Query } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nconst databases = new Databases(client);\n\n// Page 1\nconst page1 = await databases.listDocuments(\n '',\n '',\n [\n Query.limit(25),\n Query.offset(0)\n ]\n);\n\n// Page 2\nconst page2 = await databases.listDocuments(\n '',\n '',\n [\n Query.limit(25),\n Query.offset(25)\n ]\n);\n```\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nvoid main() async {\n final client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\n final databases = Databases(client);\n\n final page1 = await databases.listDocuments(\n databaseId: '',\n collectionId: '',\n queries: [\n Query.limit(25),\n Query.offset(0)\n ]\n );\n\n final page2 = await databases.listDocuments(\n databaseId: '',\n collectionId: '',\n queries: [\n Query.limit(25),\n Query.offset(25)\n ]\n );\n}\n```\n```client-apple\nimport Appwrite\nimport AppwriteModels\n\nfunc main() async throws {\n let client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\n let databases = Databases(client)\n\n let page1 = try await databases.listDocuments(\n databaseId: \"\",\n collectionId: \"\",\n queries: [\n Query.limit(25),\n Query.offset(0)\n ]\n )\n\n let page2 = try await databases.listDocuments(\n databaseId: \"\",\n collectionId: \"\",\n queries: [\n Query.limit(25),\n Query.offset(25)\n ]\n )\n}\n```\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.Query\nimport io.appwrite.services.Databases\n\nsuspend fun main() {\n val client = Client(applicationContext)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\n val databases = Databases(client)\n\n val page1 = databases.listDocuments(\n databaseId = \"\",\n collectionId = \"\",\n queries = [\n Query.limit(25),\n Query.offset(0)\n ]\n )\n\n val page2 = databases.listDocuments(\n databaseId = \"\",\n collectionId = \"\",\n queries = [\n Query.limit(25),\n Query.offset(25)\n ]\n )\n}\n```\n\n{% /multicode %}\n\n{% info title=\"Drawbacks\" %}\nWhile traditional offset pagination is familiar, it comes with some drawbacks.\nThe 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.\nIf the data changes frequently, offset pagination will also produce **missing and duplicate** results.\n{% /info %}\n\n# Cursor pagination {% #cursor-pagination %}\n\nThe cursor is a unique identifier for a document that points to where the next page should start.\nAfter 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.\nPass the first document's ID into the `Query.cursorBefore(firstId)` query method to retrieve the previous page.\n\n{% multicode %}\n\n```client-web\nimport { Databases, Query } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\");\n\nconst databases = new Databases(client);\n\n// Page 1\nconst page1 = await databases.listDocuments(\n '',\n '',\n [\n Query.limit(25),\n ]\n);\n\nconst lastId = page1.documents[page1.documents.length - 1].$id;\n\n// Page 2\nconst page2 = await databases.listDocuments(\n '',\n '',\n [\n Query.limit(25),\n Query.cursorAfter(lastId),\n ]\n);\n```\n\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nvoid main() async {\n final client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\n final databases = Databases(client);\n\n final page1 = await databases.listDocuments(\n databaseId: '',\n collectionId: '',\n queries: [\n Query.limit(25)\n ]\n );\n\n final lastId = page1.documents[page1.documents.length - 1].$id;\n\n final page2 = await databases.listDocuments(\n databaseId: '',\n collectionId: '',\n queries: [\n Query.limit(25),\n Query.cursorAfter(lastId)\n ]\n );\n\n}\n```\n```client-apple\nimport Appwrite\nimport AppwriteModels\n\nfunc main() async throws {\n let client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\n let databases = Databases(client)\n\n let page1 = try await databases.listDocuments(\n databaseId: \"\",\n collectionId: \"\",\n queries: [\n Query.limit(25)\n ]\n )\n\n let lastId = page1.documents[page1.documents.count - 1].$id\n\n let page2 = try await databases.listDocuments(\n databaseId: \"\",\n collectionId: \"\",\n queries: [\n Query.limit(25),\n Query.cursorAfter(lastId)\n ]\n )\n}\n```\n```client-android-kotlin\nimport android.util.Log\nimport io.appwrite.AppwriteException\nimport io.appwrite.Client\nimport io.appwrite.Query\nimport io.appwrite.services.Databases\n\nsuspend fun main() {\n val client = Client(applicationContext)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\n val databases = Databases(client)\n\n val page1 = databases.listDocuments(\n databaseId = \"\",\n collectionId = \"\",\n queries = [\n Query.limit(25)\n ]\n )\n\n val lastId = page1.documents[page1.documents.size - 1].$id\n\n val page2 = databases.listDocuments(\n databaseId = \"\",\n collectionId = \"\",\n queries = [\n Query.limit(25),\n Query.cursorAfter(lastId)\n ]\n )\n}\n```\n\n{% /multicode %}\n\n# When to use what? {% #when-to-use %}\nOffset pagination should be used for collections that rarely change.\nOffset paginations allow you to create indicator of the current page number and total page number.\nFor example, a list with up to 20 pages or static data like a list of countries or currencies.\nUsing offset pagination on large collections and frequently updated collections may result in slow performance and **missing and duplicate** results.\n\nCursor pagination should be used for frequently updated collections.\nIt is best suited for lazy-loaded pages with infinite scrolling.\nFor example, a feed, comment section, chat history, or high volume datasets."}, {"path": "docs/products/databases/legacy/permissions", "title": "Database permissions", "description": "Enhance data security and access control with Appwrite Database Permissions. Learn how to set permissions and access rules for your database collections", "content": "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.\nPermissions exist at two levels, collection level and document level permissions.\n\nIn Appwrite, permissions are **granted**, meaning a user has no access by default and receive access when granted.\nA user with access granted at either collection level or document level will be able to access a document.\nUsers **don't need access at both levels** to access documents.\n\n# Collection level {% #collection-level %}\nCollection level permissions apply to every document in the collection.\nIf a user has read, create, update, or delete permissions at the collection level, the user can access **all documents** inside the collection.\n\nConfigure collection level permissions by navigating to **Your collection** > **Settings** > **Permissions**.\n\n[Learn more about permissions and roles](/docs/advanced/security/permissions)\n\n# Document level {% #document-level %}\nDocument level permissions grant access to individual documents.\nIf a user has read, create, update, or delete permissions at the document level, the user can access the **individual document**.\n\nDocument level permissions are only applied if Document Security is enabled in the settings of your collection.\nEnable document level permissions by navigating to **Your collection** > **Settings** > **Document security**.\n\nDocument level permissions are configured in individual documents.\n\n[Learn more about permissions and roles](/docs/advanced/security/permissions)\n\n# Common use cases {% #common-use-cases %}\n\nFor 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."}, {"path": "docs/products/databases/legacy/queries", "title": "Queries", "description": "Harness the power of querying with Appwrite Databases. Discover various query options, filtering, sorting, and advanced querying techniques.", "content": "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.\n\n# Query class {% #query-class %}\n\nAppwrite SDKs provide a `Query` class to help you build queries. The `Query` class has methods for each type of supported query operation.\n\n# Building queries {% #building-queries %}\n\nQueries are passed to an endpoint through the `queries` parameter as an array of query strings, which can be generated using the `Query` class.\n\nEach query method is logically separated via `AND` operations. For `OR` operation, pass multiple values into the query method separated by commas.\nFor example `Query.equal('title', ['Avatar', 'Lord of the Rings'])` will fetch the movies `Avatar` or `Lord of the Rings`.\n\n{% info title=\"Default pagination behavior\" %}\nBy default, results are limited to the **first 25 items**.\nYou can change this through [pagination](/docs/products/databases/legacy/pagination).\n{% /info %}\n\n{% multicode %}\n\n```client-web\nimport { Client, Databases, Query } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nconst databases = new Databases(client);\n\ndatabases.listDocuments(\n '',\n '',\n [\n Query.equal('title', ['Avatar', 'Lord of the Rings']),\n Query.greaterThan('year', 1999)\n ]\n);\n```\n```server-nodejs\nconst sdk = require('node-appwrite');\n\nconst client = new sdk.Client();\n\nconst databases = new sdk.Databases(client);\n\nclient\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('')\n;\n\nconst promise = databases.listDocuments(\n '',\n '',\n [\n sdk.Query.equal('title', ['Avatar', 'Lord of the Rings']),\n sdk.Query.greaterThan('year', 1999)\n ]\n);\n\npromise.then(function (response) {\n console.log(response);\n}, function (error) {\n console.log(error);\n});\n```\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nvoid main() async {\n final client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\n final databases = Databases(client);\n\n try {\n final documents = await databases.listDocuments(\n '',\n '',\n [\n Query.equal('title', ['Avatar', 'Lord of the Rings']),\n Query.greaterThan('year', 1999)\n ]\n );\n } on AppwriteException catch(e) {\n print(e);\n }\n}\n```\n```client-apple\nimport Appwrite\nimport AppwriteModels\n\nfunc main() async throws {\n let client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\n let databases = Databases(client)\n\n do {\n let documents = try await databases.listDocuments(\n databaseId: \"\",\n collectionId: \"\",\n queries: [\n Query.equal(\"title\", value: [\"Avatar\", \"Lord of the Rings\"]),\n Query.greaterThan(\"year\", value: 1999)\n ]\n )\n } catch {\n print(error.localizedDescription)\n }\n}\n```\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.Query\nimport io.appwrite.services.Databases\n\nsuspend fun main() {\n val client = Client(applicationContext)\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\n val databases = Databases(client)\n\n try {\n val documents = databases.listDocuments(\n databaseId = \"\",\n collectionId = \"\",\n queries = listOf(\n Query.equal(\"title\", listOf(\"Avatar\", \"Lord of the Rings\")),\n Query.greaterThan(\"year\", 1999)\n )\n )\n } catch (e: AppwriteException) {\n Log.e(\"Appwrite\", e.message)\n }\n}\n```\n```php\nsetEndpoint('https://.cloud.appwrite.io/v1')\n ->setProject('')\n ->setKey('')\n;\n\n$databases = new Databases($client);\n\n$result = $databases->listDocuments(\n '',\n '',\n [\n Query::equal('title', ['Avatar', 'Lord of the Rings']),\n Query::greaterThan('year', 1999)\n ]\n);\n```\n```python\nfrom appwrite.client import Client\nfrom appwrite.query import Query\nfrom appwrite.services.databases import Databases\n\nclient = Client()\n\n(client\n .set_endpoint('https://.cloud.appwrite.io/v1')\n .set_project('')\n .set_key('')\n)\n\ndatabases = Databases(client)\n\nresult = databases.list_documents(\n '',\n '',\n [\n Query.equal('title', ['Avatar', 'Lord of the Rings']),\n Query.greater_than('year', 1999)\n ]\n)\n```\n```graphql\nquery {\n databasesListDocuments(\n databaseId: \"\",\n collectionId: \"\"\n queries: [\n \"{\\\"method\\\":\\\"equal\\\",\\\"attribute\\\":\\\"title\\\",\\\"values\\\":[\\\"Avatar\\\",\\\"Lord of the Rings\\\"]}\",\n \"{\\\"method\\\":\\\"greaterThan\\\",\\\"attribute\\\":\\\"year\\\",\\\"values\\\":[1999]}\"\n ]\n ) {\n total\n documents {\n _id\n data\n }\n }\n}\n```\n```http\nGET /v1/databases//collections//documents?queries[]=%7B%22method%22%3A%22equal%22%2C%22attribute%22%3A%22title%22%2C%22values%22%3A%5B%22Avatar%22%2C%22Lord%20of%20the%20Rings%22%5D%7D&queries[]=%7B%22method%22%3A%22greaterThan%22%2C%22attribute%22%3A%22year%22%2C%22values%22%3A%5B1999%5D%7D HTTP/1.1\nContent-Type: application/json\nX-Appwrite-Project: \n```\n```rust\nuse appwrite::Client;\nuse appwrite::services::databases::Databases;\nuse appwrite::query::Query;\nuse serde_json::json;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box> {\n let client = Client::new()\n .set_endpoint(\"https://.cloud.appwrite.io/v1\")\n .set_project(\"\")\n .set_key(\"\");\n\n let databases = Databases::new(&client);\n\n let result = databases.list_documents(\n \"\",\n \"\",\n Some(vec![\n Query::equal(\"title\", json!([\"Avatar\", \"Lord of the Rings\"])).to_string(),\n Query::greater_than(\"year\", 1999).to_string(),\n ]),\n None,\n None,\n None,\n ).await?;\n\n println!(\"{:?}\", result);\n\n Ok(())\n}\n```\n{% /multicode %}\n\n# Query operators {% #query-operators %}\n\n## Select {% #select %}\n\nThe `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.\n\n{% multicode %}\n```client-web\nQuery.select([\"name\", \"title\"])\n```\n```client-flutter\nQuery.select([\"name\", \"title\"])\n```\n```python\nQuery.select([\"name\", \"title\"])\n```\n```ruby\nQuery.select([\"name\", \"title\"])\n```\n```server-nodejs\nQuery.select([\"name\", \"title\"])\n```\n```php\nQuery::select([\"name\", \"title\"])\n```\n```swift\nQuery.select([\"name\", \"title\"])\n```\n```http\n{\"method\":\"select\",\"values\":[\"name\",\"title\"]}\n```\n```rust\nQuery::select(vec![\"name\", \"title\"])\n```\n{% /multicode %}\n\n### Select relationship data {% #relationship-select %}\n\nWith [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.\n\n#### Get documents without relationships\nBy default, documents return only their own fields:\n\n{% multicode %}\n```client-web\nconst doc = await databases.getDocument(\n '', '', '',\n [Query.select(['name', 'age'])]\n);\n```\n```client-flutter\nfinal doc = await databases.getDocument(\n databaseId: '',\n collectionId: '',\n documentId: '',\n queries: [Query.select([\"name\", \"age\"])]\n);\n```\n```python\ndoc = databases.get_document(\n '', '', '',\n [Query.select([\"name\", \"age\"])]\n)\n```\n```ruby\ndoc = databases.get_document(\n '', '', '',\n [Query.select([\"name\", \"age\"])]\n)\n```\n```server-nodejs\nconst doc = await databases.getDocument(\n '', '', '',\n [Query.select(['name', 'age'])]\n);\n```\n```php\n$doc = $databases->getDocument(\n '', '', '',\n [Query::select([\"name\", \"age\"])]\n);\n```\n```swift\nlet doc = try await databases.getDocument(\n databaseId: \"\",\n collectionId: \"\",\n documentId: \"\",\n queries: [Query.select([\"name\", \"age\"])]\n)\n```\n```http\nGET /v1/databases//collections//documents/?queries[]=%7B%22method%22%3A%22select%22%2C%22values%22%3A%5B%22name%22%2C%22age%22%5D%7D HTTP/1.1\nContent-Type: application/json\nX-Appwrite-Project: \n```\n```rust\nlet doc = databases.get_document(\n \"\",\n \"\",\n \"\",\n Some(vec![\n Query::select(vec![\"name\", \"age\"]).to_string(),\n ]),\n None,\n).await?;\n```\n{% /multicode %}\n\n#### Load all relationship data\nUse the `*` wildcard to load all fields from related documents:\n\n{% multicode %}\n```client-web\nconst doc = await databases.getDocument(\n '', '', '',\n [Query.select(['*', 'reviews.*'])]\n);\n```\n```client-flutter\nfinal doc = await databases.getDocument(\n databaseId: '',\n collectionId: '',\n documentId: '',\n queries: [Query.select([\"*\", \"reviews.*\"])]\n);\n```\n```python\ndoc = databases.get_document(\n '', '', '',\n [Query.select([\"*\", \"reviews.*\"])]\n)\n```\n```ruby\ndoc = databases.get_document(\n '', '', '',\n [Query.select([\"*\", \"reviews.*\"])]\n)\n```\n```server-nodejs\nconst doc = await databases.getDocument(\n '', '', '',\n [Query.select([\"*\", \"reviews.*\"])]\n);\n```\n```php\n$doc = $databases->getDocument(\n '', '', '',\n [Query::select([\"*\", \"reviews.*\"])]\n);\n```\n```swift\nlet doc = try await databases.getDocument(\n databaseId: \"\",\n collectionId: \"\",\n documentId: \"\",\n queries: [Query.select([\"*\", \"reviews.*\"])]\n)\n```\n```http\nGET /v1/databases//collections//documents/?queries[]=%7B%22method%22%3A%22select%22%2C%22values%22%3A%5B%22%2A%22%2C%22reviews.%2A%22%5D%7D HTTP/1.1\nContent-Type: application/json\nX-Appwrite-Project: \n{\"method\":\"select\",\"values\":[\"*\",\"reviews.*\"]}\n```\n```rust\nlet doc = databases.get_document(\n \"\",\n \"\",\n \"\",\n Some(vec![\n Query::select(vec![\"*\", \"reviews.*\"]).to_string(),\n ]),\n None,\n).await?;\n```\n{% /multicode %}\n\n#### Select specific relationship fields\nFor precise control, select only specific fields from related documents:\n\n{% multicode %}\n```client-web\nconst doc = await databases.getDocument(\n '', '', '',\n [Query.select(['name', 'age', 'reviews.author', 'reviews.rating'])]\n);\n```\n```client-flutter\nfinal doc = await databases.getDocument(\n databaseId: '',\n collectionId: '',\n documentId: '',\n queries: [Query.select([\"name\", \"age\", \"reviews.author\", \"reviews.rating\"])]\n);\n```\n```python\ndoc = databases.get_document(\n '', '', '',\n [Query.select([\"name\", \"age\", \"reviews.author\", \"reviews.rating\"])]\n)\n```\n```ruby\ndoc = databases.get_document(\n '', '', '',\n [Query.select([\"name\", \"age\", \"reviews.author\", \"reviews.rating\"])]\n)\n```\n```server-nodejs\nconst doc = await databases.getDocument(\n '', '', '',\n [Query.select([\"name\", \"age\", \"reviews.author\", \"reviews.rating\"])]\n);\n// Result: { name: \"John\", age: 30, reviews: [{ author: \"...\", rating: 5 }] }\n```\n```php\n$doc = $databases->getDocument(\n '', '', '',\n [Query::select([\"name\", \"age\", \"reviews.author\", \"reviews.rating\"])]\n);\n```\n```swift\nlet doc = try await databases.getDocument(\n databaseId: \"\",\n collectionId: \"\",\n documentId: \"\",\n queries: [Query.select([\"name\", \"age\", \"reviews.author\", \"reviews.rating\"])]\n)\n```\n```http\n# Load specific fields from main and related documents\n{\"method\":\"select\",\"values\":[\"name\",\"age\",\"reviews.author\",\"reviews.rating\"]}\n```\n```rust\nlet doc = databases.get_document(\n \"\",\n \"\",\n \"\",\n Some(vec![\n Query::select(vec![\"name\", \"age\", \"reviews.author\", \"reviews.rating\"]).to_string(),\n ]),\n None,\n).await?;\n```\n{% /multicode %}\n\n#### Load nested relationships\nYou can also load relationships of relationships:\n\n{% multicode %}\n```client-web\nQuery.select([\"*\", \"reviews.*\", \"reviews.author.*\"])\n```\n```client-flutter\nQuery.select([\"*\", \"reviews.*\", \"reviews.author.*\"])\n```\n```python\nQuery.select([\"*\", \"reviews.*\", \"reviews.author.*\"])\n```\n```ruby\nQuery.select([\"*\", \"reviews.*\", \"reviews.author.*\"])\n```\n```server-nodejs\nQuery.select([\"*\", \"reviews.*\", \"reviews.author.*\"])\n```\n```php\nQuery::select([\"*\", \"reviews.*\", \"reviews.author.*\"])\n```\n```swift\nQuery.select([\"*\", \"reviews.*\", \"reviews.author.*\"])\n```\n```http\n{\"method\":\"select\",\"values\":[\"*\",\"reviews.*\",\"reviews.author.*\"]}\n```\n```rust\nQuery::select(vec![\"*\", \"reviews.*\", \"reviews.author.*\"])\n```\n{% /multicode %}\n\n### Use selection patterns {% #select-patterns %}\n\n| Pattern | Description | Use case |\n|---------|-------------|----------|\n| `[\"field1\", \"field2\"]` | Specific attributes only | Minimize response size |\n| `[\"*\"]` | All document attributes | Get complete document data |\n| `[\"*\", \"relationName.*\"]` | Document + all relationship fields | Load document with complete related data |\n| `[\"field1\", \"relationName.field2\"]` | Specific fields from document and relationships | Precise data loading |\n| `[\"*\", \"relationName.field1\", \"relationName.field2\"]` | All document fields + specific relationship fields | Partial relationship loading |\n| `[\"relationName.*\", \"relationName.nestedRelation.*\"]` | Nested relationship loading | Load relationships of relationships |\n\n### Optimize performance {% #select-performance %}\n\n**Optimize response size** - Only select the fields you actually need. Smaller responses are faster to transfer and parse.\n\n**Control relationship loading** - Related documents are not loaded by default. Use explicit selection to load only the relationships you need.\n\n**Reduce database load** - Selecting fewer fields reduces database processing time, especially for large documents.\n\n{% info title=\"Related documents\" %}\nBy default, relationship attributes contain only document IDs.\nTo load the actual related document data, you must explicitly include relationship fields in your select query.\nLearn more about [relationship performance optimization](/docs/products/databases/legacy/relationships#performance-loading).\n{% /info %}\n\n## Comparison operators {% #comparison %}\n\n### Equal {% #equal %}\n\nReturns document if attribute is equal to any value in the provided array.\n\n{% multicode %}\n```client-web\nQuery.equal(\"title\", [\"Iron Man\"])\n```\n```client-flutter\nQuery.equal(\"title\", [\"Iron Man\"])\n```\n```python\nQuery.equal(\"title\", [\"Iron Man\"])\n```\n```ruby\nQuery.equal(\"title\", [\"Iron Man\"])\n```\n```server-nodejs\nQuery.equal(\"title\", [\"Iron Man\"])\n```\n```php\nQuery::equal(\"title\", [\"Iron Man\"])\n```\n```swift\nQuery.equal(\"title\", value: [\"Iron Man\"])\n```\n```http\n{\"method\":\"equal\",\"attribute\":\"title\",\"values\":[\"Iron Man\"]}\n```\n```rust\nQuery::equal(\"title\", json!([\"Iron Man\"]))\n```\n{% /multicode %}\n\n### Not equal {% #not-equal %}\n\nReturns document if attribute is not equal to any value in the provided array.\n\n{% multicode %}\n```client-web\nQuery.notEqual(\"title\", \"Iron Man\")\n```\n```client-flutter\nQuery.notEqual(\"title\", \"Iron Man\")\n```\n```python\nQuery.not_equal(\"title\", \"Iron Man\")\n```\n```ruby\nQuery.not_equal(\"title\", \"Iron Man\")\n```\n```server-nodejs\nQuery.notEqual(\"title\", \"Iron Man\")\n```\n```php\nQuery::notEqual(\"title\", \"Iron Man\")\n```\n```swift\nQuery.notEqual(\"title\", value: \"Iron Man\")\n```\n```http\n{\"method\":\"notEqual\",\"attribute\":\"title\",\"values\":\"Iron Man\"}\n```\n```rust\nQuery::not_equal(\"title\", \"Iron Man\")\n```\n{% /multicode %}\n\n### Less than {% #less-than %}\n\nReturns document if attribute is less than the provided value.\n\n{% multicode %}\n```client-web\nQuery.lessThan(\"score\", 10)\n```\n```client-flutter\nQuery.lessThan(\"score\", 10)\n```\n```python\nQuery.less_than(\"score\", 10)\n```\n```ruby\nQuery.less_than(\"score\", 10)\n```\n```server-nodejs\nQuery.lessThan(\"score\", 10)\n```\n```php\nQuery::lessThan(\"score\", 10)\n```\n```swift\nQuery.lessThan(\"score\", value: 10)\n```\n```http\n{\"method\":\"lessThan\",\"attribute\":\"score\",\"values\":[10]}\n```\n```rust\nQuery::less_than(\"score\", 10)\n```\n{% /multicode %}\n\n### Less than or equal {% #less-than-equal %}\n\nReturns document if attribute is less than or equal to the provided value.\n\n{% multicode %}\n```client-web\nQuery.lessThanEqual(\"score\", 10)\n```\n```client-flutter\nQuery.lessThanEqual(\"score\", 10)\n```\n```python\nQuery.less_than_equal(\"score\", 10)\n```\n```ruby\nQuery.less_than_equal(\"score\", 10)\n```\n```server-nodejs\nQuery.lessThanEqual(\"score\", 10)\n```\n```php\nQuery::lessThanEqual(\"score\", 10)\n```\n```swift\nQuery.lessThanEqual(\"score\", value: 10)\n```\n```http\n{\"method\":\"lessThanEqual\",\"attribute\":\"score\",\"values\":[10]}\n```\n```rust\nQuery::less_than_equal(\"score\", 10)\n```\n{% /multicode %}\n\n### Greater than {% #greater-than %}\n\nReturns document if attribute is greater than the provided value.\n\n{% multicode %}\n```client-web\nQuery.greaterThan(\"score\", 10)\n```\n```client-flutter\nQuery.greaterThan(\"score\", 10)\n```\n```python\nQuery.greater_than(\"score\", 10)\n```\n```ruby\nQuery.greater_than(\"score\", 10)\n```\n```server-nodejs\nQuery.greaterThan(\"score\", 10)\n```\n```php\nQuery::greaterThan(\"score\", 10)\n```\n```swift\nQuery.greaterThan(\"score\", value: 10)\n```\n```http\n{\"method\":\"greaterThan\",\"attribute\":\"score\",\"values\":[10]}\n```\n```rust\nQuery::greater_than(\"score\", 10)\n```\n{% /multicode %}\n\n### Greater than or equal {% #greater-than-equal %}\n\nReturns document if attribute is greater than or equal to the provided value.\n\n{% multicode %}\n```client-web\nQuery.greaterThanEqual(\"score\", 10)\n```\n```client-flutter\nQuery.greaterThanEqual(\"score\", 10)\n```\n```python\nQuery.greater_than_equal(\"score\", 10)\n```\n```ruby\nQuery.greater_than_equal(\"score\", 10)\n```\n```server-nodejs\nQuery.greaterThanEqual(\"score\", 10)\n```\n```php\nQuery::greaterThanEqual(\"score\", 10)\n```\n```swift\nQuery.greaterThanEqual(\"score\", value: 10)\n```\n```http\n{\"method\":\"greaterThanEqual\",\"attribute\":\"score\",\"values\":[10]}\n```\n```rust\nQuery::greater_than_equal(\"score\", 10)\n```\n{% /multicode %}\n\n### Between {% #between %}\n\nReturns document if attribute value falls between the two values. The boundary values are inclusive and can be strings or numbers.\n\n{% multicode %}\n```client-web\nQuery.between(\"price\", 5, 10)\n```\n```client-flutter\nQuery.between(\"price\", 5, 10)\n```\n```python\nQuery.between(\"price\", 5, 10)\n```\n```ruby\nQuery.between(\"price\", 5, 10)\n```\n```server-nodejs\nQuery.between(\"price\", 5, 10)\n```\n```php\nQuery::between(\"price\", 5, 10)\n```\n```swift\nQuery.between(\"price\", start: 5, end: 10)\n```\n```http\n{\"method\":\"between\",\"attribute\":\"price\",\"values\":[5,10]}\n```\n```rust\nQuery::between(\"price\", 5, 10)\n```\n{% /multicode %}\n\n## Null checks {% #null-checks %}\n\n### Is null {% #is-null %}\n\nReturns documents where attribute value is null.\n\n{% multicode %}\n```client-web\nQuery.isNull(\"name\")\n```\n```client-flutter\nQuery.isNull(\"name\")\n```\n```python\nQuery.is_null(\"name\")\n```\n```ruby\nQuery.is_null(\"name\")\n```\n```server-nodejs\nQuery.isNull(\"name\")\n```\n```php\nQuery::isNull(\"name\")\n```\n```swift\nQuery.isNull(\"name\")\n```\n```http\n{\"method\":\"isNull\",\"attribute\":\"name\"}\n```\n```rust\nQuery::is_null(\"name\")\n```\n{% /multicode %}\n\n### Is not null {% #is-not-null %}\n\nReturns documents where attribute value is **not** null.\n\n{% multicode %}\n```client-web\nQuery.isNotNull(\"name\")\n```\n```client-flutter\nQuery.isNotNull(\"name\")\n```\n```python\nQuery.is_not_null(\"name\")\n```\n```ruby\nQuery.is_not_null(\"name\")\n```\n```server-nodejs\nQuery.isNotNull(\"name\")\n```\n```php\nQuery::isNotNull(\"name\")\n```\n```swift\nQuery.isNotNull(\"name\")\n```\n```http\n{\"method\":\"isNotNull\",\"attribute\":\"name\"}\n```\n```rust\nQuery::is_not_null(\"name\")\n```\n{% /multicode %}\n\n## String operations {% #string-operations %}\n\n### Starts with {% #starts-with %}\n\nReturns documents if a string attribute starts with a substring.\n\n{% multicode %}\n```client-web\nQuery.startsWith(\"name\", \"Once upon a time\")\n```\n```client-flutter\nQuery.startsWith(\"name\", \"Once upon a time\")\n```\n```python\nQuery.starts_with(\"name\", \"Once upon a time\")\n```\n```ruby\nQuery.starts_with(\"name\", \"Once upon a time\")\n```\n```server-nodejs\nQuery.startsWith(\"name\", \"Once upon a time\")\n```\n```php\nQuery::startsWith(\"name\", \"Once upon a time\")\n```\n```swift\nQuery.startsWith(\"name\", value: \"Once upon a time\")\n```\n```http\n{\"method\":\"startsWith\",\"attribute\":\"name\",\"values\":[\"Once upon a time\"]}\n```\n```rust\nQuery::starts_with(\"name\", \"Once upon a time\")\n```\n{% /multicode %}\n\n### Ends with {% #ends-with %}\n\nReturns documents if a string attribute ends with a substring.\n\n{% multicode %}\n```client-web\nQuery.endsWith(\"name\", \"happily ever after.\")\n```\n```client-flutter\nQuery.endsWith(\"name\", \"happily ever after.\")\n```\n```python\nQuery.ends_with(\"name\", \"happily ever after.\")\n```\n```ruby\nQuery.ends_with(\"name\", \"happily ever after.\")\n```\n```server-nodejs\nQuery.endsWith(\"name\", \"happily ever after.\")\n```\n```php\nQuery::endsWith(\"name\", \"happily ever after.\")\n```\n```swift\nQuery.endsWith(\"name\", value: \"happily ever after.\")\n```\n```http\n{\"method\":\"endsWith\",\"attribute\":\"name\",\"values\":[\"happily ever after.\"]}\n```\n```rust\nQuery::ends_with(\"name\", \"happily ever after.\")\n```\n{% /multicode %}\n\n### Contains {% #contains %}\n\nReturns documents if the array attribute contains the specified elements or if a string attribute contains the specified substring.\n\n{% multicode %}\n```client-web\n// For arrays\nQuery.contains(\"ingredients\", ['apple', 'banana'])\n\n// For strings\nQuery.contains(\"name\", \"Tom\")\n```\n```client-flutter\n// For arrays\nQuery.contains(\"ingredients\", ['apple', 'banana'])\n\n// For strings\nQuery.contains(\"name\", \"Tom\")\n```\n```python\n# For arrays\nQuery.contains(\"ingredients\", ['apple', 'banana'])\n\n# For strings\nQuery.contains(\"name\", \"Tom\")\n```\n```ruby\n# For arrays\nQuery.contains(\"ingredients\", ['apple', 'banana'])\n\n# For strings\nQuery.contains(\"name\", \"Tom\")\n```\n```server-nodejs\n// For arrays\nQuery.contains(\"ingredients\", ['apple', 'banana'])\n\n// For strings\nQuery.contains(\"name\", \"Tom\")\n```\n```php\n// For arrays\nQuery::contains(\"ingredients\", ['apple', 'banana'])\n\n// For strings\nQuery::contains(\"name\", \"Tom\")\n```\n```swift\n// For arrays\nQuery.contains(\"ingredients\", value: ['apple', 'banana'])\n\n// For strings\nQuery.contains(\"name\", value: \"Tom\")\n```\n```http\n# For arrays\n{\"method\":\"contains\",\"attribute\":\"ingredients\",\"values\":[\"apple\",\"banana\"]}\n\n# For strings\n{\"method\":\"contains\",\"attribute\":\"name\",\"values\":[\"Tom\"]}\n```\n```rust\n// For arrays\nQuery::contains(\"ingredients\", json!([\"apple\", \"banana\"]))\n\n// For strings\nQuery::contains(\"name\", \"Tom\")\n```\n{% /multicode %}\n\n### Search {% #search %}\n\nSearches 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.\n\n{% info title=\"Searching values with hyphens\" %}\nThe 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\"')`.\n{% /info %}\n\n{% multicode %}\n```client-web\nQuery.search(\"text\", \"key words\")\n```\n```client-flutter\nQuery.search(\"text\", \"key words\")\n```\n```python\nQuery.search(\"text\", \"key words\")\n```\n```ruby\nQuery.search(\"text\", \"key words\")\n```\n```server-nodejs\nQuery.search(\"text\", \"key words\")\n```\n```php\nQuery::search(\"text\", \"key words\")\n```\n```swift\nQuery.search(\"text\", value: \"key words\")\n```\n```http\n{\"method\":\"search\",\"attribute\":\"text\",\"values\":[\"key words\"]}\n```\n```rust\nQuery::search(\"text\", \"key words\")\n```\n{% /multicode %}\n\n## Logical operators {% #logical-operators %}\n\n### AND {% #and %}\n\nReturns document if it matches all of the nested sub-queries in the array passed in.\n\n{% multicode %}\n```client-web\nQuery.and([\n Query.lessThan(\"size\", 10),\n Query.greaterThan(\"size\", 5)\n])\n```\n```client-flutter\nQuery.and([\n Query.lessThan(\"size\", 10),\n Query.greaterThan(\"size\", 5)\n])\n```\n```python\nQuery.and_queries([\n Query.less_than(\"size\", 10),\n Query.greater_than(\"size\", 5)\n])\n```\n```ruby\nQuery.and([\n Query.less_than(\"size\", 10),\n Query.greater_than(\"size\", 5)\n])\n```\n```server-nodejs\nQuery.and([\n Query.lessThan(\"size\", 10),\n Query.greaterThan(\"size\", 5)\n])\n```\n```php\nQuery::and([\n Query::lessThan(\"size\", 10),\n Query::greaterThan(\"size\", 5)\n])\n```\n```swift\nQuery.and([\n Query.lessThan(\"size\", value: 10),\n Query.greaterThan(\"size\", value: 5)\n])\n```\n```http\n{\"method\":\"and\",\"values\":[{\"method\":\"lessThan\",\"attribute\":\"size\",\"values\":[10]},{\"method\":\"greaterThan\",\"attribute\":\"size\",\"values\":[5]}]}\n```\n```rust\nQuery::and(vec![\n Query::less_than(\"size\", 10).to_string(),\n Query::greater_than(\"size\", 5).to_string(),\n])\n```\n{% /multicode %}\n\n### OR {% #or %}\n\nReturns document if it matches any of the nested sub-queries in the array passed in.\n\n{% multicode %}\n```client-web\nQuery.or([\n Query.lessThan(\"size\", 5),\n Query.greaterThan(\"size\", 10)\n])\n```\n```client-flutter\nQuery.or([\n Query.lessThan(\"size\", 5),\n Query.greaterThan(\"size\", 10)\n])\n```\n```python\nQuery.or_queries([\n Query.less_than(\"size\", 5),\n Query.greater_than(\"size\", 10)\n])\n```\n```ruby\nQuery.or([\n Query.less_than(\"size\", 5),\n Query.greater_than(\"size\", 10)\n])\n```\n```server-nodejs\nQuery.or([\n Query.lessThan(\"size\", 5),\n Query.greaterThan(\"size\", 10)\n])\n```\n```php\nQuery::or([\n Query::lessThan(\"size\", 5),\n Query::greaterThan(\"size\", 10)\n])\n```\n```swift\nQuery.or([\n Query.lessThan(\"size\", value: 5),\n Query.greaterThan(\"size\", value: 10)\n])\n```\n```http\n{\"method\":\"or\",\"values\":[{\"method\":\"lessThan\",\"attribute\":\"size\",\"values\":[5]},{\"method\":\"greaterThan\",\"attribute\":\"size\",\"values\":[10]}]}\n```\n```rust\nQuery::or(vec![\n Query::less_than(\"size\", 5).to_string(),\n Query::greater_than(\"size\", 10).to_string(),\n])\n```\n{% /multicode %}\n\n## Ordering {% #ordering %}\n\n### Order descending {% #order-desc %}\n\nOrders results in descending order by attribute. Attribute must be indexed.\n\n{% multicode %}\n```client-web\nQuery.orderDesc(\"attribute\")\n```\n```client-flutter\nQuery.orderDesc(\"attribute\")\n```\n```python\nQuery.order_desc(\"attribute\")\n```\n```ruby\nQuery.order_desc(\"attribute\")\n```\n```server-nodejs\nQuery.orderDesc(\"attribute\")\n```\n```php\nQuery::orderDesc(\"attribute\")\n```\n```swift\nQuery.orderDesc(\"attribute\")\n```\n```http\n{\"method\":\"orderDesc\",\"attribute\":\"attribute\"}\n```\n```rust\nQuery::order_desc(\"attribute\")\n```\n{% /multicode %}\n\n### Order ascending {% #order-asc %}\n\nOrders results in ascending order by attribute. Attribute must be indexed.\n\n{% multicode %}\n```client-web\nQuery.orderAsc(\"attribute\")\n```\n```client-flutter\nQuery.orderAsc(\"attribute\")\n```\n```python\nQuery.order_asc(\"attribute\")\n```\n```ruby\nQuery.order_asc(\"attribute\")\n```\n```server-nodejs\nQuery.orderAsc(\"attribute\")\n```\n```php\nQuery::orderAsc(\"attribute\")\n```\n```swift\nQuery.orderAsc(\"attribute\")\n```\n```http\n{\"method\":\"orderAsc\",\"attribute\":\"attribute\"}\n```\n```rust\nQuery::order_asc(\"attribute\")\n```\n{% /multicode %}\n\n## Pagination {% #pagination %}\n\n### Limit {% #limit %}\n\nLimits the number of results returned by the query. Used for [pagination](/docs/products/databases/legacy/pagination).\n\n{% multicode %}\n```client-web\nQuery.limit(25)\n```\n```client-flutter\nQuery.limit(25)\n```\n```python\nQuery.limit(25)\n```\n```ruby\nQuery.limit(25)\n```\n```server-nodejs\nQuery.limit(25)\n```\n```php\nQuery::limit(25)\n```\n```swift\nQuery.limit(25)\n```\n```http\n{\"method\":\"limit\",\"values\":[25]}\n```\n```rust\nQuery::limit(25)\n```\n{% /multicode %}\n\n### Offset {% #offset %}\n\nOffset the results returned by skipping some of the results. Used for [pagination](/docs/products/databases/legacy/pagination).\n\n{% multicode %}\n```client-web\nQuery.offset(0)\n```\n```client-flutter\nQuery.offset(0)\n```\n```python\nQuery.offset(0)\n```\n```ruby\nQuery.offset(0)\n```\n```server-nodejs\nQuery.offset(0)\n```\n```php\nQuery::offset(0)\n```\n```swift\nQuery.offset(0)\n```\n```http\n{\"method\":\"offset\",\"values\":[0]}\n```\n```rust\nQuery::offset(0)\n```\n{% /multicode %}\n\n### Cursor after {% #cursor-after %}\n\nPlaces the cursor after the specified resource ID. Used for [pagination](/docs/products/databases/legacy/pagination).\n\n{% multicode %}\n```client-web\nQuery.cursorAfter(\"62a7...f620\")\n```\n```client-flutter\nQuery.cursorAfter(\"62a7...f620\")\n```\n```python\nQuery.cursor_after(\"62a7...f620\")\n```\n```ruby\nQuery.cursor_after(\"62a7...f620\")\n```\n```server-nodejs\nQuery.cursorAfter(\"62a7...f620\")\n```\n```php\nQuery::cursorAfter(\"62a7...f620\")\n```\n```swift\nQuery.cursorAfter(\"62a7...f620\")\n```\n```http\n{\"method\":\"cursorAfter\",\"values\":[\"62a7...f620\"]}\n```\n```rust\nQuery::cursor_after(\"62a7...f620\")\n```\n{% /multicode %}\n\n### Cursor before {% #cursor-before %}\n\nPlaces the cursor before the specified resource ID. Used for [pagination](/docs/products/databases/legacy/pagination).\n\n{% multicode %}\n```client-web\nQuery.cursorBefore(\"62a7...a600\")\n```\n```client-flutter\nQuery.cursorBefore(\"62a7...a600\")\n```\n```python\nQuery.cursor_before(\"62a7...a600\")\n```\n```ruby\nQuery.cursor_before(\"62a7...a600\")\n```\n```server-nodejs\nQuery.cursorBefore(\"62a7...a600\")\n```\n```php\nQuery::cursorBefore(\"62a7...a600\")\n```\n```swift\nQuery.cursorBefore(\"62a7...a600\")\n```\n```http\n{\"method\":\"cursorBefore\",\"values\":[\"62a7...a600\"]}\n```\n```rust\nQuery::cursor_before(\"62a7...a600\")\n```\n{% /multicode %}\n\n# Complex queries {% #complex-queries %}\n\nYou 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:\n\n{% multicode %}\n```client-web\nconst results = await databases.listDocuments(\n '',\n '',\n [\n Query.or([\n Query.and([\n Query.equal('category', ['books']),\n Query.lessThan('price', 20)\n ]),\n Query.and([\n Query.equal('category', ['magazines']),\n Query.lessThan('price', 10)\n ])\n ])\n ]\n);\n```\n```client-flutter\nfinal results = await databases.listDocuments(\n '',\n '',\n [\n Query.or([\n Query.and([\n Query.equal('category', ['books']),\n Query.lessThan('price', 20)\n ]),\n Query.and([\n Query.equal('category', ['magazines']),\n Query.lessThan('price', 10)\n ])\n ])\n ]\n);\n```\n```python\nresults = databases.list_documents(\n database_id='',\n collection_id='',\n queries=[\n Query.or_queries([\n Query.and_queries([\n Query.equal('category', ['books']),\n Query.less_than('price', 20)\n ]),\n Query.and_queries([\n Query.equal('category', ['magazines']),\n Query.less_than('price', 10)\n ])\n ])\n ]\n)\n```\n```http\n{\"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]}]}]}\n```\n```rust\nuse appwrite::Client;\nuse appwrite::services::databases::Databases;\nuse appwrite::query::Query;\nuse serde_json::json;\n\nlet client = Client::new()\n .set_endpoint(\"https://.cloud.appwrite.io/v1\")\n .set_project(\"\")\n .set_key(\"\");\n\nlet databases = Databases::new(&client);\n\nlet results = databases.list_documents(\n \"\",\n \"\",\n Some(vec![\n Query::or(vec![\n Query::and(vec![\n Query::equal(\"category\", json!([\"books\"])).to_string(),\n Query::less_than(\"price\", 20).to_string(),\n ]).to_string(),\n Query::and(vec![\n Query::equal(\"category\", json!([\"magazines\"])).to_string(),\n Query::less_than(\"price\", 10).to_string(),\n ]).to_string(),\n ]).to_string(),\n ]),\n None,\n None,\n None,\n).await?;\n```\n{% /multicode %}\n\nThis 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.\nEach 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."}, {"path": "docs/products/databases/legacy/quick-start", "title": "Start with Databases", "description": "Get started with Appwrite Databases. Follow a step-by-step guide to create your first database, define collections, and perform basic data operations.", "content": "{% section #create-database step=1 title=\"Create database\" %}\nHead to your [Appwrite Console](https://cloud.appwrite.io/console/) and create a database and name it `Oscar`.\nOptionally, add a custom database ID.\n{% /section %}\n\n{% section #create-collection step=2 title=\"Create collection\" %}\nCreate a collection and name it `My books`. Optionally, add a custom collection ID.\n\nNavigate to **Attributes** and create attributes by clicking **Create attribute** and select **String**.\nAttributes define the structure of your collection's documents. Enter **Attribute key** and **Size**. For example, `title` and `100`.\n\nNavigate to **Settings** > **Permissions** and add a new role **Any**.\nCheck the **CREATE** and **READ** permissions, so anyone can create and read documents.\n{% /section %}\n\n\n{% section #create-documents step=3 title=\"Create documents\" %}\nTo create a document use the `createDocument` method.\n\nIn the **Settings** menu, find your project ID and replace `` in the example.\n\nNavigate to the `Oscar` database, copy the database ID, and replace ``.\nThen, in the `My books` collection, copy the collection ID, and replace ``.\n\n{% multicode %}\n```client-web\nimport { Client, Databases, ID } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nconst databases = new Databases(client);\n\nconst promise = databases.createDocument(\n '',\n '',\n ID.unique(),\n { \"title\": \"Hamlet\" }\n);\n\npromise.then(function (response) {\n console.log(response);\n}, function (error) {\n console.log(error);\n});\n```\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nvoid main() async {\n final client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\n final databases = Databases(client);\n\n try {\n final document = databases.createDocument(\n databaseId: '',\n collectionId: '',\n documentId: ID.unique(),\n data: { \"title\": \"Hamlet\" }\n );\n } on AppwriteException catch(e) {\n print(e);\n }\n}\n```\n```client-apple\nimport Appwrite\nimport AppwriteModels\n\nfunc main() async throws {\n let client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\n let databases = Databases(client)\n\n do {\n let document = try await databases.createDocument(\n databaseId: \"\",\n collectionId: \"\",\n documentId: ID.unique(),\n data: [\"title\" : \"hamlet\"]\n )\n } catch {\n print(error.localizedDescription)\n }\n}\n```\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Databases\n\nsuspend fun main() {\n val client = Client(applicationContext)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\n val databases = Databases(client)\n\n try {\n val document = databases.createDocument(\n databaseId = \"\",\n collectionId = \"\",\n documentId = ID.unique(),\n data = mapOf(\"title\" to \"hamlet\"),\n )\n } catch (e: Exception) {\n Log.e(\"Appwrite\", \"Error: \" + e.message)\n }\n}\n```\n{% /multicode %}\n\nThe response should look similar to this.\n\n```json\n{\n \"title\": \"Hamlet\",\n \"$id\": \"65013138dcd8618e80c4\",\n \"$permissions\": [],\n \"$createdAt\": \"2023-09-13T03:49:12.905+00:00\",\n \"$updatedAt\": \"2023-09-13T03:49:12.905+00:00\",\n \"$databaseId\": \"650125c64b3c25ce4bc4\",\n \"$collectionId\": \"650125cff227cf9f95ad\"\n}\n```\n\n{% /section %}\n\n{% section #list-documents step=4 title=\"List documents\" %}\nTo read and query data from your collection, use the `listDocuments` endpoint.\n\nLike the previous step, replace ``, ``, and`` with their respective IDs.\n{% multicode %}\n```client-web\nimport { Client, Databases, Query } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nconst databases = new Databases(client);\n\nlet promise = databases.listDocuments(\n \"\",\n \"\",\n [\n Query.equal('title', 'Hamlet')\n ]\n);\n\npromise.then(function (response) {\n console.log(response);\n}, function (error) {\n console.log(error);\n});\n```\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nvoid main() async {\n final client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\n final databases = Databases(client);\n\n try {\n final documents = await databases.listDocuments(\n databaseId: '',\n collectionId: '',\n queries: [\n Query.equal('title', 'Hamlet')\n ]\n );\n } on AppwriteException catch(e) {\n print(e);\n }\n}\n```\n```client-apple\nimport Appwrite\nimport AppwriteModels\n\nfunc main() async throws{\n let client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\n let databases = Databases(client)\n\n do {\n let documents = try await databases.listDocuments(\n databaseId: \"\",\n collectionId: \"\",\n queries: [\n Query.equal(\"title\", value: \"Hamlet\")\n ]\n )\n } catch {\n print(error.localizedDescription)\n }\n}\n```\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.Query\nimport io.appwrite.services.Databases\n\nsuspend fun main() {\n val client = Client(applicationContext)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\n val databases = Databases(client)\n\n try {\n val documents = databases.listDocuments(\n databaseId = \"\",\n collectionId = \"\",\n queries = listOf(\n Query.equal(\"title\", \"Hamlet\")\n )\n )\n } catch (e: AppwriteException) {\n Log.e(\"Appwrite\", \"Error: \" + e.message)\n }\n}\n```\n\n{% /multicode %}\n{% /section %}"}, {"path": "docs/products/databases/legacy/relationships", "title": "Relationships", "description": "Manage complex data relationships with Appwrite Databases. Discover how to define and work with relationships between documents for interconnected data.", "content": "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.\n\nThese types of association between entities can be modeled in Appwrite using relationships.\n\n# Relationship Attributes {% #relationship-attributes %}\n\nRelationships are represented in a collection using **relationship attributes**.\nThe relationship attribute contains the ID of related documents, which it references during read, update, and delete operations.\nThis attribute is **null** if a document has no related documents.\n\n# When to use a relationship {% #when-to-use-relationships %}\n\nRelationships 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.\n\n# Benefits of relationships {% #benefit-of-relationships %}\n\nDuplicated 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.\n\n# Tradeoff {% #trade-offs %}\n\nConsider 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.\n\n# Opt-in Loading {% #performance-loading %}\n\nBy 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.\n\n{% arrow_link href=\"/docs/products/databases/legacy/queries#relationship-select\" %}\nLearn how to load relationships with queries\n{% /arrow_link %}\n\n# Directionality {% #directionality %}\n\nAppwrite relationships can be one-way or two-way.\n\n| Type | Description |\n| -------- | ----------------------------------------------------------------------------------------------------------------- |\n| One-way | The relationship is only visible to one side of the relation. This is similar to a tree data structure. |\n| Two-way | The relationship is visible to both sides of the relationship. This is similar to a graph data structure. |\n\n# Types {% #types %}\n\nAppwrite provides four different relationship types to enforce different associative rules between documents.\n\n| Type | Description |\n| ----------- | ----------------------------------------------------------------------- |\n| One-to-one | A document can only be related to one and only one document. |\n| One-to-many | A document can be related to many other documents. |\n| Many-to-one | Many documents can be related to a single document. |\n| Many-to-many| A document can be related to many other documents. |\n\n\n# On-delete {% #on-delete %}\n\nAppwrite also allows you to define the behavior of a relationship when a document is deleted.\n\n| Type | Description |\n| ---------- | ---------------------------------------------------------------------- |\n| Restrict | If a document has at least one related document, it cannot be deleted.|\n| Cascade | If a document has related documents, when it is deleted, the related documents are also deleted.|\n| Set null | If a document has related documents, when it is deleted, the related documents are kept with their relationship attribute set to null.|\n\n# Creating relationships {% #create-relationships %}\nYou can define relationships in the Appwrite Console, or using a [Server SDK](/docs/sdks#server)\n\n{% tabs %}\n{% tabsitem #console title=\"Console\" %}\n\nYou can create relationships in the Appwrite Console by adding a relationship attribute to a collection.\n\n1. In your project, navigate to **Databases** > **Select your database** > **Select your collection** > **Attributes** > **Create attribute**.\n2. Select **Relationship** as the attribute type.\n3. In the **Relationship** modal, select the [relationship type](#types) and pick the related collection and attributes.\n4. 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.\n5. Select desired [on delete](#on-delete) behavior.\n6. Click the **Create** button to create the relationship.\n{% /tabsitem %}\n\n{% tabsitem #sdk title=\"SDK\" %}\nHere's an example that adds a relationship between the collections **movies** and **reviews**.\nA 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.\n\n{% multicode %}\n```js\nconst { Client, Databases } = require('node-appwrite');\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject(''); // Your project ID\n\nconst databases = new Databases(client);\n\ndatabases.createRelationshipAttribute(\n 'marvel', // Database ID\n 'movies', // Collection ID\n 'reviews', // Related collection ID\n 'oneToMany', // Relationship type\n true, // Is two-way\n 'reviews', // Attribute key\n 'movie', // Two-way attribute key\n 'cascade' // On delete action\n);\n```\n\n\n```php\nuse \\Appwrite\\Client;\nuse \\Appwrite\\Services\\Databases;\n\n$client = (new Client())\n ->setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n ->setProject(''); // Your project ID\n\n$databases = new Databases($client);\n\n$databases->createRelationshipAttribute(\n databaseId: 'marvel', // Database ID\n collectionId: 'movies', // Collection ID\n relatedCollectionId: 'reviews', // Related collection ID\n type: 'oneToMany', // Relationship type\n twoWay: true, // Is two-way\n key: 'reviews', // Attribute key\n twoWayKey: 'movie', // Two-way attribute key\n onDelete: 'cascade' // On delete action\n);\n```\n\n\n```python\nfrom appwrite.client import Client\nfrom appwrite.services.databases import Databases\n\nclient = (Client()\n .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('')) # Your project ID\n\ndatabases = Databases(client)\n\ndatabases.create_relationship_attribute(\n database_id='marvel', # Database ID\n collection_id='movies', # Collection ID\n related_collection_id='reviews', # Related collection ID\n type='oneToMany', # Relationship type\n two_way=True, # Is two-way\n key='reviews', # Attribute key\n two_way_key='movie', # Two-way attribute key\n on_delete='cascade' # On delete action\n)\n```\n\n\n```ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://.cloud.appwrite.io/v1')# Your API Endpoint\n .set_project('') # Your project ID\n\ndatabases = Databases.new(client)\n\ndatabases.create_relationship_attribute(\n database_id: 'marvel', # Database ID\n collection_id: 'movies', # Collection ID\n related_collection_id: 'reviews', # Related collection ID\n type: 'oneToMany', # Relationship type\n two_way: true, # Is two-way\n key: 'reviews', # Attribute key\n two_way_key: 'movie', # Two-way attribute key\n on_delete: 'cascade' # On delete action\n)\n```\n\n\n```deno\nimport { Client, Databases } from \"npm:node-appwrite\";\n\nconst client = new Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\"); // Your project ID\n\nconst databases = new Databases(client);\n\ndatabases.createRelationshipAttribute(\n \"marvel\", // Database ID\n \"movies\", // Collection ID\n \"reviews\", // Related collection ID\n \"oneToMany\", // Relationship type\n true, // Is two-way\n \"reviews\", // Attribute key\n \"movie\", // Two-way attribute key\n \"cascade\" // On delete action\n);\n```\n\n\n```dart\nimport 'package:appwrite/appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject(''); // Your project ID\n\nfinal databases = Databases(client);\n\nawait databases.createRelationshipAttribute(\n databaseId: 'marvel', // Database ID\n collectionId: 'movies', // Collection ID\n relatedCollectionId: 'reviews', // Related collection ID\n type: 'oneToMany', // Relationship type\n twoWay: true, // Is two-way\n key: 'reviews', // Attribute key\n twoWayKey: 'movie', // Two-way attribute key\n onDelete: 'cascade', // On delete action\n);\n```\n\n\n```kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Databases\n\nval client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n\nval databases = Databases(client)\n\ndatabases.createRelationshipAttribute(\n databaseId = \"marvel\", // Database ID\n collectionId = \"movies\", // Collection ID\n relatedCollectionId = \"reviews\", // Related collection ID\n type = \"oneToMany\", // Relationship type\n twoWay = true, // Is two-way\n key = \"reviews\", // Attribute key\n twoWayKey = \"movie\", // Two-way attribute key\n onDelete = \"cascade\" // On delete action\n)\n```\n\n\n```swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n\nlet databases = Databases(client)\n\ndatabases.createRelationshipAttribute(\n databaseId: \"marvel\", // Database ID\n collectionId: \"movies\", // Collection ID\n relatedCollectionId: \"reviews\", // Related collection ID\n type: \"oneToMany\", // Relationship type\n twoWay: true, // Is two-way\n key: \"reviews\", // Attribute key\n twoWayKey: \"movie\", // Two-way attribute key\n onDelete\n\n: \"cascade\" // On delete action\n)\n```\n\n\n```csharp\nusing Appwrite;\nusing Appwrite.Services;\n\nvar client = new Client()\n .SetEndpoint(\"https://.cloud.appwrite.io/v1\")\n .SetProject(\"\");\n\nvar databases = new Databases(client);\n\nawait databases.CreateRelationshipAttribute(\n databaseId: \"marvel\",\n collectionId: \"movies\",\n relatedCollectionId: \"reviews\",\n type: \"oneToMany\",\n twoWay: true,\n key: \"reviews\",\n twoWayKey: \"movie\",\n onDelete: \"cascade\");\n```\n{% /multicode %}\n{% /tabsitem %}\n{% /tabs %}\n\n# Creating documents {% #create-documents %}\nIf a collection has relationship attributes, you can create documents in two ways.\nYou create both parent and child at the same time using a **nested** syntax or link parent and child documents through **references***.\n\n{% tabs %}\n{% tabsitem #nested title=\"Nested\" %}\nYou can create both the **parent** and **child** at once in a relationship by nesting data.\n\n{% multicode %}\n```js\nconst { Client, Databases, ID } = require('node-appwrite');\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject(''); // Your project ID\n\nconst databases = new Databases(client);\n\nawait databases.createDocument(\n 'marvel',\n 'movies',\n ID.unique(),\n {\n title: 'Spiderman',\n year: 2002,\n reviews: [\n { author: 'Bob', text: 'Great movie!' },\n { author: 'Alice', text: 'Loved it!' }\n ]\n }\n)\n```\n\n```dart\nimport 'package:appwrite/appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject(''); // Your project ID\n\nfinal databases = Databases(client);\n\nawait databases.createDocument(\n databaseId: 'marvel',\n collectionId: 'movies',\n documentId: ID.unique(),\n data: {\n 'title': 'Spiderman',\n 'year': 2002,\n 'reviews': [\n { 'author': 'Bob', 'text': 'Great movie!' },\n { 'author': 'Alice', 'text': 'Loved it!' }\n ]\n },\n)\n```\n\n```swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n\nlet databases = Database(client: client)\n\ndatabases.createDocument(\n databaseId: \"marvel\",\n collectionId: \"movies\",\n documentId: ID.unique(),\n data: [\n \"title\": \"Spiderman\",\n \"year\": 2002,\n \"reviews\": [\n [ \"author\": \"Bob\", \"text\": \"Great movie!\" ],\n [ \"author\": \"Alice\", \"text\": \"Loved it!\" ]\n ]\n ]\n)\n```\n\n```kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Database\nimport io.appwrite.ID\n\nval client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n\nval databases = Database(client)\n\ndatabases.createDocument(\n databaseId = \"marvel\",\n collectionId = \"movies\",\n documentId = ID.unique(),\n data = mapOf(\n \"title\" to \"Spiderman\",\n \"year\" to 2002,\n \"reviews\" to listOf(\n mapOf(\"author\" to \"Bob\", \"text\" to \"Great movie!\"),\n mapOf(\"author\" to \"Alice\", \"text\" to \"Loved it!\")\n )\n )\n)\n```\n{% /multicode %}\n\n## Edge case behaviors {% #edge-case-behaviors %}\n- If a nested child document is included and **no child document ID** is provided, the child document will be given a unique ID.\n- If a nested child document is included and **no conflicting child document ID** exists, the child document will be **created**.\n- If a nested child document is included and the **child document ID already exists**, the child document will be **updated**.\n\n{% /tabsitem %}\n{% tabsitem #reference title=\"Reference\" %}\nIf the child documents are already present in the related collection, you can create the parent and **reference the child documents** using their IDs.\nHere's an example connecting reviews to a movie.\n{% multicode %}\n```js\nconst { Client, Databases, ID } = require('node-appwrite');\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject(''); // Your project ID\n\nconst databases = new Databases(client);\n\nawait databases.createDocument(\n 'marvel',\n 'movies',\n ID.unique(),\n {\n title: 'Spiderman',\n year: 2002,\n reviews: [\n '',\n ''\n ]\n }\n)\n```\n\n```dart\nimport 'package:appwrite/appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject(''); // Your project ID\n\nfinal databases = Databases(client);\n\nawait databases.createDocument(\n databaseId: 'marvel',\n collectionId: 'movies',\n documentId: ID.unique(),\n data: {\n 'title': 'Spiderman',\n 'year': 2002,\n 'reviews': [\n '',\n ''\n ]\n },\n)\n```\n\n```swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n\nlet databases = Database(client: client)\n\ndatabases.createDocument(\n databaseId: \"marvel\",\n collectionId: \"movies\",\n documentId: ID.unique(),\n data: [\n \"title\": \"Spiderman\",\n \"year\": 2002,\n \"reviews\": [\n \"\",\n \"\"\n ]\n ]\n)\n```\n\n```kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Database\nimport io.appwrite.ID\n\nval client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n\nval databases = Database(client)\n\ndatabases.createDocument(\n databaseId = \"marvel\",\n collectionId = \"movies\",\n documentId = ID.unique(),\n data = mapOf(\n \"title\" to \"Spiderman\",\n \"year\" to 2002,\n \"reviews\" to listOf(\n \"\",\n \"\"\n )\n )\n)\n```\n{% /multicode %}\n{% /tabsitem %}\n{% /tabs %}\n\n# Queries {% #queries %}\n\nYou 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.\n\nUse the format `relationshipKey.field` to reference fields on related documents.\n\n{% multicode %}\n```js\nconst { Client, Databases, Query } = require('node-appwrite');\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject(''); // Your project ID\n\nconst databases = new Databases(client);\n\nawait databases.listDocuments(\n 'marvel',\n 'movies',\n [\n Query.equal('reviews.author', ['Bob'])\n ]\n);\n```\n\n```dart\nimport 'package:appwrite/appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nfinal databases = Databases(client);\n\nawait databases.listDocuments(\n databaseId: 'marvel',\n collectionId: 'movies',\n queries: [\n Query.equal('reviews.author', ['Bob']),\n ],\n);\n```\n\n```swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nlet databases = Databases(client)\n\ndatabases.listDocuments(\n databaseId: \"marvel\",\n collectionId: \"movies\",\n queries: [\n Query.equal(\"reviews.author\", value: [\"Bob\"])\n ]\n)\n```\n\n```kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Databases\nimport io.appwrite.Query\n\nval client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nval databases = Databases(client)\n\ndatabases.listDocuments(\n databaseId = \"marvel\",\n collectionId = \"movies\",\n queries = listOf(\n Query.equal(\"reviews.author\", listOf(\"Bob\"))\n )\n)\n```\n{% /multicode %}\n\nAll filter queries are supported on relationship fields, including `equal`, `notEqual`, `greaterThan`, `lessThan`, `between`, `contains`, and other [comparison operators](/docs/products/databases/legacy/queries#comparison).\n\n{% arrow_link href=\"/docs/products/databases/legacy/queries#relationship-select\" %}\nLearn how to select and load relationship data\n{% /arrow_link %}\n\n# Update Relationships {% #update %}\nRelationships can be updated by updating the relationship attribute.\n\n{% multicode %}\n```js\nconst { Client, Databases } = require('node-appwrite');\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject(''); // Your project ID\n\nconst databases = new Databases(client);\n\nawait databases.updateDocument(\n 'marvel',\n 'movies',\n 'spiderman',\n {\n title: 'Spiderman',\n year: 2002,\n reviews: [\n 'review4',\n 'review5'\n ]\n }\n);\n```\n\n```dart\nimport 'package:appwrite/appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nfinal databases = Databases(client);\n\nawait databases.updateDocument(\n databaseId: 'marvel',\n collectionId: 'movies',\n documentId: 'spiderman',\n data: {\n 'title': 'Spiderman',\n 'year': 2002,\n 'reviews': [\n 'review4',\n 'review5'\n ]\n },\n);\n```\n\n```swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nlet databases = Database(client: client)\n\ndatabases.updateDocument(\n databaseId: \"marvel\",\n collectionId: \"movies\",\n documentId: \"spiderman\",\n data: [\n \"title\": \"Spiderman\",\n \"year\": 2002,\n \"reviews\": [\n \"review4\",\n \"review5\"\n ]\n ]\n)\n```\n\n```kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Database\n\nval client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nval databases = Database(client)\n\ndatabases.updateDocument(\n databaseId = \"marvel\",\n collectionId = \"movies\",\n documentId = \"spiderman\",\n data = mapOf(\n \"title\" to \"Spiderman\",\n \"year\" to 2002,\n \"reviews\" to listOf(\n \"review4\",\n \"review5\"\n )\n )\n)\n```\n\n{% /multicode %}\n\n# Delete relationships {% #delete %}\n## Unlink relationships, retain documents {% #unlink %}\n\nIf 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.\n\nIf a document can be related to **only one document**, you can delete the relationship by setting the relationship attribute to `null`.\n\nIf a document can be related to **more than one document**, you can delete the relationship by setting the relationship attribute to an empty list.\n\n## Delete relationships and documents {% #delete-both %}\n\nIf 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.\n\nIf the on-delete behavior is **restrict**, the link between the documents needs to be deleted first before the documents can be deleted **individually**.\n\nIf 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**.\n\nIf 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.\n\n{% multicode %}\n```js\nconst { Client, Databases } = require('node-appwrite');\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject(''); // Your project ID\n\nconst databases = new Databases(client);\n\nawait databases.deleteDocument(\n 'marvel',\n 'movies',\n 'spiderman'\n);\n```\n\n```dart\nimport 'package:appwrite/appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nfinal databases = Databases(client);\n\nawait databases.deleteDocument(\n databaseId: 'marvel',\n collectionId: 'movies',\n documentId: 'spiderman'\n);\n```\n\n```swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nlet databases = Database(client: client)\n\ndatabases.deleteDocument(\n databaseId: \"marvel\",\n collectionId: \"movies\",\n documentId: \"spiderman\"\n)\n```\n\n```kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Database\n\nval client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nval databases = Database(client)\n\ndatabases.deleteDocument(\n databaseId = \"marvel\",\n collectionId = \"movies\",\n documentId = \"spiderman\"\n)\n```\n\n{% /multicode %}\n\n# Permissions {% #permissions %}\n\nTo access documents in a relationship, you must have permission to access both the parent and child documents.\n\nWhen creating both the parent and child documents, the child document will **inherit permissions** from its parent.\n\nYou can also provide explicit permissions to the child document if they should be **different from their parent**.\n\n{% multicode %}\n```js\nconst { Client, Databases, ID } = require('node-appwrite');\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject(''); // Your project ID\n\nconst databases = new Databases(client);\n\nawait databases.createDocument(\n 'marvel',\n 'movies',\n ID.unique(),\n {\n title: 'Spiderman',\n year: 2002,\n reviews: [\n {\n author: 'Bob',\n text: 'Great movie!',\n $permissions: [\n Permission.read(Role.any())\n ]\n },\n ]\n }\n);\n```\n```dart\nimport 'package:appwrite/appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nfinal databases = Databases(client);\n\nawait databases.createDocument(\n databaseId: 'marvel',\n collectionId: 'movies',\n documentId: ID.unique(),\n data: {\n 'title': 'Spiderman',\n 'year': 2002,\n 'reviews': [\n {\n 'author': 'Bob',\n 'text': 'Great movie!',\n '\\$permissions': [\n Permission.read(Role.any())\n ]\n },\n ]\n },\n);\n```\n```swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nlet databases = Database(client: client)\n\ndatabases.createDocument(\n databaseId: \"marvel\",\n collectionId: \"movies\",\n documentId: ID.unique(),\n data: [\n \"title\": \"Spiderman\",\n \"year\": 2002,\n \"reviews\": [\n [\n \"author\": \"Bob\",\n \"text\": \"Great movie!\",\n \"$permissions\": [\n Permission.read(Role.any())\n ]\n ],\n ]\n ]\n);\n```\n```kotlin\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nlet databases = Database(client: client)\n\ndatabases.createDocument(\n databaseId: \"marvel\",\n collectionId: \"movies\",\n documentId: ID.unique(),\n data: [\n \"title\": \"Spiderman\",\n \"year\": 2002,\n \"reviews\": [\n [\n \"author\": \"Bob\",\n \"text\": \"Great movie!\",\n \"$permissions\": [\n Permission.read(Role.any())\n ]\n ],\n ]\n ]\n);\n```\n{% /multicode %}\n\nWhen creating, updating, or deleting in a relationship, you must have permission to access all documents referenced.\nIf the user does not have read permission to any document, an exception will be thrown.\n\n# Limitations {% #limitations %}\n\nRelationships can be nested between collections, but are restricted to a **max depth of three levels**.\nRelationship attribute key, type, and directionality can't be updated.\nOn-delete behavior is the only option that can be updated for relationship attributes."}, {"path": "docs/products/databases/legacy/type-generation", "title": "Type generation", "description": "Generate types from your Appwrite database schema. Learn how to use the Appwrite CLI to create and manage your types effectively.", "content": "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.\n\nTo generate types, the CLI reads the database schema from your project's `appwrite.json` file and generates type definitions for each collection.\n\n## Generating types\n\nFirst, 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:\n\n```bash\nappwrite pull collections\n```\n\nTo generate types, you can use the Appwrite CLI command:\n\n```bash\nappwrite types [options] \n```\n\nThe following options are currently available:\n\n| Option | Description |\n|--------|-------------|\n| `--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. |\n| `--help` or `-h` | Displays help information for the command. |\n\n## Example usage\n\nSuppose you want to generate types for a collection with data on books with the following schema from your `appwrite.json` file:\n\n```json\n{\n \"projectId\": \"682ca9a50004cf4b330f\",\n \"projectName\": \"Appwrite project\",\n \"databases\": [\n {\n \"$id\": \"684c678b00211ddac082\",\n \"name\": \"Library\",\n \"enabled\": true\n }\n ],\n \"collections\": [\n {\n \"$id\": \"684c6790002d457ee89d\",\n \"$permissions\": [],\n \"databaseId\": \"684c678b00211ddac082\",\n \"name\": \"Books\",\n \"enabled\": true,\n \"documentSecurity\": false,\n \"attributes\": [\n {\n \"key\": \"name\",\n \"type\": \"varchar\",\n \"required\": true,\n \"array\": false,\n \"size\": 255,\n \"default\": null\n },\n {\n \"key\": \"author\",\n \"type\": \"varchar\",\n \"required\": true,\n \"array\": false,\n \"size\": 255,\n \"default\": null\n },\n {\n \"key\": \"release_year\",\n \"type\": \"datetime\",\n \"required\": false,\n \"array\": false,\n \"format\": \"\",\n \"default\": null\n },\n {\n \"key\": \"category\",\n \"type\": \"varchar\",\n \"required\": false,\n \"array\": false,\n \"elements\": [\n \"fiction\",\n \"nonfiction\"\n ],\n \"format\": \"enum\",\n \"default\": null\n },\n {\n \"key\": \"genre\",\n \"type\": \"varchar\",\n \"required\": false,\n \"array\": true,\n \"size\": 100,\n \"default\": null\n },\n {\n \"key\": \"is_checked_out\",\n \"type\": \"boolean\",\n \"required\": true,\n \"array\": false,\n \"default\": null\n }\n ],\n \"indexes\": []\n }\n ]\n}\n```\n\nHere's how you can generate types for this collection across all supported languages:\n\n{% tabs %}\n{% tabsitem #ts title=\"TypeScript\" %}\nRun the following command in your terminal:\n\n```bash\nappwrite types --language ts ./types\n```\n\nThis will generate the following types in the `./types` sub-directory of your project:\n\n```ts\nimport { Models } from 'appwrite';\n\nexport enum Category {\n FICTION = \"fiction\",\n NONFICTION = \"nonfiction\",\n}\n\nexport type Books = Models.Document & {\n name: string;\n author: string;\n releaseYear: string | null;\n category: Category | null;\n genre: string[] | null;\n isCheckedOut: boolean;\n}\n```\n{% /tabsitem %}\n\n{% tabsitem #js title=\"JavaScript\" %}\nRun the following command in your terminal:\n\n```bash\nappwrite types --language js ./types\n```\n\nThis will generate the following types in the `./types` sub-directory of your project:\n\n```js\n/**\n * @typedef {import('appwrite').Models.Document} Document\n */\n\n\n/**\n * @typedef {Object} Books\n * @property {string} name\n * @property {string} author\n * @property {string|null|undefined} releaseYear\n * @property {\"fiction\"|\"nonfiction\"|null|undefined} category\n * @property {string[]|null|undefined} genre\n * @property {boolean} isCheckedOut\n */\n```\n{% /tabsitem %}\n\n{% tabsitem #java title=\"Java\" %}\nRun the following command in your terminal:\n\n```bash\nappwrite types --language java ./types\n```\n\nThis will generate the following types in the `./types` sub-directory of your project:\n\n```java\npackage io.appwrite.models;\n\nimport java.util.*;\npublic class Books {\n\n public enum Category {\n fiction,\n nonfiction;\n }\n\n private String name;\n private String author;\n private String releaseYear;\n private Category category;\n private List genre;\n private boolean isCheckedOut;\n\n public Books() {\n }\n\n public Books(\n String name,\n String author,\n String releaseYear,\n Category category,\n List genre,\n boolean isCheckedOut\n ) {\n this.name = name;\n this.author = author;\n this.releaseYear = releaseYear;\n this.category = category;\n this.genre = genre;\n this.isCheckedOut = isCheckedOut;\n }\n\n public String getName() {\n return name;\n }\n\n public void setName(String name) {\n this.name = name;\n }\n\n public String getAuthor() {\n return author;\n }\n\n public void setAuthor(String author) {\n this.author = author;\n }\n\n public String getReleaseYear() {\n return releaseYear;\n }\n\n public void setReleaseYear(String releaseYear) {\n this.releaseYear = releaseYear;\n }\n\n public Category getCategory() {\n return category;\n }\n\n public void setCategory(Category category) {\n this.category = category;\n }\n\n public List getGenre() {\n return genre;\n }\n\n public void setGenre(List genre) {\n this.genre = genre;\n }\n\n public boolean getIsCheckedOut() {\n return isCheckedOut;\n }\n\n public void setIsCheckedOut(boolean isCheckedOut) {\n this.isCheckedOut = isCheckedOut;\n }\n\n @Override\n public boolean equals(Object obj) {\n if (this == obj) return true;\n if (obj == null || getClass() != obj.getClass()) return false;\n Books that = (Books) obj;\n return Objects.equals(name, that.name) &&\n Objects.equals(author, that.author) &&\n Objects.equals(releaseYear, that.releaseYear) &&\n Objects.equals(category, that.category) &&\n Objects.equals(genre, that.genre) &&\n Objects.equals(isCheckedOut, that.isCheckedOut);\n }\n\n @Override\n public int hashCode() {\n return Objects.hash(name, author, releaseYear, category, genre, isCheckedOut);\n }\n\n @Override\n public String toString() {\n return \"Books{\" +\n \"name=\" + name +\n \"author=\" + author +\n \"releaseYear=\" + releaseYear +\n \"category=\" + category +\n \"genre=\" + genre +\n \"isCheckedOut=\" + isCheckedOut +\n '}';\n }\n}\n```\n{% /tabsitem %}\n\n{% tabsitem #php title=\"PHP\" %}\nRun the following command in your terminal:\n\n```bash\nappwrite types --language php ./types\n```\n\nThis will generate the following types in the `./types` sub-directory of your project:\n\n```php\nname = $name;\n $this->author = $author;\n $this->releaseYear = $releaseYear;\n $this->category = $category;\n $this->genre = $genre;\n $this->isCheckedOut = $isCheckedOut;\n }\n\n public function getName(): string {\n return $this->name;\n }\n\n public function setName(string $name): void {\n $this->name = $name;\n }\n public function getAuthor(): string {\n return $this->author;\n }\n\n public function setAuthor(string $author): void {\n $this->author = $author;\n }\n public function getReleaseYear(): string|null {\n return $this->releaseYear;\n }\n\n public function setReleaseYear(string|null $releaseYear): void {\n $this->releaseYear = $releaseYear;\n }\n public function getCategory(): Category|null {\n return $this->category;\n }\n\n public function setCategory(Category|null $category): void {\n $this->category = $category;\n }\n public function getGenre(): array {\n return $this->genre;\n }\n\n public function setGenre(array $genre): void {\n $this->genre = $genre;\n }\n public function getIsCheckedOut(): bool {\n return $this->isCheckedOut;\n }\n\n public function setIsCheckedOut(bool $isCheckedOut): void {\n $this->isCheckedOut = $isCheckedOut;\n }\n}\n```\n{% /tabsitem %}\n\n{% tabsitem #dart title=\"Dart\" %}\nRun the following command in your terminal:\n\n```bash\nappwrite types --language dart ./types\n```\n\nThis will generate the following types in the `./types` sub-directory of your project:\n\n```dart\nenum Category {\n fiction,\n nonfiction,\n}\n\nclass Books {\n String name;\n String author;\n String? releaseYear;\n Category? category;\n List? genre;\n bool isCheckedOut;\n\n Books({\n required this.name,\n required this.author,\n this.releaseYear,\n this.category,\n this.genre,\n required this.isCheckedOut,\n });\n\n factory Books.fromMap(Map map) {\n return Books(\n name: map['name'].toString(),\n author: map['author'].toString(),\n releaseYear: map['release_year']?.toString() ?? null,\n category: map['category'] != null ? Category.values.where((e) => e.name == map['category']).firstOrNull : null,\n genre: List.from(map['genre'] ?? []) ?? [],\n isCheckedOut: map['is_checked_out'],\n );\n }\n\n Map toMap() {\n return {\n \"name\": name,\n \"author\": author,\n \"release_year\": releaseYear,\n \"category\": category?.name ?? null,\n \"genre\": genre,\n \"is_checked_out\": isCheckedOut,\n };\n }\n}\n```\n{% /tabsitem %}\n\n{% tabsitem #kotlin title=\"Kotlin\" %}\nRun the following command in your terminal:\n\n```bash\nappwrite types --language kotlin ./types\n```\n\nThis will generate the following types in the `./types` sub-directory of your project:\n\n```kotlin\npackage io.appwrite.models\n\nenum class Category {\n fiction,\n nonfiction\n}\n\ndata class Books(\n val name: String,\n val author: String,\n val releaseYear: String?,\n val category: Category?,\n val genre: List?,\n val isCheckedOut: Boolean,\n)\n```\n{% /tabsitem %}\n\n{% tabsitem #swift title=\"Swift\" %}\nRun the following command in your terminal:\n\n```bash\nappwrite types --language swift ./types\n```\n\nThis will generate the following types in the `./types` sub-directory of your project:\n\n```swift\nimport Foundation\n\npublic enum Category: String, Codable, CaseIterable {\n case fiction = \"fiction\"\n case nonfiction = \"nonfiction\"\n}\n\npublic class Books: Codable {\n public let name: String\n public let author: String\n public let releaseYear: String?\n public let category: Category?\n public let genre: [String]?\n public let isCheckedOut: Bool\n\n enum CodingKeys: String, CodingKey {\n case name = \"name\"\n case author = \"author\"\n case releaseYear = \"release_year\"\n case category = \"category\"\n case genre = \"genre\"\n case isCheckedOut = \"is_checked_out\"\n }\n\n init(\n name: String,\n author: String,\n releaseYear: String?,\n category: Category?,\n genre: [String]?,\n isCheckedOut: Bool\n ) {\n self.name = name\n self.author = author\n self.releaseYear = releaseYear\n self.category = category\n self.genre = genre\n self.isCheckedOut = isCheckedOut\n }\n\n public required init(from decoder: Decoder) throws {\n let container = try decoder.container(keyedBy: CodingKeys.self)\n\n self.name = try container.decode(String.self, forKey: .name)\n self.author = try container.decode(String.self, forKey: .author)\n self.releaseYear = try container.decodeIfPresent(String.self, forKey: .releaseYear)\n self.category = try container.decodeIfPresent(Category.self, forKey: .category)\n self.genre = try container.decodeIfPresent([String].self, forKey: .genre)\n self.isCheckedOut = try container.decode(Bool.self, forKey: .isCheckedOut)\n }\n\n public func encode(to encoder: Encoder) throws {\n var container = encoder.container(keyedBy: CodingKeys.self)\n\n try container.encode(name, forKey: .name)\n try container.encode(author, forKey: .author)\n try container.encodeIfPresent(releaseYear, forKey: .releaseYear)\n try container.encodeIfPresent(category, forKey: .category)\n try container.encodeIfPresent(genre, forKey: .genre)\n try container.encode(isCheckedOut, forKey: .isCheckedOut)\n }\n\n public func toMap() -> [String: Any] {\n return [\n \"name\": name as Any,\n \"author\": author as Any,\n \"release_year\": releaseYear as Any,\n \"category\": category as Any,\n \"genre\": genre as Any,\n \"is_checked_out\": isCheckedOut as Any\n ]\n }\n\n public static func from(map: [String: Any]) -> Books {\n return Books(\n name: map[\"name\"] as! String,\n author: map[\"author\"] as! String,\n releaseYear: map[\"release_year\"] as? String,\n category: map[\"category\"] as? String,\n genre: map[\"genre\"] as? [String],\n isCheckedOut: map[\"is_checked_out\"] as! Bool\n )\n }\n}\n```\n{% /tabsitem %}\n{% /tabs %}"}, {"path": "docs/products/databases/offline", "title": "Offline sync", "description": "Enable offline synchronization of data between your apps and Appwrite Databases.", "content": "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.\n\nThis 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.\n\nSome real-world scenarios where offline sync is useful are:\n\n- Journaling and note-taking apps\n- Warehouse inventory management systems\n- Medical data entry tools\n- Airline check-in management apps\n- GPS navigation software\n\n# Integrate offline sync in your apps\n\n{% only_light %}\n{% cards %}\n\n{% cards_item href=\"/integrations/replication-rxdb\" title=\"RxDB\" image=\"/images/docs/databases/offline/logos/rxdb.svg\" %}\n{% /cards_item %}\n\n{% /cards %}\n{% /only_light %}\n\n{% only_dark %}\n{% cards %}\n\n{% cards_item href=\"/integrations/replication-rxdb\" title=\"RxDB\" image=\"/images/docs/databases/offline/logos/dark/rxdb.svg\" %}\n{% /cards_item %}\n\n{% /cards %}\n{% /only_dark %}\n\n# How does offline sync work?\n\nThe process of implementing offline sync in Appwrite-powered apps (and in general) is as follows:\n\n1. **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.\n\n2. **Working offline**: While offline, users can either read previously synced data or make changes (create, update, or delete data) in the local data store.\n\n3. **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.\n\n5. **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**.\n\n6. **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*."}, {"path": "docs/products/databases/operators", "title": "Operators", "description": "Update multiple fields atomically without fetching the full row. Perform numeric, array, string, and date updates in a single, consistent workflow.", "content": "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.\n\n- Atomic by field: Each operation is applied safely at the storage layer to prevent lost updates under concurrency.\n- Multi-field updates: Apply multiple operations across different fields in a single request or transaction.\n- Type-safe: Operators are exposed through typed SDK methods for clarity and safety.\n- Transaction-ready: Operators can be staged and committed alongside other database actions for consistent writes.\n\n# How operators work {% #how-database-operators-work %}\n\nInstead 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.\n\nLet's take an example of appending a value to an array field.\n\n**Traditional approach:**\n1. Fetch row → `{ letters: ['a', 'b' ] }`\n2. Update client-side → `letters: ['a', 'b', 'c']`\n3. Write back → `{ letters: ['a', 'b', 'c'] }`\n\n**Operator approach:**\n1. Update/upsert the row with the appropriate value to append\n2. Server applies atomically → `letters: ['a', 'b', 'c']`\n\nHere's how you can do so programmatically:\n\n{% multicode %}\n```client-web\nimport { Client, TablesDB, Operator } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nconst tablesDB = new TablesDB(client);\n\nawait tablesDB.updateRow({\n databaseId: \"\",\n tableId: \"\",\n rowId: \"\",\n data: {\n letters: Operator.arrayAppend(['c'])\n }\n});\n```\n```server-nodejs\nconst sdk = require('node-appwrite');\n\nconst client = new sdk.Client();\nclient\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nconst tablesDB = new sdk.TablesDB(client);\n\nconst result = await tablesDB.updateRow({\n databaseId: '',\n tableId: '',\n rowId: '',\n data: {\n letters: sdk.Operator.arrayAppend(['c'])\n }\n});\n```\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nvoid main() async {\n final client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\n final tablesDB = TablesDB(client);\n\n try {\n await tablesDB.updateRow(\n '',\n '',\n '',\n {\n 'letters': Operator.arrayAppend(['c'])\n },\n );\n } on AppwriteException catch (e) {\n print(e);\n }\n}\n```\n```client-apple\nimport Appwrite\nimport AppwriteModels\n\nfunc main() async throws {\n let client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\n let tablesDB = TablesDB(client)\n\n do {\n _ = try await tablesDB.updateRow(\n databaseId: \"\",\n tableId: \"\",\n rowId: \"\",\n data: [\n \"letters\": Operator.arrayAppend([\"c\"]) \n ]\n )\n } catch {\n print(error.localizedDescription)\n }\n}\n```\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.TablesDB\nimport io.appwrite.Operator\n\nsuspend fun main() {\n val client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\n val tablesDB = TablesDB(client)\n\n tablesDB.updateRow(\n databaseId = \"\",\n tableId = \"\",\n rowId = \"\",\n data = mapOf(\n \"letters\" to Operator.arrayAppend(listOf(\"c\"))\n )\n )\n}\n```\n```server-go\npackage main\n\nimport (\n \"log\"\n \"github.com/appwrite/sdk-for-go/appwrite\"\n operator \"github.com/appwrite/sdk-for-go/operator\"\n)\n\nfunc main() {\n client := appwrite.NewClient(\n appwrite.WithEndpoint(\"https://.cloud.appwrite.io/v1\"),\n appwrite.WithProject(\"\"),\n appwrite.WithKey(\"\"),\n )\n\n tablesDB := appwrite.NewTablesDB(client)\n\n _, err := tablesDB.UpdateRow(\n \"\",\n \"\",\n \"\",\n tablesDB.WithUpdateRowData(map[string]any{\n \"letters\": operator.ArrayAppend([]string{\"c\"}),\n }),\n )\n if err != nil {\n log.Fatal(err)\n }\n}\n```\n```server-php\nsetEndpoint('https://.cloud.appwrite.io/v1')\n ->setProject('')\n ->setKey('');\n\n$tablesDB = new TablesDB($client);\n\n$result = $tablesDB->updateRow(\n '',\n '',\n '',\n [ 'letters' => Operator::arrayAppend(['c']) ]\n);\n```\n```server-python\nfrom appwrite.client import Client\nfrom appwrite.services.tables_db import TablesDB\nfrom appwrite.operator import Operator\n\nclient = Client()\n(client\n .set_endpoint('https://.cloud.appwrite.io/v1')\n .set_project('')\n .set_key(''))\n\ntablesDB = TablesDB(client)\n\nresult = tablesDB.update_row(\n '',\n '',\n '',\n { 'letters': Operator.arrayAppend(['c']) }\n)\n```\n```server-dotnet\nusing Appwrite;\nusing Appwrite.Services;\n\nvar client = new Client()\n .SetEndpoint(\"https://.cloud.appwrite.io/v1\")\n .SetProject(\"\")\n .SetKey(\"\");\n\nvar tablesDB = new TablesDB(client);\n\nawait tablesDB.UpdateRow(\n databaseId: \"\",\n tableId: \"\",\n rowId: \"\",\n data: new Dictionary\n {\n { \"letters\", Operator.ArrayAppend(new[] { \"c\" }) }\n }\n);\n```\n```server-ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new()\n\nclient\n .set_endpoint('https://.cloud.appwrite.io/v1')\n .set_project('')\n .set_key('')\n\ntablesDB = TablesDB.new(client)\n\nresult = tablesDB.update_row(\n '',\n '',\n '',\n { 'letters' => Operator.arrayAppend(['c']) }\n)\n```\n```server-java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.TablesDB;\nimport io.appwrite.Operator;\nimport java.util.*;\n\nClient client = new Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\");\n\nTablesDB tablesDB = new TablesDB(client);\n\ntablesDB.updateRow(\n \"\",\n \"\",\n \"\",\n Map.of(\"letters\", Operator.arrayAppend(List.of(\"c\"))),\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n System.out.println(result);\n })\n);\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::tables_db::TablesDB;\nuse appwrite::operator;\nuse serde_json::json;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box> {\n let client = Client::new()\n .set_endpoint(\"https://.cloud.appwrite.io/v1\")\n .set_project(\"\")\n .set_key(\"\");\n\n let tables_db = TablesDB::new(&client);\n\n let result = tables_db.update_row(\n \"\",\n \"\",\n \"\",\n Some(json!({\n \"letters\": operator::array_append(&[\"c\"])\n })),\n None,\n None,\n ).await?;\n\n Ok(())\n}\n```\n{% /multicode %}\n\n\n# When to use operators {% #when-to-use %}\n\nUse operators when you need to:\n\n- Update fields frequently under concurrency (likes, scores, credits, inventory)\n- Edit lists/tags without rewriting whole arrays\n- Make small text changes in-place\n- Adjust dates for lifecycle events or scheduling\n\nThis keeps payloads small, avoids race conditions, and reduces round-trips.\n\n# Available operators {% #operators %}\n\nThe following operators are available, grouped by field type. Each operator updates the given column atomically on the server.\n\n## Numeric {% #numeric %}\n\nPerform arithmetic on numeric fields without reading the row first.\n\n### increment {% #increment %}\n\nIncrease a numeric field by a specified value. Optionally cap the result at a maximum value.\n\n{% multicode %}\n```client-web\nOperator.increment(1)\n```\n```server-python\nOperator.increment(1)\n```\n```server-php\nOperator::increment(1)\n```\n```client-apple\nOperator.increment(1)\n```\n```client-android-kotlin\nOperator.increment(1)\n```\n```server-go\noperator.Increment(1)\n```\n```client-flutter\nOperator.increment(1)\n```\n```server-dotnet\nOperator.Increment(1)\n```\n```server-ruby\nOperator.increment(1)\n```\n```server-java\nOperator.increment(1)\n```\n```server-rust\noperator::increment_by(1)\n```\n{% /multicode %}\n\n### decrement {% #decrement %}\n\nDecrease a numeric field by a specified value. Optionally cap the result at a minimum value.\n\n{% multicode %}\n```client-web\nOperator.decrement(1)\n```\n```server-python\nOperator.decrement(1)\n```\n```server-php\nOperator::decrement(1)\n```\n```client-apple\nOperator.decrement(1)\n```\n```client-android-kotlin\nOperator.decrement(1)\n```\n```server-go\noperator.Decrement(1)\n```\n```client-flutter\nOperator.decrement(1)\n```\n```server-dotnet\nOperator.Decrement(1)\n```\n```server-ruby\nOperator.decrement(1)\n```\n```server-java\nOperator.decrement(1)\n```\n```server-rust\noperator::decrement_by(1)\n```\n{% /multicode %}\n\n### multiply {% #multiply %}\n\nMultiply a numeric field by a specified factor. Optionally cap the result at a maximum value.\n\n{% multicode %}\n```client-web\nOperator.multiply(2)\n```\n```server-python\nOperator.multiply(2)\n```\n```server-php\nOperator::multiply(2)\n```\n```client-apple\nOperator.multiply(2)\n```\n```client-android-kotlin\nOperator.multiply(2)\n```\n```server-go\noperator.Multiply(2)\n```\n```client-flutter\nOperator.multiply(2)\n```\n```server-dotnet\nOperator.Multiply(2)\n```\n```server-ruby\nOperator.multiply(2)\n```\n```server-java\nOperator.multiply(2)\n```\n```server-rust\noperator::multiply(2)\n```\n{% /multicode %}\n\n### divide {% #divide %}\n\nDivide a numeric field by a specified divisor. Optionally cap the result at a minimum value. Divisor cannot be zero.\n\n{% multicode %}\n```client-web\nOperator.divide(5)\n```\n```server-python\nOperator.divide(5)\n```\n```server-php\nOperator::divide(5)\n```\n```client-apple\nOperator.divide(5)\n```\n```client-android-kotlin\nOperator.divide(5)\n```\n```server-go\noperator.Divide(5)\n```\n```client-flutter\nOperator.divide(5)\n```\n```server-dotnet\nOperator.Divide(5)\n```\n```server-ruby\nOperator.divide(5)\n```\n```server-java\nOperator.divide(5)\n```\n```server-rust\noperator::divide(5)\n```\n{% /multicode %}\n\n### modulo {% #modulo %}\n\nSet a numeric field to the remainder of itself divided by a specified value.\n\n{% multicode %}\n```client-web\nOperator.modulo(3)\n```\n```server-python\nOperator.modulo(3)\n```\n```server-php\nOperator::modulo(3)\n```\n```client-apple\nOperator.modulo(3)\n```\n```client-android-kotlin\nOperator.modulo(3)\n```\n```server-go\noperator.Modulo(3)\n```\n```client-flutter\nOperator.modulo(3)\n```\n```server-dotnet\nOperator.Modulo(3)\n```\n```server-ruby\nOperator.modulo(3)\n```\n```server-java\nOperator.modulo(3)\n```\n```server-rust\noperator::modulo(3)\n```\n{% /multicode %}\n\n### power {% #power %}\n\nRaise a numeric field to a specified exponent. Optionally cap the result at a maximum value.\n\n{% multicode %}\n```client-web\nOperator.power(2)\n```\n```server-python\nOperator.power(2)\n```\n```server-php\nOperator::power(2)\n```\n```client-apple\nOperator.power(2)\n```\n```client-android-kotlin\nOperator.power(2)\n```\n```server-go\noperator.Power(2)\n```\n```client-flutter\nOperator.power(2)\n```\n```server-dotnet\nOperator.Power(2)\n```\n```server-ruby\nOperator.power(2)\n```\n```server-java\nOperator.power(2)\n```\n```server-rust\noperator::power(2)\n```\n{% /multicode %}\n\n## Array {% #array %}\n\nEdit lists in place: append, remove, or modify array items atomically.\n\n### arrayAppend {% #array-append %}\n\nAdd one or more elements to the end of an array.\n\n{% multicode %}\n```client-web\nOperator.arrayAppend(['c'])\n```\n```server-python\nOperator.arrayAppend(['c'])\n```\n```server-php\nOperator::arrayAppend(['c'])\n```\n```client-apple\nOperator.arrayAppend([\"c\"])\n```\n```client-android-kotlin\nOperator.arrayAppend(listOf(\"c\"))\n```\n```server-go\noperator.ArrayAppend([]string{\"c\"})\n```\n```client-flutter\nOperator.arrayAppend(['c'])\n```\n```server-dotnet\nOperator.ArrayAppend(new[] { \"c\" })\n```\n```server-ruby\nOperator.arrayAppend(['c'])\n```\n```server-java\nOperator.arrayAppend(List.of(\"c\"))\n```\n```server-rust\noperator::array_append(&[\"c\"])\n```\n{% /multicode %}\n\n### arrayPrepend {% #array-prepend %}\n\nAdd one or more elements to the beginning of an array.\n\n{% multicode %}\n```client-web\nOperator.arrayPrepend(['z'])\n```\n```server-python\nOperator.arrayPrepend(['z'])\n```\n```server-php\nOperator::arrayPrepend(['z'])\n```\n```client-apple\nOperator.arrayPrepend([\"z\"])\n```\n```client-android-kotlin\nOperator.arrayPrepend(listOf(\"z\"))\n```\n```server-go\noperator.ArrayPrepend([]string{\"z\"})\n```\n```client-flutter\nOperator.arrayPrepend(['z'])\n```\n```server-dotnet\nOperator.ArrayPrepend(new[] { \"z\" })\n```\n```server-ruby\nOperator.arrayPrepend(['z'])\n```\n```server-java\nOperator.arrayPrepend(List.of(\"z\"))\n```\n```server-rust\noperator::array_prepend(&[\"z\"])\n```\n{% /multicode %}\n\n### arrayInsert {% #array-insert %}\n\nInsert an element at a specific index in an array.\n\n{% multicode %}\n```client-web\nOperator.arrayInsert(1, 'x')\n```\n```server-python\nOperator.arrayInsert(1, 'x')\n```\n```server-php\nOperator::arrayInsert(1, 'x')\n```\n```client-apple\nOperator.arrayInsert(1, \"x\")\n```\n```client-android-kotlin\nOperator.arrayInsert(1, \"x\")\n```\n```server-go\noperator.ArrayInsert(1, \"x\")\n```\n```client-flutter\nOperator.arrayInsert(1, 'x')\n```\n```server-dotnet\nOperator.ArrayInsert(1, \"x\")\n```\n```server-ruby\nOperator.arrayInsert(1, 'x')\n```\n```server-java\nOperator.arrayInsert(1, \"x\")\n```\n```server-rust\noperator::array_insert(1, \"x\")\n```\n{% /multicode %}\n\n### arrayRemove {% #array-remove %}\n\nRemove a specified element from an array.\n\n{% multicode %}\n```client-web\nOperator.arrayRemove('b')\n```\n```server-python\nOperator.arrayRemove('b')\n```\n```server-php\nOperator::arrayRemove('b')\n```\n```client-apple\nOperator.arrayRemove(\"b\")\n```\n```client-android-kotlin\nOperator.arrayRemove(\"b\")\n```\n```server-go\noperator.ArrayRemove(\"b\")\n```\n```client-flutter\nOperator.arrayRemove('b')\n```\n```server-dotnet\nOperator.ArrayRemove(\"b\")\n```\n```server-ruby\nOperator.arrayRemove('b')\n```\n```server-java\nOperator.arrayRemove(\"b\")\n```\n```server-rust\noperator::array_remove(\"b\")\n```\n{% /multicode %}\n\n### arrayUnique {% #array-unique %}\n\nRemove duplicate elements from an array.\n\n{% multicode %}\n```client-web\nOperator.arrayUnique()\n```\n```server-python\nOperator.arrayUnique()\n```\n```server-php\nOperator::arrayUnique()\n```\n```client-apple\nOperator.arrayUnique()\n```\n```client-android-kotlin\nOperator.arrayUnique()\n```\n```server-go\noperator.ArrayUnique()\n```\n```client-flutter\nOperator.arrayUnique()\n```\n```server-dotnet\nOperator.ArrayUnique()\n```\n```server-ruby\nOperator.arrayUnique()\n```\n```server-java\nOperator.arrayUnique()\n```\n```server-rust\noperator::array_unique()\n```\n{% /multicode %}\n\n### arrayIntersect {% #array-intersect %}\n\nKeep only elements that exist in both arrays.\n\n{% multicode %}\n```client-web\nOperator.arrayIntersect(['news', 'tech'])\n```\n```server-python\nOperator.arrayIntersect(['news', 'tech'])\n```\n```server-php\nOperator::arrayIntersect(['news', 'tech'])\n```\n```client-apple\nOperator.arrayIntersect([\"news\", \"tech\"])\n```\n```client-android-kotlin\nOperator.arrayIntersect(listOf(\"news\", \"tech\"))\n```\n```server-go\noperator.ArrayIntersect([]string{\"news\", \"tech\"})\n```\n```client-flutter\nOperator.arrayIntersect(['news', 'tech'])\n```\n```server-dotnet\nOperator.ArrayIntersect(new[] { \"news\", \"tech\" })\n```\n```server-ruby\nOperator.arrayIntersect(['news', 'tech'])\n```\n```server-java\nOperator.arrayIntersect(new String[] {\"news\", \"tech\"})\n```\n```server-rust\noperator::array_intersect(&[\"news\", \"tech\"])\n```\n{% /multicode %}\n\n### arrayDiff {% #array-diff %}\n\nReturn elements that exist in the current array but not in the provided array.\n\n{% multicode %}\n```client-web\nOperator.arrayDiff(['old'])\n```\n```server-python\nOperator.arrayDiff(['old'])\n```\n```server-php\nOperator::arrayDiff(['old'])\n```\n```client-apple\nOperator.arrayDiff([\"old\"])\n```\n```client-android-kotlin\nOperator.arrayDiff(listOf(\"old\"))\n```\n```server-go\noperator.ArrayDiff([]string{\"old\"})\n```\n```client-flutter\nOperator.arrayDiff(['old'])\n```\n```server-dotnet\nOperator.ArrayDiff(new[] { \"old\" })\n```\n```server-ruby\nOperator.arrayDiff(['old'])\n```\n```server-java\nOperator.arrayDiff(new String[] {\"old\"})\n```\n```server-rust\noperator::array_diff(&[\"old\"])\n```\n{% /multicode %}\n\n### arrayFilter {% #array-filter %}\n\nFilter array elements based on a condition.\n\n{% multicode %}\n```client-web\nOperator.arrayFilter(Condition.GreaterThan, 10)\n```\n```server-python\nOperator.arrayFilter(Condition.GreaterThan, 10)\n```\n```server-php\nOperator::arrayFilter(Condition::GreaterThan, 10)\n```\n```client-apple\nOperator.arrayFilter(Condition.GreaterThan, 10)\n```\n```client-android-kotlin\nOperator.arrayFilter(Condition.GreaterThan, 10)\n```\n```server-go\noperator.ArrayFilter(ConditionGreaterThan, 10)\n```\n```client-flutter\nOperator.arrayFilter(Condition.GreaterThan, 10)\n```\n```server-dotnet\nOperator.ArrayFilter(Condition.GreaterThan, 10)\n```\n```server-ruby\nOperator.arrayFilter(Condition.GreaterThan, 10)\n```\n```server-java\nOperator.arrayFilter(Condition.GreaterThan, 10)\n```\n```server-rust\noperator::array_filter_with_value(operator::Condition::GreaterThan, 10)\n```\n{% /multicode %}\n\n## String {% #string %}\n\nMake lightweight text changes without rewriting the whole row.\n\n### stringConcat {% #string-concat %}\n\nConcatenate a value to a string or array field.\n\n{% multicode %}\n```client-web\nOperator.stringConcat('!')\n```\n```server-python\nOperator.stringConcat('!')\n```\n```server-php\nOperator::stringConcat('!')\n```\n```client-apple\nOperator.stringConcat(\"!\")\n```\n```client-android-kotlin\nOperator.stringConcat(\"!\")\n```\n```server-go\noperator.StringConcat(\"!\")\n```\n```client-flutter\nOperator.stringConcat('!')\n```\n```server-dotnet\nOperator.StringConcat(\"!\")\n```\n```server-ruby\nOperator.stringConcat('!')\n```\n```server-java\nOperator.stringConcat(\"!\")\n```\n```server-rust\noperator::string_concat(\"!\")\n```\n{% /multicode %}\n\n### stringReplace {% #string-replace %}\n\nReplace occurrences of a substring with a new string.\n\n{% multicode %}\n```client-web\nOperator.stringReplace('old', 'new')\n```\n```server-python\nOperator.stringReplace('old', 'new')\n```\n```server-php\nOperator::stringReplace('old', 'new')\n```\n```client-apple\nOperator.stringReplace(\"old\", \"new\")\n```\n```client-android-kotlin\nOperator.stringReplace(\"old\", \"new\")\n```\n```server-go\noperator.StringReplace(\"old\", \"new\")\n```\n```client-flutter\nOperator.stringReplace('old', 'new')\n```\n```server-dotnet\nOperator.StringReplace(\"old\", \"new\")\n```\n```server-ruby\nOperator.stringReplace('old', 'new')\n```\n```server-java\nOperator.stringReplace(\"old\", \"new\")\n```\n```server-rust\noperator::string_replace(\"old\", \"new\")\n```\n{% /multicode %}\n\n## Date {% #date %}\n\nAdjust time-based fields for lifecycle and scheduling logic.\n\n### dateAddDays {% #date-add-days %}\n\nAdd a specified number of days to a date field.\n\n{% multicode %}\n```client-web\nOperator.dateAddDays(7)\n```\n```server-python\nOperator.dateAddDays(7)\n```\n```server-php\nOperator::dateAddDays(7)\n```\n```client-apple\nOperator.dateAddDays(7)\n```\n```client-android-kotlin\nOperator.dateAddDays(7)\n```\n```server-go\noperator.DateAddDays(7)\n```\n```client-flutter\nOperator.dateAddDays(7)\n```\n```server-dotnet\nOperator.DateAddDays(7)\n```\n```server-ruby\nOperator.dateAddDays(7)\n```\n```server-java\nOperator.dateAddDays(7)\n```\n```server-rust\noperator::date_add_days(7)\n```\n{% /multicode %}\n\n### dateSubDays {% #date-sub-days %}\n\nSubtract a specified number of days from a date field.\n\n{% multicode %}\n```client-web\nOperator.dateSubDays(3)\n```\n```server-python\nOperator.dateSubDays(3)\n```\n```server-php\nOperator::dateSubDays(3)\n```\n```client-apple\nOperator.dateSubDays(3)\n```\n```client-android-kotlin\nOperator.dateSubDays(3)\n```\n```server-go\noperator.DateSubDays(3)\n```\n```client-flutter\nOperator.dateSubDays(3)\n```\n```server-dotnet\nOperator.DateSubDays(3)\n```\n```server-ruby\nOperator.dateSubDays(3)\n```\n```server-java\nOperator.dateSubDays(3)\n```\n```server-rust\noperator::date_sub_days(3)\n```\n{% /multicode %}\n\n### dateSetNow {% #date-set-now %}\n\nSet a date field to the current time on the server.\n\n{% multicode %}\n```client-web\nOperator.dateSetNow()\n```\n```server-python\nOperator.dateSetNow()\n```\n```server-php\nOperator::dateSetNow()\n```\n```client-apple\nOperator.dateSetNow()\n```\n```client-android-kotlin\nOperator.dateSetNow()\n```\n```server-go\noperator.DateSetNow()\n```\n```client-flutter\nOperator.dateSetNow()\n```\n```server-dotnet\nOperator.DateSetNow()\n```\n```server-ruby\nOperator.dateSetNow()\n```\n```server-java\nOperator.dateSetNow()\n```\n```server-rust\noperator::date_set_now()\n```\n{% /multicode %}\n\n## Boolean {% #boolean %}\n\nToggle boolean values in place.\n\n### toggle {% #toggle %}\n\nToggle a boolean field between true and false.\n\n{% multicode %}\n```client-web\nOperator.toggle()\n```\n```server-python\nOperator.toggle()\n```\n```server-php\nOperator::toggle()\n```\n```client-apple\nOperator.toggle()\n```\n```client-android-kotlin\nOperator.toggle()\n```\n```server-go\noperator.Toggle()\n```\n```client-flutter\nOperator.toggle()\n```\n```server-dotnet\nOperator.Toggle()\n```\n```server-ruby\nOperator.toggle()\n```\n```server-java\nOperator.toggle()\n```\n```server-rust\noperator::toggle()\n```\n{% /multicode %}\n\n# Examples {% #examples %}\n\nThe following examples will demonstrate how you can use operators in different situations\n\n## Update the count of upvotes on a post\n\nThis example demonstrates using the `increment` operator to atomically increase the upvote count on a post.\n\n{% multicode %}\n```client-web\nimport { Client, TablesDB, Operator } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nconst tablesDB = new TablesDB(client);\n\nawait tablesDB.updateRow({\n databaseId: \"\",\n tableId: \"\",\n rowId: \"\",\n data: {\n upvotes: Operator.increment(1)\n }\n});\n```\n```server-nodejs\nconst sdk = require('node-appwrite');\n\nconst client = new sdk.Client();\nclient\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nconst tablesDB = new sdk.TablesDB(client);\n\nconst result = await tablesDB.updateRow({\n databaseId: '',\n tableId: '',\n rowId: '',\n data: {\n upvotes: sdk.Operator.increment(1)\n }\n});\n```\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nvoid main() async {\n final client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\n final tablesDB = TablesDB(client);\n\n try {\n await tablesDB.updateRow(\n '',\n '',\n '',\n {\n 'upvotes': Operator.increment(1)\n },\n );\n } on AppwriteException catch (e) {\n print(e);\n }\n}\n```\n```client-apple\nimport Appwrite\nimport AppwriteModels\n\nfunc main() async throws {\n let client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\n let tablesDB = TablesDB(client)\n\n do {\n _ = try await tablesDB.updateRow(\n databaseId: \"\",\n tableId: \"\",\n rowId: \"\",\n data: [\n \"upvotes\": Operator.increment(1) \n ]\n )\n } catch {\n print(error.localizedDescription)\n }\n}\n```\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.TablesDB\nimport io.appwrite.Operator\n\nsuspend fun main() {\n val client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\n val tablesDB = TablesDB(client)\n\n tablesDB.updateRow(\n databaseId = \"\",\n tableId = \"\",\n rowId = \"\",\n data = mapOf(\n \"upvotes\" to Operator.increment(1)\n )\n )\n}\n```\n```server-go\npackage main\n\nimport (\n \"log\"\n \"github.com/appwrite/sdk-for-go/appwrite\"\n operator \"github.com/appwrite/sdk-for-go/operator\"\n)\n\nfunc main() {\n client := appwrite.NewClient(\n appwrite.WithEndpoint(\"https://.cloud.appwrite.io/v1\"),\n appwrite.WithProject(\"\"),\n appwrite.WithKey(\"\"),\n )\n\n tablesDB := appwrite.NewTablesDB(client)\n\n _, err := tablesDB.UpdateRow(\n \"\",\n \"\",\n \"\",\n tablesDB.WithUpdateRowData(map[string]any{\n \"upvotes\": operator.Increment(1),\n }),\n )\n if err != nil {\n log.Fatal(err)\n }\n}\n```\n```server-php\nsetEndpoint('https://.cloud.appwrite.io/v1')\n ->setProject('')\n ->setKey('');\n\n$tablesDB = new TablesDB($client);\n\n$result = $tablesDB->updateRow(\n '',\n '',\n '',\n [ 'upvotes' => Operator::increment(1) ]\n);\n```\n```server-python\nfrom appwrite.client import Client\nfrom appwrite.services.tables_db import TablesDB\nfrom appwrite.operator import Operator\n\nclient = Client()\n(client\n .set_endpoint('https://.cloud.appwrite.io/v1')\n .set_project('')\n .set_key(''))\n\ntablesDB = TablesDB(client)\n\nresult = tablesDB.update_row(\n '',\n '',\n '',\n { 'upvotes': Operator.increment(1) }\n)\n```\n```server-dotnet\nusing Appwrite;\nusing Appwrite.Services;\n\nvar client = new Client()\n .SetEndpoint(\"https://.cloud.appwrite.io/v1\")\n .SetProject(\"\")\n .SetKey(\"\");\n\nvar tablesDB = new TablesDB(client);\n\nawait tablesDB.UpdateRow(\n databaseId: \"\",\n tableId: \"\",\n rowId: \"\",\n data: new Dictionary\n {\n { \"upvotes\", Operator.Increment(1) }\n }\n);\n```\n```server-ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new()\n\nclient\n .set_endpoint('https://.cloud.appwrite.io/v1')\n .set_project('')\n .set_key('')\n\ntablesDB = TablesDB.new(client)\n\nresult = tablesDB.update_row(\n '',\n '',\n '',\n { 'upvotes' => Operator.increment(1) }\n)\n```\n```server-java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.TablesDB;\nimport io.appwrite.Operator;\nimport java.util.*;\n\nClient client = new Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\");\n\nTablesDB tablesDB = new TablesDB(client);\n\ntablesDB.updateRow(\n \"\",\n \"\",\n \"\",\n Map.of(\"upvotes\", Operator.increment(1)),\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n System.out.println(result);\n })\n);\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::tables_db::TablesDB;\nuse appwrite::operator;\nuse serde_json::json;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box> {\n let client = Client::new()\n .set_endpoint(\"https://.cloud.appwrite.io/v1\")\n .set_project(\"\")\n .set_key(\"\");\n\n let tables_db = TablesDB::new(&client);\n\n let result = tables_db.update_row(\n \"\",\n \"\",\n \"\",\n Some(json!({\n \"upvotes\": operator::increment_by(1)\n })),\n None,\n None,\n ).await?;\n\n Ok(())\n}\n```\n{% /multicode %}\n\n## Add a book to a list\n\nThis example demonstrates using the `arrayAppend` operator to add a new book to an existing array of books.\n\n{% multicode %}\n```client-web\nimport { Client, TablesDB, Operator } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nconst tablesDB = new TablesDB(client);\n\nawait tablesDB.updateRow({\n databaseId: \"\",\n tableId: \"\",\n rowId: \"\",\n data: {\n books: Operator.arrayAppend(['The Great Gatsby'])\n }\n});\n```\n```server-nodejs\nconst sdk = require('node-appwrite');\n\nconst client = new sdk.Client();\nclient\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nconst tablesDB = new sdk.TablesDB(client);\n\nconst result = await tablesDB.updateRow({\n databaseId: '',\n tableId: '',\n rowId: '',\n data: {\n books: sdk.Operator.arrayAppend(['The Great Gatsby'])\n }\n});\n```\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nvoid main() async {\n final client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\n final tablesDB = TablesDB(client);\n\n try {\n await tablesDB.updateRow(\n '',\n '',\n '',\n {\n 'books': Operator.arrayAppend(['The Great Gatsby'])\n },\n );\n } on AppwriteException catch (e) {\n print(e);\n }\n}\n```\n```client-apple\nimport Appwrite\nimport AppwriteModels\n\nfunc main() async throws {\n let client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\n let tablesDB = TablesDB(client)\n\n do {\n _ = try await tablesDB.updateRow(\n databaseId: \"\",\n tableId: \"\",\n rowId: \"\",\n data: [\n \"books\": Operator.arrayAppend([\"The Great Gatsby\"]) \n ]\n )\n } catch {\n print(error.localizedDescription)\n }\n}\n```\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.TablesDB\nimport io.appwrite.Operator\n\nsuspend fun main() {\n val client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\n val tablesDB = TablesDB(client)\n\n tablesDB.updateRow(\n databaseId = \"\",\n tableId = \"\",\n rowId = \"\",\n data = mapOf(\n \"books\" to Operator.arrayAppend(listOf(\"The Great Gatsby\"))\n )\n )\n}\n```\n```server-go\npackage main\n\nimport (\n \"log\"\n \"github.com/appwrite/sdk-for-go/appwrite\"\n operator \"github.com/appwrite/sdk-for-go/operator\"\n)\n\nfunc main() {\n client := appwrite.NewClient(\n appwrite.WithEndpoint(\"https://.cloud.appwrite.io/v1\"),\n appwrite.WithProject(\"\"),\n appwrite.WithKey(\"\"),\n )\n\n tablesDB := appwrite.NewTablesDB(client)\n\n _, err := tablesDB.UpdateRow(\n \"\",\n \"\",\n \"\",\n tablesDB.WithUpdateRowData(map[string]any{\n \"books\": operator.ArrayAppend([]string{\"The Great Gatsby\"}),\n }),\n )\n if err != nil {\n log.Fatal(err)\n }\n}\n```\n```server-php\nsetEndpoint('https://.cloud.appwrite.io/v1')\n ->setProject('')\n ->setKey('');\n\n$tablesDB = new TablesDB($client);\n\n$result = $tablesDB->updateRow(\n '',\n '',\n '',\n [ 'books' => Operator::arrayAppend(['The Great Gatsby']) ]\n);\n```\n```server-python\nfrom appwrite.client import Client\nfrom appwrite.services.tables_db import TablesDB\nfrom appwrite.operator import Operator\n\nclient = Client()\n(client\n .set_endpoint('https://.cloud.appwrite.io/v1')\n .set_project('')\n .set_key(''))\n\ntablesDB = TablesDB(client)\n\nresult = tablesDB.update_row(\n '',\n '',\n '',\n { 'books': Operator.arrayAppend(['The Great Gatsby']) }\n)\n```\n```server-dotnet\nusing Appwrite;\nusing Appwrite.Services;\n\nvar client = new Client()\n .SetEndpoint(\"https://.cloud.appwrite.io/v1\")\n .SetProject(\"\")\n .SetKey(\"\");\n\nvar tablesDB = new TablesDB(client);\n\nawait tablesDB.UpdateRow(\n databaseId: \"\",\n tableId: \"\",\n rowId: \"\",\n data: new Dictionary\n {\n { \"books\", Operator.ArrayAppend(new[] { \"The Great Gatsby\" }) }\n }\n);\n```\n```server-ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new()\n\nclient\n .set_endpoint('https://.cloud.appwrite.io/v1')\n .set_project('')\n .set_key('')\n\ntablesDB = TablesDB.new(client)\n\nresult = tablesDB.update_row(\n '',\n '',\n '',\n { 'books' => Operator.arrayAppend(['The Great Gatsby']) }\n)\n```\n```server-java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.TablesDB;\nimport io.appwrite.Operator;\nimport java.util.*;\n\nClient client = new Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\");\n\nTablesDB tablesDB = new TablesDB(client);\n\ntablesDB.updateRow(\n \"\",\n \"\",\n \"\",\n Map.of(\"books\", Operator.arrayAppend(List.of(\"The Great Gatsby\"))),\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n System.out.println(result);\n })\n);\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::tables_db::TablesDB;\nuse appwrite::operator;\nuse serde_json::json;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box> {\n let client = Client::new()\n .set_endpoint(\"https://.cloud.appwrite.io/v1\")\n .set_project(\"\")\n .set_key(\"\");\n\n let tables_db = TablesDB::new(&client);\n\n let result = tables_db.update_row(\n \"\",\n \"\",\n \"\",\n Some(json!({\n \"books\": operator::array_append(&[\"The Great Gatsby\"])\n })),\n None,\n None,\n ).await?;\n\n Ok(())\n}\n```\n{% /multicode %}\n\n## Update the date field in a deletion table\n\nThis example demonstrates using the `dateAddDays` operator to set a scheduled deletion date 30 days from now.\n\n{% multicode %}\n```client-web\nimport { Client, TablesDB, Operator } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nconst tablesDB = new TablesDB(client);\n\nawait tablesDB.updateRow({\n databaseId: \"\",\n tableId: \"\",\n rowId: \"\",\n data: {\n scheduledDeletion: Operator.dateAddDays(30)\n }\n});\n```\n```server-nodejs\nconst sdk = require('node-appwrite');\n\nconst client = new sdk.Client();\nclient\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nconst tablesDB = new sdk.TablesDB(client);\n\nconst result = await tablesDB.updateRow({\n databaseId: '',\n tableId: '',\n rowId: '',\n data: {\n scheduledDeletion: sdk.Operator.dateAddDays(30)\n }\n});\n```\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nvoid main() async {\n final client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\n final tablesDB = TablesDB(client);\n\n try {\n await tablesDB.updateRow(\n '',\n '',\n '',\n {\n 'scheduledDeletion': Operator.dateAddDays(30)\n },\n );\n } on AppwriteException catch (e) {\n print(e);\n }\n}\n```\n```client-apple\nimport Appwrite\nimport AppwriteModels\n\nfunc main() async throws {\n let client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\n let tablesDB = TablesDB(client)\n\n do {\n _ = try await tablesDB.updateRow(\n databaseId: \"\",\n tableId: \"\",\n rowId: \"\",\n data: [\n \"scheduledDeletion\": Operator.dateAddDays(30) \n ]\n )\n } catch {\n print(error.localizedDescription)\n }\n}\n```\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.TablesDB\nimport io.appwrite.Operator\n\nsuspend fun main() {\n val client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\n val tablesDB = TablesDB(client)\n\n tablesDB.updateRow(\n databaseId = \"\",\n tableId = \"\",\n rowId = \"\",\n data = mapOf(\n \"scheduledDeletion\" to Operator.dateAddDays(30)\n )\n )\n}\n```\n```server-go\npackage main\n\nimport (\n \"log\"\n \"github.com/appwrite/sdk-for-go/appwrite\"\n operator \"github.com/appwrite/sdk-for-go/operator\"\n)\n\nfunc main() {\n client := appwrite.NewClient(\n appwrite.WithEndpoint(\"https://.cloud.appwrite.io/v1\"),\n appwrite.WithProject(\"\"),\n appwrite.WithKey(\"\"),\n )\n\n tablesDB := appwrite.NewTablesDB(client)\n\n _, err := tablesDB.UpdateRow(\n \"\",\n \"\",\n \"\",\n tablesDB.WithUpdateRowData(map[string]any{\n \"scheduledDeletion\": operator.DateAddDays(30),\n }),\n )\n if err != nil {\n log.Fatal(err)\n }\n}\n```\n```server-php\nsetEndpoint('https://.cloud.appwrite.io/v1')\n ->setProject('')\n ->setKey('');\n\n$tablesDB = new TablesDB($client);\n\n$result = $tablesDB->updateRow(\n '',\n '',\n '',\n [ 'scheduledDeletion' => Operator::dateAddDays(30) ]\n);\n```\n```server-python\nfrom appwrite.client import Client\nfrom appwrite.services.tables_db import TablesDB\nfrom appwrite.operator import Operator\n\nclient = Client()\n(client\n .set_endpoint('https://.cloud.appwrite.io/v1')\n .set_project('')\n .set_key(''))\n\ntablesDB = TablesDB(client)\n\nresult = tablesDB.update_row(\n '',\n '',\n '',\n { 'scheduledDeletion': Operator.dateAddDays(30) }\n)\n```\n```server-dotnet\nusing Appwrite;\nusing Appwrite.Services;\n\nvar client = new Client()\n .SetEndpoint(\"https://.cloud.appwrite.io/v1\")\n .SetProject(\"\")\n .SetKey(\"\");\n\nvar tablesDB = new TablesDB(client);\n\nawait tablesDB.UpdateRow(\n databaseId: \"\",\n tableId: \"\",\n rowId: \"\",\n data: new Dictionary\n {\n { \"scheduledDeletion\", Operator.DateAddDays(30) }\n }\n);\n```\n```server-ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new()\n\nclient\n .set_endpoint('https://.cloud.appwrite.io/v1')\n .set_project('')\n .set_key('')\n\ntablesDB = TablesDB.new(client)\n\nresult = tablesDB.update_row(\n '',\n '',\n '',\n { 'scheduledDeletion' => Operator.dateAddDays(30) }\n)\n```\n```server-java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.TablesDB;\nimport io.appwrite.Operator;\nimport java.util.*;\n\nClient client = new Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\");\n\nTablesDB tablesDB = new TablesDB(client);\n\ntablesDB.updateRow(\n \"\",\n \"\",\n \"\",\n Map.of(\"scheduledDeletion\", Operator.dateAddDays(30)),\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n System.out.println(result);\n })\n);\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::tables_db::TablesDB;\nuse appwrite::operator;\nuse serde_json::json;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box> {\n let client = Client::new()\n .set_endpoint(\"https://.cloud.appwrite.io/v1\")\n .set_project(\"\")\n .set_key(\"\");\n\n let tables_db = TablesDB::new(&client);\n\n let result = tables_db.update_row(\n \"\",\n \"\",\n \"\",\n Some(json!({\n \"scheduledDeletion\": operator::date_add_days(30)\n })),\n None,\n None,\n ).await?;\n\n Ok(())\n}\n```\n{% /multicode %}\n\n## Update a single row in a transaction\n\nThis example demonstrates combining multiple operators (`increment` and `dateSetNow`) in a single transaction to ensure atomic updates.\n\n{% multicode %}\n```client-web\nimport { Client, TablesDB, Operator } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nconst tablesDB = new TablesDB(client);\n\n// Create a transaction\nconst tx = await tablesDB.createTransaction();\n\n// Update row with operators inside the transaction\nawait tablesDB.updateRow({\n databaseId: \"\",\n tableId: \"\",\n rowId: \"\",\n data: {\n upvotes: Operator.increment(1),\n lastModified: Operator.dateSetNow()\n },\n transactionId: tx.$id\n});\n\n// Commit the transaction\nawait tablesDB.updateTransaction(tx.$id, 'commit');\n```\n```server-nodejs\nconst sdk = require('node-appwrite');\n\nconst client = new sdk.Client();\nclient\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nconst tablesDB = new sdk.TablesDB(client);\n\n// Create a transaction\nconst tx = await tablesDB.createTransaction();\n\n// Update row with operators inside the transaction\nawait tablesDB.updateRow({\n databaseId: '',\n tableId: '',\n rowId: '',\n data: {\n upvotes: sdk.Operator.increment(1),\n lastModified: sdk.Operator.dateSetNow()\n },\n transactionId: tx.$id\n});\n\n// Commit the transaction\nawait tablesDB.updateTransaction(tx.$id, 'commit');\n```\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nvoid main() async {\n final client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\n final tablesDB = TablesDB(client);\n\n try {\n // Create a transaction\n final tx = await tablesDB.createTransaction();\n\n // Update row with operators inside the transaction\n await tablesDB.updateRow(\n '',\n '',\n '',\n {\n 'upvotes': Operator.increment(1),\n 'lastModified': Operator.dateSetNow()\n },\n transactionId: tx.$id\n );\n\n // Commit the transaction\n await tablesDB.updateTransaction(tx.$id, 'commit');\n } on AppwriteException catch (e) {\n print(e);\n }\n}\n```\n```client-apple\nimport Appwrite\nimport AppwriteModels\n\nfunc main() async throws {\n let client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\n let tablesDB = TablesDB(client)\n\n do {\n // Create a transaction\n let tx = try await tablesDB.createTransaction()\n\n // Update row with operators inside the transaction\n _ = try await tablesDB.updateRow(\n databaseId: \"\",\n tableId: \"\",\n rowId: \"\",\n data: [\n \"upvotes\": Operator.increment(1),\n \"lastModified\": Operator.dateSetNow()\n ],\n transactionId: tx.$id\n )\n\n // Commit the transaction\n _ = try await tablesDB.updateTransaction(\n transactionId: tx.$id,\n status: \"commit\"\n )\n } catch {\n print(error.localizedDescription)\n }\n}\n```\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.TablesDB\nimport io.appwrite.Operator\n\nsuspend fun main() {\n val client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\n val tablesDB = TablesDB(client)\n\n // Create a transaction\n val tx = tablesDB.createTransaction()\n\n // Update row with operators inside the transaction\n tablesDB.updateRow(\n databaseId = \"\",\n tableId = \"\",\n rowId = \"\",\n data = mapOf(\n \"upvotes\" to Operator.increment(1),\n \"lastModified\" to Operator.dateSetNow()\n ),\n transactionId = tx.$id\n )\n\n // Commit the transaction\n tablesDB.updateTransaction(tx.$id, \"commit\")\n}\n```\n```server-go\npackage main\n\nimport (\n \"log\"\n \"github.com/appwrite/sdk-for-go/appwrite\"\n operator \"github.com/appwrite/sdk-for-go/operator\"\n)\n\nfunc main() {\n client := appwrite.NewClient(\n appwrite.WithEndpoint(\"https://.cloud.appwrite.io/v1\"),\n appwrite.WithProject(\"\"),\n appwrite.WithKey(\"\"),\n )\n\n tablesDB := appwrite.NewTablesDB(client)\n\n // Create a transaction\n tx, err := tablesDB.CreateTransaction()\n if err != nil {\n log.Fatal(err)\n }\n\n // Update row with operators inside the transaction\n _, err = tablesDB.UpdateRow(\n \"\",\n \"\",\n \"\",\n tablesDB.WithUpdateRowData(map[string]any{\n \"upvotes\": operator.Increment(1),\n \"lastModified\": operator.DateSetNow(),\n }),\n tablesDB.WithUpdateRowTransactionId(tx.Id),\n )\n if err != nil {\n log.Fatal(err)\n }\n\n // Commit the transaction\n _, err = tablesDB.UpdateTransaction(tx.Id, \"commit\")\n if err != nil {\n log.Fatal(err)\n }\n}\n```\n```server-php\nsetEndpoint('https://.cloud.appwrite.io/v1')\n ->setProject('')\n ->setKey('');\n\n$tablesDB = new TablesDB($client);\n\n// Create a transaction\n$tx = $tablesDB->createTransaction();\n\n// Update row with operators inside the transaction\n$result = $tablesDB->updateRow(\n '',\n '',\n '',\n [\n 'upvotes' => Operator::increment(1),\n 'lastModified' => Operator::dateSetNow()\n ],\n transactionId: $tx['$id']\n);\n\n// Commit the transaction\n$tablesDB->updateTransaction($tx['$id'], 'commit');\n```\n```server-python\nfrom appwrite.client import Client\nfrom appwrite.services.tables_db import TablesDB\nfrom appwrite.operator import Operator\n\nclient = Client()\n(client\n .set_endpoint('https://.cloud.appwrite.io/v1')\n .set_project('')\n .set_key(''))\n\ntablesDB = TablesDB(client)\n\n# Create a transaction\ntx = tablesDB.create_transaction()\n\n# Update row with operators inside the transaction\nresult = tablesDB.update_row(\n '',\n '',\n '',\n {\n 'upvotes': Operator.increment(1),\n 'lastModified': Operator.dateSetNow()\n },\n transaction_id=tx.id\n)\n\n# Commit the transaction\ntablesDB.update_transaction(tx.id, 'commit')\n```\n```server-dotnet\nusing Appwrite;\nusing Appwrite.Services;\n\nvar client = new Client()\n .SetEndpoint(\"https://.cloud.appwrite.io/v1\")\n .SetProject(\"\")\n .SetKey(\"\");\n\nvar tablesDB = new TablesDB(client);\n\n// Create a transaction\nvar tx = await tablesDB.CreateTransaction();\n\n// Update row with operators inside the transaction\nawait tablesDB.UpdateRow(\n databaseId: \"\",\n tableId: \"\",\n rowId: \"\",\n data: new Dictionary\n {\n { \"upvotes\", Operator.Increment(1) },\n { \"lastModified\", Operator.DateSetNow() }\n },\n transactionId: tx.Id\n);\n\n// Commit the transaction\nawait tablesDB.UpdateTransaction(tx.Id, \"commit\");\n```\n```server-ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new()\n\nclient\n .set_endpoint('https://.cloud.appwrite.io/v1')\n .set_project('')\n .set_key('')\n\ntablesDB = TablesDB.new(client)\n\n# Create a transaction\ntx = tablesDB.create_transaction\n\n# Update row with operators inside the transaction\nresult = tablesDB.update_row(\n '',\n '',\n '',\n {\n 'upvotes' => Operator.increment(1),\n 'lastModified' => Operator.dateSetNow()\n },\n transaction_id: tx['$id']\n)\n\n# Commit the transaction\ntablesDB.update_transaction(tx['$id'], 'commit')\n```\n```server-java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.TablesDB;\nimport io.appwrite.Operator;\nimport java.util.*;\n\nClient client = new Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\");\n\nTablesDB tablesDB = new TablesDB(client);\n\n// Create a transaction\ntablesDB.createTransaction(new CoroutineCallback<>((tx, txError) -> {\n if (txError != null) {\n txError.printStackTrace();\n return;\n }\n\n // Update row with operators inside the transaction\n tablesDB.updateRow(\n \"\",\n \"\",\n \"\",\n Map.of(\n \"upvotes\", Operator.increment(1),\n \"lastModified\", Operator.dateSetNow()\n ),\n tx.getId(),\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n // Commit the transaction\n tablesDB.updateTransaction(\n tx.getId(),\n \"commit\",\n new CoroutineCallback<>((commitResult, commitError) -> {\n if (commitError != null) {\n commitError.printStackTrace();\n return;\n }\n System.out.println(\"Transaction committed\");\n })\n );\n })\n );\n}));\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::tables_db::TablesDB;\nuse appwrite::operator;\nuse serde_json::json;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box> {\n let client = Client::new()\n .set_endpoint(\"https://.cloud.appwrite.io/v1\")\n .set_project(\"\")\n .set_key(\"\");\n\n let tables_db = TablesDB::new(&client);\n\n // Create a transaction\n let tx = tables_db.create_transaction(None).await?;\n\n // Update row with operators inside the transaction\n let result = tables_db.update_row(\n \"\",\n \"\",\n \"\",\n Some(json!({\n \"upvotes\": operator::increment_by(1),\n \"lastModified\": operator::date_set_now()\n })),\n None,\n Some(&tx.id),\n ).await?;\n\n // Commit the transaction\n tables_db.update_transaction(&tx.id, Some(true), None).await?;\n\n Ok(())\n}\n```\n{% /multicode %}\n\n## Update multiple rows in a transaction\n\nThis example demonstrates using `createOperations` to update multiple rows across different tables atomically, combining date and array operators in a single transaction.\n\n{% multicode %}\n```client-web\nimport { Client, TablesDB, Operator } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nconst tablesDB = new TablesDB(client);\n\n// Create a transaction\nconst tx = await tablesDB.createTransaction();\n\n// Stage multiple operations at once using createOperations\nawait tablesDB.createOperations({\n transactionId: tx.$id,\n operations: [\n {\n action: 'update',\n databaseId: '',\n tableId: '',\n rowId: '',\n data: {\n lastActivity: Operator.dateSetNow()\n }\n },\n {\n action: 'update',\n databaseId: '',\n tableId: '',\n rowId: '',\n data: {\n amount: Operator.increment(10),\n events: Operator.arrayAppend(['credit_added'])\n }\n }\n ]\n});\n\n// Commit the transaction\nawait tablesDB.updateTransaction(tx.$id, 'commit');\n```\n```server-nodejs\nconst sdk = require('node-appwrite');\n\nconst client = new sdk.Client();\nclient\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nconst tablesDB = new sdk.TablesDB(client);\n\n// Create a transaction\nconst tx = await tablesDB.createTransaction();\n\n// Stage multiple operations at once using createOperations\nawait tablesDB.createOperations({\n transactionId: tx.$id,\n operations: [\n {\n action: 'update',\n databaseId: '',\n tableId: '',\n rowId: '',\n data: {\n lastActivity: sdk.Operator.dateSetNow()\n }\n },\n {\n action: 'update',\n databaseId: '',\n tableId: '',\n rowId: '',\n data: {\n amount: sdk.Operator.increment(10),\n events: sdk.Operator.arrayAppend(['credit_added'])\n }\n }\n ]\n});\n\n// Commit the transaction\nawait tablesDB.updateTransaction(tx.$id, 'commit');\n```\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nvoid main() async {\n final client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\n final tablesDB = TablesDB(client);\n\n try {\n // Create a transaction\n final tx = await tablesDB.createTransaction();\n\n // Stage multiple operations at once using createOperations\n await tablesDB.createOperations(\n transactionId: tx.$id,\n operations: [\n {\n 'action': 'update',\n 'databaseId': '',\n 'tableId': '',\n 'rowId': '',\n 'data': {\n 'lastActivity': Operator.dateSetNow()\n }\n },\n {\n 'action': 'update',\n 'databaseId': '',\n 'tableId': '',\n 'rowId': '',\n 'data': {\n 'amount': Operator.increment(10),\n 'events': Operator.arrayAppend(['credit_added'])\n }\n }\n ],\n );\n\n // Commit the transaction\n await tablesDB.updateTransaction(tx.$id, 'commit');\n } on AppwriteException catch (e) {\n print(e);\n }\n}\n```\n```client-apple\nimport Appwrite\nimport AppwriteModels\n\nfunc main() async throws {\n let client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\n let tablesDB = TablesDB(client)\n\n do {\n // Create a transaction\n let tx = try await tablesDB.createTransaction()\n\n // Stage multiple operations at once using createOperations\n _ = try await tablesDB.createOperations(\n transactionId: tx.$id,\n operations: [\n [\n \"action\": \"update\",\n \"databaseId\": \"\",\n \"tableId\": \"\",\n \"rowId\": \"\",\n \"data\": [\n \"lastActivity\": Operator.dateSetNow()\n ]\n ],\n [\n \"action\": \"update\",\n \"databaseId\": \"\",\n \"tableId\": \"\",\n \"rowId\": \"\",\n \"data\": [\n \"amount\": Operator.increment(10),\n \"events\": Operator.arrayAppend([\"credit_added\"])\n ]\n ]\n ]\n )\n\n // Commit the transaction\n _ = try await tablesDB.updateTransaction(\n transactionId: tx.$id,\n status: \"commit\"\n )\n } catch {\n print(error.localizedDescription)\n }\n}\n```\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.TablesDB\nimport io.appwrite.Operator\n\nsuspend fun main() {\n val client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\n val tablesDB = TablesDB(client)\n\n // Create a transaction\n val tx = tablesDB.createTransaction()\n\n // Stage multiple operations at once using createOperations\n tablesDB.createOperations(\n transactionId = tx.$id,\n operations = listOf(\n mapOf(\n \"action\" to \"update\",\n \"databaseId\" to \"\",\n \"tableId\" to \"\",\n \"rowId\" to \"\",\n \"data\" to mapOf(\n \"lastActivity\" to Operator.dateSetNow()\n )\n ),\n mapOf(\n \"action\" to \"update\",\n \"databaseId\" to \"\",\n \"tableId\" to \"\",\n \"rowId\" to \"\",\n \"data\" to mapOf(\n \"amount\" to Operator.increment(10),\n \"events\" to Operator.arrayAppend(listOf(\"credit_added\"))\n )\n )\n )\n )\n\n // Commit the transaction\n tablesDB.updateTransaction(tx.$id, \"commit\")\n}\n```\n```server-go\npackage main\n\nimport (\n \"log\"\n \"github.com/appwrite/sdk-for-go/appwrite\"\n operator \"github.com/appwrite/sdk-for-go/operator\"\n)\n\nfunc main() {\n client := appwrite.NewClient(\n appwrite.WithEndpoint(\"https://.cloud.appwrite.io/v1\"),\n appwrite.WithProject(\"\"),\n appwrite.WithKey(\"\"),\n )\n\n tablesDB := appwrite.NewTablesDB(client)\n\n // Create a transaction\n tx, err := tablesDB.CreateTransaction()\n if err != nil {\n log.Fatal(err)\n }\n\n // Stage multiple operations at once using createOperations\n _, err = tablesDB.CreateOperations(\n tx.Id,\n []map[string]any{\n {\n \"action\": \"update\",\n \"databaseId\": \"\",\n \"tableId\": \"\",\n \"rowId\": \"\",\n \"data\": map[string]any{\n \"lastActivity\": operator.DateSetNow(),\n },\n },\n {\n \"action\": \"update\",\n \"databaseId\": \"\",\n \"tableId\": \"\",\n \"rowId\": \"\",\n \"data\": map[string]any{\n \"amount\": operator.Increment(10),\n \"events\": operator.ArrayAppend([]string{\"credit_added\"}),\n },\n },\n },\n )\n if err != nil {\n log.Fatal(err)\n }\n\n // Commit the transaction\n _, err = tablesDB.UpdateTransaction(tx.Id, \"commit\")\n if err != nil {\n log.Fatal(err)\n }\n}\n```\n```server-php\nsetEndpoint('https://.cloud.appwrite.io/v1')\n ->setProject('')\n ->setKey('');\n\n$tablesDB = new TablesDB($client);\n\n// Create a transaction\n$tx = $tablesDB->createTransaction();\n\n// Stage multiple operations at once using createOperations\n$tablesDB->createOperations(\n transactionId: $tx['$id'],\n operations: [\n [\n 'action' => 'update',\n 'databaseId' => '',\n 'tableId' => '',\n 'rowId' => '',\n 'data' => [\n 'lastActivity' => Operator::dateSetNow()\n ]\n ],\n [\n 'action' => 'update',\n 'databaseId' => '',\n 'tableId' => '',\n 'rowId' => '',\n 'data' => [\n 'amount' => Operator::increment(10),\n 'events' => Operator::arrayAppend(['credit_added'])\n ]\n ]\n ]\n);\n\n// Commit the transaction\n$tablesDB->updateTransaction($tx['$id'], 'commit');\n```\n```server-python\nfrom appwrite.client import Client\nfrom appwrite.services.tables_db import TablesDB\nfrom appwrite.operator import Operator\n\nclient = Client()\n(client\n .set_endpoint('https://.cloud.appwrite.io/v1')\n .set_project('')\n .set_key(''))\n\ntablesDB = TablesDB(client)\n\n# Create a transaction\ntx = tablesDB.create_transaction()\n\n# Stage multiple operations at once using createOperations\ntablesDB.create_operations(\n transaction_id=tx.id,\n operations=[\n {\n 'action': 'update',\n 'databaseId': '',\n 'tableId': '',\n 'rowId': '',\n 'data': {\n 'lastActivity': Operator.dateSetNow()\n }\n },\n {\n 'action': 'update',\n 'databaseId': '',\n 'tableId': '',\n 'rowId': '',\n 'data': {\n 'amount': Operator.increment(10),\n 'events': Operator.arrayAppend(['credit_added'])\n }\n }\n ]\n)\n\n# Commit the transaction\ntablesDB.update_transaction(tx.id, 'commit')\n```\n```server-dotnet\nusing Appwrite;\nusing Appwrite.Services;\n\nvar client = new Client()\n .SetEndpoint(\"https://.cloud.appwrite.io/v1\")\n .SetProject(\"\")\n .SetKey(\"\");\n\nvar tablesDB = new TablesDB(client);\n\n// Create a transaction\nvar tx = await tablesDB.CreateTransaction();\n\n// Stage multiple operations at once using createOperations\nawait tablesDB.CreateOperations(\n transactionId: tx.Id,\n operations: new List>\n {\n new Dictionary\n {\n { \"action\", \"update\" },\n { \"databaseId\", \"\" },\n { \"tableId\", \"\" },\n { \"rowId\", \"\" },\n { \"data\", new Dictionary\n {\n { \"lastActivity\", Operator.DateSetNow() }\n }\n }\n },\n new Dictionary\n {\n { \"action\", \"update\" },\n { \"databaseId\", \"\" },\n { \"tableId\", \"\" },\n { \"rowId\", \"\" },\n { \"data\", new Dictionary\n {\n { \"amount\", Operator.Increment(10) },\n { \"events\", Operator.ArrayAppend(new[] { \"credit_added\" }) }\n }\n }\n }\n }\n);\n\n// Commit the transaction\nawait tablesDB.UpdateTransaction(tx.Id, \"commit\");\n```\n```server-ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new()\n\nclient\n .set_endpoint('https://.cloud.appwrite.io/v1')\n .set_project('')\n .set_key('')\n\ntablesDB = TablesDB.new(client)\n\n# Create a transaction\ntx = tablesDB.create_transaction\n\n# Stage multiple operations at once using createOperations\ntablesDB.create_operations(\n transaction_id: tx['$id'],\n operations: [\n {\n 'action' => 'update',\n 'databaseId' => '',\n 'tableId' => '',\n 'rowId' => '',\n 'data' => {\n 'lastActivity' => Operator.dateSetNow()\n }\n },\n {\n 'action' => 'update',\n 'databaseId' => '',\n 'tableId' => '',\n 'rowId' => '',\n 'data' => {\n 'amount' => Operator.increment(10),\n 'events' => Operator.arrayAppend(['credit_added'])\n }\n }\n ]\n)\n\n# Commit the transaction\ntablesDB.update_transaction(tx['$id'], 'commit')\n```\n```server-java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.TablesDB;\nimport io.appwrite.Operator;\nimport java.util.*;\n\nClient client = new Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\");\n\nTablesDB tablesDB = new TablesDB(client);\n\n// Create a transaction\ntablesDB.createTransaction(new CoroutineCallback<>((tx, txError) -> {\n if (txError != null) {\n txError.printStackTrace();\n return;\n }\n\n // Stage multiple operations at once using createOperations\n List> operations = Arrays.asList(\n Map.of(\n \"action\", \"update\",\n \"databaseId\", \"\",\n \"tableId\", \"\",\n \"rowId\", \"\",\n \"data\", Map.of(\n \"lastActivity\", Operator.dateSetNow()\n )\n ),\n Map.of(\n \"action\", \"update\",\n \"databaseId\", \"\",\n \"tableId\", \"\",\n \"rowId\", \"\",\n \"data\", Map.of(\n \"amount\", Operator.increment(10),\n \"events\", Operator.arrayAppend(List.of(\"credit_added\"))\n )\n )\n );\n\n tablesDB.createOperations(\n tx.getId(),\n operations,\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n // Commit the transaction\n tablesDB.updateTransaction(\n tx.getId(),\n \"commit\",\n new CoroutineCallback<>((commitResult, commitError) -> {\n if (commitError != null) {\n commitError.printStackTrace();\n return;\n }\n System.out.println(\"Transaction committed\");\n })\n );\n })\n );\n}));\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::tables_db::TablesDB;\nuse appwrite::operator;\nuse serde_json::json;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box> {\n let client = Client::new()\n .set_endpoint(\"https://.cloud.appwrite.io/v1\")\n .set_project(\"\")\n .set_key(\"\");\n\n let tables_db = TablesDB::new(&client);\n\n // Create a transaction\n let tx = tables_db.create_transaction(None).await?;\n\n // Stage multiple operations at once using createOperations\n tables_db.create_operations(\n &tx.id,\n Some(vec![\n json!({\n \"action\": \"update\",\n \"databaseId\": \"\",\n \"tableId\": \"\",\n \"rowId\": \"\",\n \"data\": {\n \"lastActivity\": operator::date_set_now()\n }\n }),\n json!({\n \"action\": \"update\",\n \"databaseId\": \"\",\n \"tableId\": \"\",\n \"rowId\": \"\",\n \"data\": {\n \"amount\": operator::increment_by(10),\n \"events\": operator::array_append(&[\"credit_added\"])\n }\n }),\n ]),\n ).await?;\n\n // Commit the transaction\n tables_db.update_transaction(&tx.id, Some(true), None).await?;\n\n Ok(())\n}\n```\n{% /multicode %}"}, {"path": "docs/products/databases/order", "title": "Order", "description": "Understand how to do data ordering in Appwrite Databases. Learn how to order and sort your database records for efficient data retrieval.", "content": "You can order results returned by Appwrite Databases by using an order query.\nFor best performance, create an [index](/docs/products/databases/tables#indexes) on the column you plan to order by.\n\n# Ordering one column {% #one-column %}\n\nWhen querying using the [listRows](/docs/references/cloud/client-web/tables#listRows) endpoint,\nyou can specify the order of the rows returned using the `Query.orderAsc()` and `Query.orderDesc()` query methods.\n\n{% multicode %}\n```client-web\nimport { Client, Query, TablesDB } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nconst tablesDB = new TablesDB(client);\n\ntablesDB.listRows({\n databaseId: '',\n tableId: '',\n queries: [\n Query.orderAsc('title'),\n ]\n});\n```\n\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nvoid main() async {\n final client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\n final tablesDB = TablesDB(client);\n\n try {\n final rows = await tablesDB.listRows(\n databaseId: '',\n tableId: '',\n queries: [\n Query.orderAsc('title')\n ]\n );\n } on AppwriteException catch(e) {\n print(e);\n }\n}\n```\n\n```client-apple\nimport Appwrite\nimport AppwriteModels\n\nfunc main() async throws {\n let client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\n let tablesDB = TablesDB(client)\n\n do {\n let rows = try await tablesDB.listRows(\n databaseId: \"\",\n tableId: \"\",\n queries: [\n Query.orderAsc(\"title\")\n ]\n )\n } catch {\n print(error.localizedDescription)\n }\n}\n```\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.Query\nimport io.appwrite.services.TablesDB\n\nsuspend fun main() {\n val client = Client(applicationContext)\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\n val tablesDB = TablesDB(client)\n\n try {\n val rows = tablesDB.listRows(\n databaseId = \"\",\n tableId = \"\",\n queries = [\n Query.orderAsc(\"title\")\n ]\n )\n } catch (e: AppwriteException) {\n Log.e(\"Appwrite\", e.message)\n }\n}\n```\n\n```graphql\nquery {\n tablesListRows(\n databaseId: \"\",\n tableId: \"\"\n queries: [\"orderAsc(\\\"title\\\")\"]\n ) {\n total\n rows {\n _id\n data\n }\n }\n}\n```\n\n{% /multicode %}\n\n# Multiple columns {% #multiple-columns %}\nTo sort based on multiple columns, simply provide multiple query methods.\nFor better performance, create an index on the first column that you order by.\n\nIn the example below, the movies returned will be first sorted by `title` in ascending order, then sorted by `year` in descending order.\n{% multicode %}\n\n```js\n// Web SDK code example for sorting based on multiple columns\n// ...\n\n// List rows and sort based on multiple columns\ntablesDB.listRows({\n databaseId: '',\n tableId: '',\n queries: [\n Query.orderAsc('title'), // Order first by title in ascending order\n Query.orderDesc('year'), // Then, order by year in descending order\n ]\n});\n```\n```dart\n// Flutter SDK code example for sorting based on multiple columns\n// ...\n\n// List rows and sort based on multiple columns\ntry {\n final rows = await tablesDB.listRows(\n databaseId: '',\n tableId: '',\n queries: [\n Query.orderAsc('title'), // Order by title in ascending order\n Query.orderDesc('year') // Order by year in descending order\n ]\n );\n} on AppwriteException catch(e) {\n print(e);\n}\n```\n```kotlin\n// Android SDK code example for sorting based on multiple columns\n// ...\n\n// List rows and sort based on multiple columns\ntry {\n val rows = tablesDB.listRows(\n databaseId = \"\",\n tableId = \"\",\n queries = [\n Query.orderAsc(\"title\"), // Order by title in ascending order\n Query.orderDesc(\"year\") // Order by year in descending order\n ]\n );\n} catch (e: AppwriteException) {\n Log.e(\"Appwrite\", e.message);\n}\n```\n```swift\n// Apple SDK code example for sorting based on multiple columns\n// ...\n\n// List rows and sort based on multiple columns\ndo {\n let rows = try await tablesDB.listRows(\n databaseId: \"\",\n tableId: \"\",\n queries: [\n Query.orderAsc(\"title\"), // Order by title in ascending order\n Query.orderDesc(\"year\") // Order by year in descending order\n ]\n );\n} catch {\n print(error.localizedDescription);\n}\n```\n```graphql\nquery {\n tablesListRows(\n databaseId: \"\",\n tableId: \"\",\n queries: [\"orderAsc(\\\"title\\\")\", \"orderDesc(\\\"year\\\")\"]\n ) {\n total\n rows {\n _id\n data\n }\n }\n}\n```\n{% /multicode %}\n\n# Ordering by sequence {% #sequence-ordering %}\n\nFor 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.\n\n{% multicode %}\n```client-web\nimport { Client, Query, TablesDB } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nconst tablesDB = new TablesDB(client);\n\ntablesDB.listRows({\n databaseId: '',\n tableId: '',\n queries: [\n Query.orderAsc('$sequence'),\n ]\n});\n```\n\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nvoid main() async {\n final client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\n final tablesDB = TablesDB(client);\n\n try {\n final rows = await tablesDB.listRows(\n databaseId: '',\n tableId: '',\n queries: [\n Query.orderAsc('\\$sequence')\n ]\n );\n } on AppwriteException catch(e) {\n print(e);\n }\n}\n```\n\n```client-apple\nimport Appwrite\nimport AppwriteModels\n\nfunc main() async throws {\n let client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\n let tablesDB = TablesDB(client)\n\n do {\n let rows = try await tablesDB.listRows(\n databaseId: \"\",\n tableId: \"\",\n queries: [\n Query.orderAsc(\"$sequence\")\n ]\n )\n } catch {\n print(error.localizedDescription)\n }\n}\n```\n\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.Query\nimport io.appwrite.services.TablesDB\n\nsuspend fun main() {\n val client = Client(applicationContext)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\n val tablesDB = TablesDB(client)\n\n try {\n val rows = tablesDB.listRows(\n databaseId = \"\",\n tableId = \"\",\n queries = listOf(\n Query.orderAsc(\"\\$sequence\")\n )\n )\n } catch (e: AppwriteException) {\n Log.e(\"Appwrite\", e.message)\n }\n}\n```\n\n```graphql\nquery {\n tablesListRows(\n databaseId: \"\",\n tableId: \"\"\n queries: [\"orderAsc(\\\"$sequence\\\")\"]\n ) {\n total\n rows {\n _id\n data\n }\n }\n}\n```\n{% /multicode %}\n\nThe `$sequence` field is useful when you need:\n- Consistent ordering for pagination, especially with high-frequency inserts\n- Reliable insertion order tracking when timestamps might not be precise enough\n- Simple numeric ordering without managing custom counter fields"}, {"path": "docs/products/databases/pagination", "title": "Pagination", "description": "Implement pagination for large data sets in Appwrite Databases. Explore techniques for splitting and displaying data across multiple pages.", "content": "As your database grows in size, you'll need to paginate results returned.\nPagination improves performance by returning a subset of results that match a query at a time, called a page.\n\nBy default, list operations return 25 items per page, which can be changed using the `Query.limit(25)` operator.\nThere is no hard limit on the number of items you can request. However, beware that **large pages can degrade performance**.\n\n# Offset pagination {% #offset-pagination %}\n\nOffset pagination works by dividing rows into `M` pages containing `N` rows.\nEvery page is retrieved by skipping `offset = M * (N - 1)` items and reading the following `M` pages.\n\nUsing `Query.limit()` and `Query.offset()` you can achieve offset pagination.\nWith `Query.limit()` you can define how many rows can be returned from one request.\nThe `Query.offset()` is number of records you wish to skip before selecting records.\n\n{% multicode %}\n```client-web\nimport { Client, Query, TablesDB } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nconst tablesDB = new TablesDB(client);\n\n// Page 1\nconst page1 = await tablesDB.listRows(\n '',\n '',\n [\n Query.limit(25),\n Query.offset(0)\n ]\n);\n\n// Page 2\nconst page2 = await tablesDB.listRows(\n '',\n '',\n [\n Query.limit(25),\n Query.offset(25)\n ]\n);\n```\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nvoid main() async {\n final client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\n final tablesDB = TablesDB(client);\n\n final page1 = await tablesDB.listRows(\n databaseId: '',\n tableId: '',\n queries: [\n Query.limit(25),\n Query.offset(0)\n ]\n );\n\n final page2 = await tablesDB.listRows(\n databaseId: '',\n tableId: '',\n queries: [\n Query.limit(25),\n Query.offset(25)\n ]\n );\n}\n```\n```client-apple\nimport Appwrite\nimport AppwriteModels\n\nfunc main() async throws {\n let client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\n let tablesDB = TablesDB(client)\n\n let page1 = try await tablesDB.listRows(\n databaseId: \"\",\n tableId: \"\",\n queries: [\n Query.limit(25),\n Query.offset(0)\n ]\n )\n\n let page2 = try await tablesDB.listRows(\n databaseId: \"\",\n tableId: \"\",\n queries: [\n Query.limit(25),\n Query.offset(25)\n ]\n )\n}\n```\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.Query\nimport io.appwrite.services.TablesDB\n\nsuspend fun main() {\n val client = Client(applicationContext)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\n val tablesDB = TablesDB(client)\n\n val page1 = tablesDB.listRows(\n databaseId = \"\",\n tableId = \"\",\n queries = [\n Query.limit(25),\n Query.offset(0)\n ]\n )\n\n val page2 = tablesDB.listRows(\n databaseId = \"\",\n tableId = \"\",\n queries = [\n Query.limit(25),\n Query.offset(25)\n ]\n )\n}\n```\n\n{% /multicode %}\n\n{% info title=\"Drawbacks\" %}\nWhile traditional offset pagination is familiar, it comes with some drawbacks.\nThe 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.\nIf the data changes frequently, offset pagination will also produce **missing and duplicate** results.\n{% /info %}\n\n# Cursor pagination {% #cursor-pagination %}\n\nThe cursor is a unique identifier for a row that points to where the next page should start.\nAfter 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.\nPass the first row's ID into the `Query.cursorBefore(firstId)` query method to retrieve the previous page.\n\n{% multicode %}\n\n```client-web\nimport { Client, Query, TablesDB } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\");\n\nconst tablesDB = new TablesDB(client);\n\n// Page 1\nconst page1 = await tablesDB.listRows(\n '',\n '',\n [\n Query.limit(25),\n ]\n);\n\nconst lastId = page1.rows[page1.rows.length - 1].$id;\n\n// Page 2\nconst page2 = await tablesDB.listRows(\n '',\n '',\n [\n Query.limit(25),\n Query.cursorAfter(lastId),\n ]\n);\n```\n\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nvoid main() async {\n final client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\n final tablesDB = TablesDB(client);\n\n final page1 = await tablesDB.listRows(\n databaseId: '',\n tableId: '',\n queries: [\n Query.limit(25)\n ]\n );\n\n final lastId = page1.rows[page1.rows.length - 1].$id;\n\n final page2 = await tablesDB.listRows(\n databaseId: '',\n tableId: '',\n queries: [\n Query.limit(25),\n Query.cursorAfter(lastId)\n ]\n );\n\n}\n```\n```client-apple\nimport Appwrite\nimport AppwriteModels\n\nfunc main() async throws {\n let client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\n let tablesDB = TablesDB(client)\n\n let page1 = try await tablesDB.listRows(\n databaseId: \"\",\n tableId: \"\",\n queries: [\n Query.limit(25)\n ]\n )\n\n let lastId = page1.rows[page1.rows.count - 1].$id\n\n let page2 = try await tablesDB.listRows(\n databaseId: \"\",\n tableId: \"\",\n queries: [\n Query.limit(25),\n Query.cursorAfter(lastId)\n ]\n )\n}\n```\n```client-android-kotlin\nimport android.util.Log\nimport io.appwrite.AppwriteException\nimport io.appwrite.Client\nimport io.appwrite.Query\nimport io.appwrite.services.TablesDB\n\nsuspend fun main() {\n val client = Client(applicationContext)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\n val tablesDB = TablesDB(client)\n\n val page1 = tablesDB.listRows(\n databaseId = \"\",\n tableId = \"\",\n queries = [\n Query.limit(25)\n ]\n )\n\n val lastId = page1.rows[page1.rows.size - 1].$id\n\n val page2 = tablesDB.listRows(\n databaseId = \"\",\n tableId = \"\",\n queries = [\n Query.limit(25),\n Query.cursorAfter(lastId)\n ]\n )\n}\n```\n\n{% /multicode %}\n\n# When to use what? {% #when-to-use %}\nOffset pagination should be used for tables that rarely change.\nOffset pagination allow you to create indicator of the current page number and total page number.\nFor example, a list with up to 20 pages or static data like a list of countries or currencies.\nUsing offset pagination on large tables and frequently updated tables may result in slow performance and **missing and duplicate** results.\n\nCursor pagination should be used for frequently updated tablesDB.\nIt is best suited for lazy-loaded pages with infinite scrolling.\nFor example, a feed, comment section, chat history, or high volume datasets.\n\n# Skip totals for faster lists {% #skip-totals %}\n\nBy 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.\n\nIf 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.\n\nRecommendations:\n- Use with cursor pagination for the best performance and UX.\n- Keep the default behavior when you need “N results” or “Page X of Y”.\n\n{% multicode %}\n```client-web\nimport { Client, Query, TablesDB } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nconst tablesDB = new TablesDB(client);\n\nconst page = await tablesDB.listRows({\n databaseId: '',\n tableId: '',\n queries: [\n Query.limit(25)\n ],\n total: false // Skip computing total count\n});\n```\n```server-nodejs\nconst sdk = require('node-appwrite');\n\nconst client = new sdk.Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nconst tablesDB = new sdk.TablesDB(client);\n\nconst page = await tablesDB.listRows({\n databaseId: '',\n tableId: '',\n queries: [\n sdk.Query.limit(25)\n ],\n total: false // Skip computing total count\n});\n```\n```server-python\nfrom appwrite.client import Client\nfrom appwrite.services.tables_db import TablesDB\nfrom appwrite.query import Query\n\nclient = Client()\nclient.set_endpoint('https://.cloud.appwrite.io/v1')\nclient.set_project('')\nclient.set_key('')\n\ntables_db = TablesDB(client)\n\npage = tables_db.list_rows(\n database_id='',\n table_id='',\n queries=[\n Query.limit(25)\n ],\n total=False # Skip computing total count\n)\n```\n```server-ruby\nrequire 'appwrite'\n\nclient = Appwrite::Client.new\n .set_endpoint('https://.cloud.appwrite.io/v1')\n .set_project('')\n .set_key('')\n\ntables_db = Appwrite::TablesDB.new(client)\n\npage = tables_db.list_rows(\n database_id: '',\n table_id: '',\n queries: [\n Appwrite::Query.limit(25)\n ],\n total: false # Skip computing total count\n)\n```\n```server-deno\nimport { Client, Query, TablesDB } from \"https://deno.land/x/appwrite/mod.ts\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nconst tablesDB = new TablesDB(client);\n\nconst page = await tablesDB.listRows({\n databaseId: '',\n tableId: '',\n queries: [\n Query.limit(25)\n ],\n total: false // Skip computing total count\n});\n```\n```server-php\nsetEndpoint('https://.cloud.appwrite.io/v1')\n ->setProject('')\n ->setKey('');\n\n$tablesDB = new TablesDB($client);\n\n$page = $tablesDB->listRows(\n databaseId: '',\n tableId: '',\n queries: [\n Query::limit(25)\n ],\n total: false // Skip computing total count\n);\n```\n```server-go\npackage main\n\nimport (\n \"fmt\"\n \"github.com/appwrite/sdk-for-go/appwrite\"\n \"github.com/appwrite/sdk-for-go/query\"\n)\n\nfunc main() {\n client := appwrite.NewClient()\n client.SetEndpoint(\"https://.cloud.appwrite.io/v1\")\n client.SetProject(\"\")\n client.SetKey(\"\")\n\n tablesDB := appwrite.NewTablesDB(client)\n\n page, err := tablesDB.ListRows(\n \"\",\n \"\",\n appwrite.WithListRowsQueries([]string{\n query.Limit(25)\n }),\n appwrite.WithListRowsTotal(false), // Skip computing total count\n )\n\n if err != nil {\n fmt.Println(err)\n }\n}\n```\n```server-swift\nimport Appwrite\nimport AppwriteModels\n\nfunc main() async throws {\n let client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\")\n\n let tablesDB = TablesDB(client)\n\n let page = try await tablesDB.listRows(\n databaseId: \"\",\n tableId: \"\",\n queries: [\n Query.limit(25)\n ],\n total: false // Skip computing total count\n )\n}\n```\n```server-kotlin\nimport io.appwrite.Client\nimport io.appwrite.Query\nimport io.appwrite.services.TablesDB\n\nsuspend fun main() {\n val client = Client(applicationContext)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\")\n\n val tablesDB = TablesDB(client)\n\n val page = tablesDB.listRows(\n databaseId = \"\",\n tableId = \"\",\n queries = listOf(\n Query.limit(25)\n ),\n total = false // Skip computing total count\n )\n}\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::tables_db::TablesDB;\nuse appwrite::query::Query;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box> {\n let client = Client::new()\n .set_endpoint(\"https://.cloud.appwrite.io/v1\")\n .set_project(\"\")\n .set_key(\"\");\n\n let tables_db = TablesDB::new(&client);\n\n let page = tables_db.list_rows(\n \"\",\n \"\",\n Some(vec![\n Query::limit(25).to_string(),\n ]),\n None, // transaction_id\n Some(false), // total - Skip computing total count\n None, // ttl\n ).await?;\n\n println!(\"{:?}\", page);\n Ok(())\n}\n```\n```server-java\nimport io.appwrite.Client;\nimport io.appwrite.Query;\nimport io.appwrite.services.TablesDB;\n\npublic class Main {\n public static void main(String[] args) throws Exception {\n Client client = new Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\");\n\n TablesDB tablesDB = new TablesDB(client);\n\n RowList page = tablesDB.listRows(\n \"\",\n \"\",\n Arrays.asList(\n Query.limit(25)\n ),\n false // Skip computing total count\n );\n }\n}\n```\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nvoid main() async {\n final client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\n final tablesDB = TablesDB(client);\n\n final page = await tablesDB.listRows(\n databaseId: '',\n tableId: '',\n queries: [\n Query.limit(25)\n ],\n total: false, // Skip computing total count\n );\n}\n```\n```client-apple\nimport Appwrite\nimport AppwriteModels\n\nfunc main() async throws {\n let client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\n let tablesDB = TablesDB(client)\n\n let page = try await tablesDB.listRows(\n databaseId: \"\",\n tableId: \"\",\n queries: [\n Query.limit(25)\n ],\n total: false // Skip computing total count\n )\n}\n```\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.Query\nimport io.appwrite.services.TablesDB\n\nsuspend fun main() {\n val client = Client(applicationContext)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\n val tablesDB = TablesDB(client)\n\n val page = tablesDB.listRows(\n databaseId = \"\",\n tableId = \"\",\n queries = listOf(\n Query.limit(25)\n ),\n total = false // Skip computing total count\n )\n}\n```\n```graphql\nquery {\n tablesListRows(\n databaseId: \"\",\n tableId: \"\",\n queries: [\"limit(25)\"],\n total: false\n ) {\n total\n rows {\n _id\n data\n }\n }\n}\n```\n```http\nGET /v1/tablesdb//tables//rows?total=false HTTP/1.1\nContent-Type: application/json\nX-Appwrite-Project: \n```\n```json\n{\n \"total\": 0,\n \"rows\": [\n { \"_id\": \"...\", \"data\": { /* ... */ } }\n ]\n}\n```\n{% /multicode %}\n\n# Cache list responses {% #cache-list-responses %}\n\nYou 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.\n\nSet `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.\n\n{% multicode %}\n```client-web\nimport { Client, Query, TablesDB } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nconst tablesDB = new TablesDB(client);\n\nconst page = await tablesDB.listRows({\n databaseId: '',\n tableId: '',\n queries: [\n Query.limit(25)\n ],\n ttl: 60 // Cache for 60 seconds\n});\n```\n```server-nodejs\nconst sdk = require('node-appwrite');\n\nconst client = new sdk.Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nconst tablesDB = new sdk.TablesDB(client);\n\nconst page = await tablesDB.listRows({\n databaseId: '',\n tableId: '',\n queries: [\n sdk.Query.limit(25)\n ],\n ttl: 60 // Cache for 60 seconds\n});\n```\n```server-python\nfrom appwrite.client import Client\nfrom appwrite.services.tables_db import TablesDB\nfrom appwrite.query import Query\n\nclient = Client()\nclient.set_endpoint('https://.cloud.appwrite.io/v1')\nclient.set_project('')\nclient.set_key('')\n\ntables_db = TablesDB(client)\n\npage = tables_db.list_rows(\n database_id='',\n table_id='',\n queries=[\n Query.limit(25)\n ],\n ttl=60 # Cache for 60 seconds\n)\n```\n```server-ruby\nrequire 'appwrite'\n\nclient = Appwrite::Client.new\n .set_endpoint('https://.cloud.appwrite.io/v1')\n .set_project('')\n .set_key('')\n\ntables_db = Appwrite::TablesDB.new(client)\n\npage = tables_db.list_rows(\n database_id: '',\n table_id: '',\n queries: [\n Appwrite::Query.limit(25)\n ],\n ttl: 60 # Cache for 60 seconds\n)\n```\n```server-deno\nimport { Client, Query, TablesDB } from \"https://deno.land/x/appwrite/mod.ts\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nconst tablesDB = new TablesDB(client);\n\nconst page = await tablesDB.listRows({\n databaseId: '',\n tableId: '',\n queries: [\n Query.limit(25)\n ],\n ttl: 60 // Cache for 60 seconds\n});\n```\n```server-php\nsetEndpoint('https://.cloud.appwrite.io/v1')\n ->setProject('')\n ->setKey('');\n\n$tablesDB = new TablesDB($client);\n\n$page = $tablesDB->listRows(\n databaseId: '',\n tableId: '',\n queries: [\n Query::limit(25)\n ],\n ttl: 60 // Cache for 60 seconds\n);\n```\n```server-go\npackage main\n\nimport (\n \"fmt\"\n \"github.com/appwrite/sdk-for-go/client\"\n \"github.com/appwrite/sdk-for-go/tablesdb\"\n \"github.com/appwrite/sdk-for-go/query\"\n)\n\nfunc main() {\n clt := client.New(\n client.WithEndpoint(\"https://.cloud.appwrite.io/v1\"),\n client.WithProject(\"\"),\n client.WithKey(\"\"),\n )\n\n tablesDB := tablesdb.New(clt)\n\n page, err := tablesDB.ListRows(\n \"\",\n \"\",\n tablesDB.WithListRowsQueries([]string{\n query.Limit(25),\n }),\n tablesDB.WithListRowsTtl(60), // Cache for 60 seconds\n )\n\n if err != nil {\n fmt.Println(err)\n }\n _ = page\n}\n```\n```server-swift\nimport Appwrite\nimport AppwriteModels\n\nfunc main() async throws {\n let client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\")\n\n let tablesDB = TablesDB(client)\n\n let page = try await tablesDB.listRows(\n databaseId: \"\",\n tableId: \"\",\n queries: [\n Query.limit(25)\n ],\n ttl: 60 // Cache for 60 seconds\n )\n}\n```\n```server-kotlin\nimport io.appwrite.Client\nimport io.appwrite.Query\nimport io.appwrite.services.TablesDB\n\nsuspend fun main() {\n val client = Client(applicationContext)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\")\n\n val tablesDB = TablesDB(client)\n\n val page = tablesDB.listRows(\n databaseId = \"\",\n tableId = \"\",\n queries = listOf(\n Query.limit(25)\n ),\n ttl = 60 // Cache for 60 seconds\n )\n}\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::tables_db::TablesDB;\nuse appwrite::query::Query;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box> {\n let client = Client::new()\n .set_endpoint(\"https://.cloud.appwrite.io/v1\")\n .set_project(\"\")\n .set_key(\"\");\n\n let tables_db = TablesDB::new(&client);\n\n let page = tables_db.list_rows(\n \"\",\n \"\",\n Some(vec![\n Query::limit(25).to_string(),\n ]),\n None, // transaction_id\n None, // total\n Some(60), // ttl - Cache for 60 seconds\n ).await?;\n\n println!(\"{:?}\", page);\n Ok(())\n}\n```\n```server-java\nimport io.appwrite.Client;\nimport io.appwrite.Query;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.TablesDB;\n\nClient client = new Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\");\n\nTablesDB tablesDB = new TablesDB(client);\n\ntablesDB.listRows(\n \"\",\n \"\",\n List.of(Query.limit(25)),\n null, // transactionId\n null, // total\n 60, // ttl - Cache for 60 seconds\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n System.out.println(result);\n })\n);\n```\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nvoid main() async {\n final client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\n final tablesDB = TablesDB(client);\n\n final page = await tablesDB.listRows(\n databaseId: '',\n tableId: '',\n queries: [\n Query.limit(25)\n ],\n ttl: 60, // Cache for 60 seconds\n );\n}\n```\n```client-apple\nimport Appwrite\nimport AppwriteModels\n\nfunc main() async throws {\n let client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\n let tablesDB = TablesDB(client)\n\n let page = try await tablesDB.listRows(\n databaseId: \"\",\n tableId: \"\",\n queries: [\n Query.limit(25)\n ],\n ttl: 60 // Cache for 60 seconds\n )\n}\n```\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.Query\nimport io.appwrite.services.TablesDB\n\nsuspend fun main() {\n val client = Client(applicationContext)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\n val tablesDB = TablesDB(client)\n\n val page = tablesDB.listRows(\n databaseId = \"\",\n tableId = \"\",\n queries = listOf(\n Query.limit(25)\n ),\n ttl = 60 // Cache for 60 seconds\n )\n}\n```\n```graphql\nquery {\n tablesListRows(\n databaseId: \"\",\n tableId: \"\",\n queries: [\"limit(25)\"],\n ttl: 60\n ) {\n total\n rows {\n _id\n data\n }\n }\n}\n```\n```http\nGET /v1/tablesdb//tables//rows?ttl=60 HTTP/1.1\nContent-Type: application/json\nX-Appwrite-Project: \n```\n{% /multicode %}\n\nRow 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)."}, {"path": "docs/products/databases/permissions", "title": "Database permissions", "description": "Enhance data security and access control with Appwrite Database Permissions. Learn how to set permissions and access rules for your database tables", "content": "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.\nPermissions exist at two levels, table level and row level permissions.\n\nIn Appwrite, permissions are **granted**, meaning a user has no access by default and receive access when granted.\nA user with access granted at either table level or row level will be able to access a row.\nUsers **don't need access at both levels** to access rows.\n\n# Table level {% #table-level %}\nTable level permissions apply to every row in the table.\nIf a user has read, create, update, or delete permissions at the table level, the user can access **all rows** inside the table.\n\nConfigure table level permissions by navigating to **Your table** > **Settings** > **Permissions**.\n\n[Learn more about permissions and roles](/docs/advanced/security/permissions)\n\n# Row level {% #row-level %}\nRow level permissions grant access to individual rows.\nIf a user has read, create, update, or delete permissions at the row level, the user can access the **individual row**.\n\nRow level permissions are only applied if Row Security is enabled in the settings of your table.\nEnable row level permissions by navigating to **Your table** > **Settings** > **Row security**.\n\nRow level permissions are configured in individual rows.\n\n[Learn more about permissions and roles](/docs/advanced/security/permissions)\n\n# Common use cases {% #common-use-cases %}\n\nFor 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."}, {"path": "docs/products/databases/queries", "title": "Queries", "description": "Harness the power of querying with Appwrite tablesDB. Discover various query options, filtering, sorting, and advanced querying techniques.", "content": "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.\n\n# Query class {% #query-class %}\n\nAppwrite SDKs provide a `Query` class to help you build queries. The `Query` class has methods for each type of supported query operation.\n\n# Building queries {% #building-queries %}\n\nQueries are passed to an endpoint through the `queries` parameter as an array of query strings, which can be generated using the `Query` class.\n\nEach query method is logically separated via `AND` operations. For `OR` operation, pass multiple values into the query method separated by commas.\nFor example `Query.equal('title', ['Avatar', 'Lord of the Rings'])` will fetch the movies `Avatar` or `Lord of the Rings`.\n\n{% info title=\"Default pagination behavior\" %}\nBy default, results are limited to the **first 25 items**.\nYou can change this through [pagination](/docs/products/databases/pagination).\n{% /info %}\n\n{% multicode %}\n\n```client-web\nimport { Client, Query, TablesDB } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nconst tablesDB = new TablesDB(client);\n\ntablesDB.listRows({\n databaseId: '',\n tableId: '',\n queries: [\n Query.equal('title', ['Avatar', 'Lord of the Rings']),\n Query.greaterThan('year', 1999)\n ]\n});\n```\n```server-nodejs\nconst sdk = require('node-appwrite');\n\nconst client = new sdk.Client();\n\nconst tablesDB = new sdk.TablesDB(client);\n\nclient\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('')\n;\n\nconst promise = tablesDB.listRows({\n databaseId: '',\n tableId: '',\n queries: [\n sdk.Query.equal('title', ['Avatar', 'Lord of the Rings']),\n sdk.Query.greaterThan('year', 1999)\n ]\n});\n\npromise.then(function (response) {\n console.log(response);\n}, function (error) {\n console.log(error);\n});\n```\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nvoid main() async {\n final client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\n final tablesDB = TablesDB(client);\n\n try {\n final rows = await tablesDB.listRows(\n '',\n '',\n [\n Query.equal('title', ['Avatar', 'Lord of the Rings']),\n Query.greaterThan('year', 1999)\n ]\n );\n } on AppwriteException catch(e) {\n print(e);\n }\n}\n```\n```client-apple\nimport Appwrite\nimport AppwriteModels\n\nfunc main() async throws {\n let client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\n let tablesDB = TablesDB(client)\n\n do {\n let rows = try await tablesDB.listRows(\n databaseId: \"\",\n tableId: \"\",\n queries: [\n Query.equal(\"title\", value: [\"Avatar\", \"Lord of the Rings\"]),\n Query.greaterThan(\"year\", value: 1999)\n ]\n )\n } catch {\n print(error.localizedDescription)\n }\n}\n```\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.Query\nimport io.appwrite.services.TablesDB\n\nsuspend fun main() {\n val client = Client(applicationContext)\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\n val tablesDB = TablesDB(client)\n\n try {\n val rows = tablesDB.listRows(\n databaseId = \"\",\n tableId = \"\",\n queries = listOf(\n Query.equal(\"title\", listOf(\"Avatar\", \"Lord of the Rings\")),\n Query.greaterThan(\"year\", 1999)\n )\n )\n } catch (e: AppwriteException) {\n Log.e(\"Appwrite\", e.message)\n }\n}\n```\n```server-go\npackage main\n\nimport (\n \"fmt\"\n \"log\"\n\n \"github.com/appwrite/sdk-for-go/appwrite\"\n \"github.com/appwrite/sdk-for-go/query\"\n)\n\nfunc main() {\n client := appwrite.NewClient(\n appwrite.WithEndpoint(\"https://.cloud.appwrite.io/v1\"),\n appwrite.WithProject(\"\"),\n appwrite.WithKey(\"\"),\n )\n\n tablesDB := appwrite.NewTablesDB(client)\n\n rows, err := tablesDB.ListRows(\n \"\",\n \"\",\n tablesDB.WithListRowsQueries([]string{\n query.Equal(\"title\", []string{\"Avatar\", \"Lord of the Rings\"}),\n query.GreaterThan(\"year\", 1999),\n }),\n )\n if err != nil {\n log.Fatal(err)\n }\n\n fmt.Printf(\"Rows: %+v\\n\", rows)\n}\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::tables_db::TablesDB;\nuse appwrite::query::Query;\nuse serde_json::Value;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box> {\n let client = Client::new()\n .set_endpoint(\"https://.cloud.appwrite.io/v1\")\n .set_project(\"\")\n .set_key(\"\");\n\n let tables_db = TablesDB::new(&client);\n\n let rows = tables_db.list_rows(\n \"\",\n \"\",\n Some(vec![\n Query::equal(\"title\", Value::Array(vec![\n Value::String(\"Avatar\".to_string()),\n Value::String(\"Lord of the Rings\".to_string()),\n ])).to_string(),\n Query::greater_than(\"year\", 1999).to_string(),\n ]),\n None,\n None,\n None,\n ).await?;\n\n println!(\"{:?}\", rows);\n Ok(())\n}\n```\n```server-php\nsetEndpoint('https://.cloud.appwrite.io/v1')\n ->setProject('')\n ->setKey('')\n;\n\n$tablesDB = new TablesDB($client);\n\n$result = $tables->listRows(\n '',\n '',\n [\n Query::equal('title', ['Avatar', 'Lord of the Rings']),\n Query::greaterThan('year', 1999)\n ]\n);\n```\n```server-python\nfrom appwrite.client import Client\nfrom appwrite.query import Query\nfrom appwrite.services.tables_db import TablesDB\n\nclient = Client()\n\n(client\n .set_endpoint('https://.cloud.appwrite.io/v1')\n .set_project('')\n .set_key('')\n)\n\ntablesDB = TablesDB(client)\n\nresult = tablesDB.list_rows(\n '',\n '',\n [\n Query.equal('title', ['Avatar', 'Lord of the Rings']),\n Query.greater_than('year', 1999)\n ]\n)\n```\n```graphql\nquery {\n tablesListRows(\n databaseId: \"\",\n tableId: \"\"\n queries: [\n \"{\\\"method\\\":\\\"equal\\\",\\\"column\\\":\\\"title\\\",\\\"values\\\":[\\\"Avatar\\\",\\\"Lord of the Rings\\\"]}\",\n \"{\\\"method\\\":\\\"greaterThan\\\",\\\"column\\\":\\\"year\\\",\\\"values\\\":[1999]}\"\n ]\n ) {\n total\n rows {\n _id\n data\n }\n }\n}\n```\n```http\nGET /v1/tablesdb//tables//rows?queries[]=%7B%22method%22%3A%22equal%22%2C%22column%22%3A%22title%22%2C%22values%22%3A%5B%22Avatar%22%2C%22Lord%20of%20the%20Rings%22%5D%7D&queries[]=%7B%22method%22%3A%22greaterThan%22%2C%22column%22%3A%22year%22%2C%22values%22%3A%5B1999%5D%7D HTTP/1.1\nContent-Type: application/json\nX-Appwrite-Project: \n```\n{% /multicode %}\n\n# Query operators {% #query-operators %}\n\n## Select {% #select %}\n\nThe `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.\n\n{% multicode %}\n```client-web\nQuery.select([\"name\", \"title\"])\n```\n```client-flutter\nQuery.select([\"name\", \"title\"])\n```\n```server-python\nQuery.select([\"name\", \"title\"])\n```\n```server-ruby\nQuery.select([\"name\", \"title\"])\n```\n```server-deno\nQuery.select([\"name\", \"title\"])\n```\n```server-php\nQuery::select([\"name\", \"title\"])\n```\n```client-apple\nQuery.select([\"name\", \"title\"])\n```\n```server-go\nquery.Select([]string{\"name\", \"title\"})\n```\n```server-rust\nQuery::select(vec![\"name\", \"title\"]).to_string()\n```\n```http\n{\"method\":\"select\",\"values\":[\"name\",\"title\"]}\n```\n{% /multicode %}\n\n### Select relationship data {% #relationship-select %}\n\nWith [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.\n\n#### Get rows without relationships\nBy default, rows return only their own fields:\n\n{% multicode %}\n```client-web\nconst doc = await tablesDB.getRow({\n databaseId: '',\n tableId: '',\n rowId: '',\n queries: [Query.select(['name', 'age'])]\n});\n```\n```client-flutter\nfinal doc = await tablesDB.getRow(\n databaseId: '',\n tableId: '',\n rowId: '',\n queries: [Query.select([\"name\", \"age\"])]\n);\n```\n```server-python\ndoc = tablesDB.get_row(\n '', '', '',\n [Query.select([\"name\", \"age\"])]\n)\n```\n```server-ruby\ndoc = tablesDB.get_row(\n '', '', '',\n [Query.select([\"name\", \"age\"])]\n)\n```\n```server-nodejs\nconst doc = await tablesDB.getRow({\n databaseId: '',\n tableId: '',\n rowId: '',\n queries: [Query.select(['name', 'age'])]\n});\n```\n```server-php\n$doc = $tablesDB->getRow(\n '', '', '',\n [Query::select([\"name\", \"age\"])]\n);\n```\n```server-rust\nlet doc = tables_db.get_row(\n \"\",\n \"\",\n \"\",\n Some(vec![Query::select(vec![\"name\", \"age\"]).to_string()]),\n None,\n).await?;\n```\n```client-apple\nlet doc = try await tablesDB.getRow(\n databaseId: \"\",\n tableId: \"\",\n rowId: \"\",\n queries: [Query.select([\"name\", \"age\"])]\n)\n```\n```http\nGET /v1/tablesdb//tables//rows/?queries[]=%7B%22method%22%3A%22select%22%2C%22values%22%3A%5B%22name%22%2C%22age%22%5D%7D HTTP/1.1\nContent-Type: application/json\nX-Appwrite-Project: \n```\n{% /multicode %}\n\n#### Load all relationship data\nUse the `*` wildcard to load all fields from related rows:\n\n{% multicode %}\n```client-web\nconst doc = await tablesDB.getRow({\n databaseId: '',\n tableId: '',\n rowId: '',\n queries: [Query.select(['*', 'reviews.*'])]\n});\n```\n```client-flutter\nfinal doc = await tablesDB.getRow(\n databaseId: '',\n tableId: '',\n rowId: '',\n queries: [Query.select([\"*\", \"reviews.*\"])]\n);\n```\n```server-python\ndoc = tablesDB.get_row(\n '', '', '',\n [Query.select([\"*\", \"reviews.*\"])]\n)\n```\n```server-ruby\ndoc = tablesDB.get_row(\n '', '', '',\n [Query.select([\"*\", \"reviews.*\"])]\n)\n```\n```server-nodejs\nconst doc = await tablesDB.getRow({\n databaseId: '',\n tableId: '',\n rowId: '',\n queries: [Query.select([\"*\", \"reviews.*\"])]\n});\n```\n```server-php\n$doc = $tablesDB->getRow(\n '', '', '',\n [Query::select([\"*\", \"reviews.*\"])]\n);\n```\n```server-rust\nlet doc = tables_db.get_row(\n \"\",\n \"\",\n \"\",\n Some(vec![Query::select(vec![\"*\", \"reviews.*\"]).to_string()]),\n None,\n).await?;\n```\n```client-apple\nlet doc = try await tablesDB.getRow(\n databaseId: \"\",\n tableId: \"\",\n rowId: \"\",\n queries: [Query.select([\"*\", \"reviews.*\"])]\n)\n```\n```http\nGET /v1/tablesdb//tables//rows/?queries[]=%7B%22method%22%3A%22select%22%2C%22values%22%3A%5B%22%2A%22%2C%22reviews.%2A%22%5D%7D HTTP/1.1\nContent-Type: application/json\nX-Appwrite-Project: \n{\"method\":\"select\",\"values\":[\"*\",\"reviews.*\"]}\n```\n{% /multicode %}\n\n#### Select specific relationship fields\nFor precise control, select only specific fields from related rows:\n\n{% multicode %}\n```client-web\nconst doc = await tablesDB.getRow({\n databaseId: '',\n tableId: '',\n rowId: '',\n queries: [Query.select(['name', 'age', 'reviews.author', 'reviews.rating'])]\n});\n```\n```client-flutter\nfinal doc = await tablesDB.getRow(\n databaseId: '',\n tableId: '',\n rowId: '',\n queries: [Query.select([\"name\", \"age\", \"reviews.author\", \"reviews.rating\"])]\n);\n```\n```server-python\ndoc = tablesDB.get_row(\n '', '', '',\n [Query.select([\"name\", \"age\", \"reviews.author\", \"reviews.rating\"])]\n)\n```\n```server-ruby\ndoc = tablesDB.get_row(\n '', '', '',\n [Query.select([\"name\", \"age\", \"reviews.author\", \"reviews.rating\"])]\n)\n```\n```server-nodejs\nconst doc = await tablesDB.getRow({\n databaseId: '',\n tableId: '',\n rowId: '',\n queries: [Query.select([\"name\", \"age\", \"reviews.author\", \"reviews.rating\"])]\n});\n// Result: { name: \"John\", age: 30, reviews: [{ author: \"...\", rating: 5 }] }\n```\n```server-php\n$doc = $tablesDB->getRow(\n '', '', '',\n [Query::select([\"name\", \"age\", \"reviews.author\", \"reviews.rating\"])]\n);\n```\n```server-rust\nlet doc = tables_db.get_row(\n \"\",\n \"\",\n \"\",\n Some(vec![Query::select(vec![\"name\", \"age\", \"reviews.author\", \"reviews.rating\"]).to_string()]),\n None,\n).await?;\n```\n```client-apple\nlet doc = try await tablesDB.getRow(\n databaseId: \"\",\n tableId: \"\",\n rowId: \"\",\n queries: [Query.select([\"name\", \"age\", \"reviews.author\", \"reviews.rating\"])]\n)\n```\n```http\n# Load specific fields from main and related rows\n{\"method\":\"select\",\"values\":[\"name\",\"age\",\"reviews.author\",\"reviews.rating\"]}\n```\n{% /multicode %}\n\n#### Load nested relationships\nYou can also load relationships of relationships:\n\n{% multicode %}\n```client-web\nQuery.select([\"*\", \"reviews.*\", \"reviews.author.*\"])\n```\n```client-flutter\nQuery.select([\"*\", \"reviews.*\", \"reviews.author.*\"])\n```\n```server-python\nQuery.select([\"*\", \"reviews.*\", \"reviews.author.*\"])\n```\n```server-ruby\nQuery.select([\"*\", \"reviews.*\", \"reviews.author.*\"])\n```\n```server-nodejs\nQuery.select([\"*\", \"reviews.*\", \"reviews.author.*\"])\n```\n```server-php\nQuery::select([\"*\", \"reviews.*\", \"reviews.author.*\"])\n```\n```server-rust\nQuery::select(vec![\"*\", \"reviews.*\", \"reviews.author.*\"]).to_string()\n```\n```client-apple\nQuery.select([\"*\", \"reviews.*\", \"reviews.author.*\"])\n```\n```http\n{\"method\":\"select\",\"values\":[\"*\",\"reviews.*\",\"reviews.author.*\"]}\n```\n{% /multicode %}\n\n### Use selection patterns {% #select-patterns %}\n\n| Pattern | Description | Use case |\n|---------|-------------|----------|\n| `[\"field1\", \"field2\"]` | Specific columns only | Minimize response size |\n| `[\"*\"]` | All row columns | Get complete row data |\n| `[\"*\", \"relationName.*\"]` | Row + all relationship fields | Load row with complete related data |\n| `[\"field1\", \"relationName.field2\"]` | Specific fields from row and relationships | Precise data loading |\n| `[\"*\", \"relationName.field1\", \"relationName.field2\"]` | All row fields + specific relationship fields | Partial relationship loading |\n| `[\"relationName.*\", \"relationName.nestedRelation.*\"]` | Nested relationship loading | Load relationships of relationships |\n\n### Optimize performance {% #select-performance %}\n\n**Optimize response size** - Only select the fields you actually need. Smaller responses are faster to transfer and parse.\n\n**Control relationship loading** - Related rows are not loaded by default. Use explicit selection to load only the relationships you need.\n\n**Reduce database load** - Selecting fewer fields reduces database processing time, especially for large rows.\n\n{% info title=\"Related rows\" %}\nBy default, relationship columns contain only row IDs.\nTo load the actual related row data, you must explicitly include relationship fields in your select query.\nLearn more about [relationship performance optimization](/docs/products/databases/relationships#performance-loading).\n{% /info %}\n\n## Comparison operators {% #comparison %}\n\n### Equal {% #equal %}\n\nReturns row if column is equal to any value in the provided array. Also supported for spatial types.\n\n{% multicode %}\n```client-web\nQuery.equal(\"title\", [\"Iron Man\"])\n```\n```client-flutter\nQuery.equal(\"title\", [\"Iron Man\"])\n```\n```server-python\nQuery.equal(\"title\", [\"Iron Man\"])\n```\n```server-ruby\nQuery.equal(\"title\", [\"Iron Man\"])\n```\n```server-deno\nQuery.equal(\"title\", [\"Iron Man\"])\n```\n```server-php\nQuery::equal(\"title\", [\"Iron Man\"])\n```\n```client-apple\nQuery.equal(\"title\", value: [\"Iron Man\"])\n```\n```server-go\nquery.Equal(\"title\", []string{\"Iron Man\"})\n```\n```server-rust\nQuery::equal(\"title\", Value::Array(vec![Value::String(\"Iron Man\".to_string())])).to_string()\n```\n```http\n{\"method\":\"equal\",\"column\":\"title\",\"values\":[\"Iron Man\"]}\n```\n{% /multicode %}\n\n### Not equal {% #not-equal %}\n\nReturns row if column is not equal to any value in the provided array. Also supported for spatial types.\n\n{% multicode %}\n```client-web\nQuery.notEqual(\"title\", \"Iron Man\")\n```\n```client-flutter\nQuery.notEqual(\"title\", \"Iron Man\")\n```\n```server-python\nQuery.not_equal(\"title\", \"Iron Man\")\n```\n```server-ruby\nQuery.not_equal(\"title\", \"Iron Man\")\n```\n```server-deno\nQuery.notEqual(\"title\", \"Iron Man\")\n```\n```server-php\nQuery::notEqual(\"title\", \"Iron Man\")\n```\n```client-apple\nQuery.notEqual(\"title\", value: \"Iron Man\")\n```\n```server-go\nquery.NotEqual(\"title\", \"Iron Man\")\n```\n```server-rust\nQuery::not_equal(\"title\", \"Iron Man\").to_string()\n```\n```http\n{\"method\":\"notEqual\",\"column\":\"title\",\"values\":\"Iron Man\"}\n```\n{% /multicode %}\n\n### Less than {% #less-than %}\n\nReturns row if column is less than the provided value.\n\n{% multicode %}\n```client-web\nQuery.lessThan(\"score\", 10)\n```\n```client-flutter\nQuery.lessThan(\"score\", 10)\n```\n```server-python\nQuery.less_than(\"score\", 10)\n```\n```server-ruby\nQuery.less_than(\"score\", 10)\n```\n```server-deno\nQuery.lessThan(\"score\", 10)\n```\n```server-php\nQuery::lessThan(\"score\", 10)\n```\n```client-apple\nQuery.lessThan(\"score\", value: 10)\n```\n```server-go\nquery.LessThan(\"score\", 10)\n```\n```server-rust\nQuery::less_than(\"score\", 10).to_string()\n```\n```http\n{\"method\":\"lessThan\",\"column\":\"score\",\"values\":[10]}\n```\n{% /multicode %}\n\n### Less than or equal {% #less-than-equal %}\n\nReturns row if column is less than or equal to the provided value.\n\n{% multicode %}\n```client-web\nQuery.lessThanEqual(\"score\", 10)\n```\n```client-flutter\nQuery.lessThanEqual(\"score\", 10)\n```\n```server-python\nQuery.less_than_equal(\"score\", 10)\n```\n```server-ruby\nQuery.less_than_equal(\"score\", 10)\n```\n```server-deno\nQuery.lessThanEqual(\"score\", 10)\n```\n```server-php\nQuery::lessThanEqual(\"score\", 10)\n```\n```client-apple\nQuery.lessThanEqual(\"score\", value: 10)\n```\n```server-go\nquery.LessThanEqual(\"score\", 10)\n```\n```server-rust\nQuery::less_than_equal(\"score\", 10).to_string()\n```\n```http\n{\"method\":\"lessThanEqual\",\"column\":\"score\",\"values\":[10]}\n```\n{% /multicode %}\n\n### Greater than {% #greater-than %}\n\nReturns row if column is greater than the provided value.\n\n{% multicode %}\n```client-web\nQuery.greaterThan(\"score\", 10)\n```\n```client-flutter\nQuery.greaterThan(\"score\", 10)\n```\n```server-python\nQuery.greater_than(\"score\", 10)\n```\n```server-ruby\nQuery.greater_than(\"score\", 10)\n```\n```server-deno\nQuery.greaterThan(\"score\", 10)\n```\n```server-php\nQuery::greaterThan(\"score\", 10)\n```\n```client-apple\nQuery.greaterThan(\"score\", value: 10)\n```\n```server-go\nquery.GreaterThan(\"score\", 10)\n```\n```server-rust\nQuery::greater_than(\"score\", 10).to_string()\n```\n```http\n{\"method\":\"greaterThan\",\"column\":\"score\",\"values\":[10]}\n```\n{% /multicode %}\n\n### Greater than or equal {% #greater-than-equal %}\n\nReturns row if column is greater than or equal to the provided value.\n\n{% multicode %}\n```client-web\nQuery.greaterThanEqual(\"score\", 10)\n```\n```client-flutter\nQuery.greaterThanEqual(\"score\", 10)\n```\n```server-python\nQuery.greater_than_equal(\"score\", 10)\n```\n```server-ruby\nQuery.greater_than_equal(\"score\", 10)\n```\n```server-deno\nQuery.greaterThanEqual(\"score\", 10)\n```\n```server-php\nQuery::greaterThanEqual(\"score\", 10)\n```\n```client-apple\nQuery.greaterThanEqual(\"score\", value: 10)\n```\n```server-go\nquery.GreaterThanEqual(\"score\", 10)\n```\n```server-rust\nQuery::greater_than_equal(\"score\", 10).to_string()\n```\n```http\n{\"method\":\"greaterThanEqual\",\"column\":\"score\",\"values\":[10]}\n```\n{% /multicode %}\n\n### Between {% #between %}\n\nReturns row if column value falls between the two values. The boundary values are inclusive and can be strings or numbers.\n\n{% multicode %}\n```client-web\nQuery.between(\"price\", 5, 10)\n```\n```client-flutter\nQuery.between(\"price\", 5, 10)\n```\n```server-python\nQuery.between(\"price\", 5, 10)\n```\n```server-ruby\nQuery.between(\"price\", 5, 10)\n```\n```server-deno\nQuery.between(\"price\", 5, 10)\n```\n```server-php\nQuery::between(\"price\", 5, 10)\n```\n```client-apple\nQuery.between(\"price\", start: 5, end: 10)\n```\n```server-go\nquery.Between(\"price\", 5, 10)\n```\n```server-rust\nQuery::between(\"price\", 5, 10).to_string()\n```\n```http\n{\"method\":\"between\",\"column\":\"price\",\"values\":[5,10]}\n```\n{% /multicode %}\n\n### Not between {% #not-between %}\n\nReturns rows if the column value is outside the range defined by the two values (strictly less than start OR strictly greater than end).\nWorks with strings or numbers. Boundary values are excluded.\n\n{% multicode %}\n```client-web\nQuery.notBetween(\"price\", 5, 10)\n```\n```client-flutter\nQuery.notBetween(\"price\", 5, 10)\n```\n```client-apple\nQuery.notBetween(\"price\", start: 5, end: 10)\n```\n```client-android-kotlin\nQuery.notBetween(\"price\", 5, 10)\n```\n```client-android-java\nQuery.notBetween(\"price\", 5, 10)\n```\n```server-python\nQuery.not_between(\"price\", 5, 10)\n```\n```server-ruby\nQuery.not_between(\"price\", 5, 10)\n```\n```server-deno\nQuery.notBetween(\"price\", 5, 10)\n```\n```server-nodejs\nQuery.notBetween(\"price\", 5, 10)\n```\n```server-php\nQuery::notBetween(\"price\", 5, 10)\n```\n```server-swift\nQuery.notBetween(\"price\", start: 5, end: 10)\n```\n```server-rust\nQuery::not_between(\"price\", 5, 10).to_string()\n```\n```http\n{\"method\":\"notBetween\",\"column\":\"price\",\"values\":[5,10]}\n```\n{% /multicode %}\n\n## Null checks {% #null-checks %}\n\n### Is null {% #is-null %}\n\nReturns rows where column value is null.\n\n{% multicode %}\n```client-web\nQuery.isNull(\"name\")\n```\n```client-flutter\nQuery.isNull(\"name\")\n```\n```server-python\nQuery.is_null(\"name\")\n```\n```server-ruby\nQuery.is_null(\"name\")\n```\n```server-deno\nQuery.isNull(\"name\")\n```\n```server-php\nQuery::isNull(\"name\")\n```\n```client-apple\nQuery.isNull(\"name\")\n```\n```server-go\nquery.IsNull(\"name\")\n```\n```server-rust\nQuery::is_null(\"name\").to_string()\n```\n```http\n{\"method\":\"isNull\",\"column\":\"name\"}\n```\n{% /multicode %}\n\n### Is not null {% #is-not-null %}\n\nReturns rows where column value is **not** null.\n\n{% multicode %}\n```client-web\nQuery.isNotNull(\"name\")\n```\n```client-flutter\nQuery.isNotNull(\"name\")\n```\n```server-python\nQuery.is_not_null(\"name\")\n```\n```server-ruby\nQuery.is_not_null(\"name\")\n```\n```server-deno\nQuery.isNotNull(\"name\")\n```\n```server-php\nQuery::isNotNull(\"name\")\n```\n```client-apple\nQuery.isNotNull(\"name\")\n```\n```server-go\nquery.IsNotNull(\"name\")\n```\n```server-rust\nQuery::is_not_null(\"name\").to_string()\n```\n```http\n{\"method\":\"isNotNull\",\"column\":\"name\"}\n```\n{% /multicode %}\n\n## String operations {% #string-operations %}\n\n### Starts with {% #starts-with %}\n\nReturns rows if a string column starts with a substring.\n\n{% multicode %}\n```client-web\nQuery.startsWith(\"name\", \"Once upon a time\")\n```\n```client-flutter\nQuery.startsWith(\"name\", \"Once upon a time\")\n```\n```server-python\nQuery.starts_with(\"name\", \"Once upon a time\")\n```\n```server-ruby\nQuery.starts_with(\"name\", \"Once upon a time\")\n```\n```server-deno\nQuery.startsWith(\"name\", \"Once upon a time\")\n```\n```server-php\nQuery::startsWith(\"name\", \"Once upon a time\")\n```\n```client-apple\nQuery.startsWith(\"name\", value: \"Once upon a time\")\n```\n```server-go\nquery.StartsWith(\"name\", \"Once upon a time\")\n```\n```server-rust\nQuery::starts_with(\"name\", \"Once upon a time\").to_string()\n```\n```http\n{\"method\":\"startsWith\",\"column\":\"name\",\"values\":[\"Once upon a time\"]}\n```\n{% /multicode %}\n\n### Not starts with {% #not-starts-with %}\n\nReturns rows if a string column does not start with a substring.\n\n{% multicode %}\n```client-web\nQuery.notStartsWith(\"name\", \"Once upon a time\")\n```\n```client-flutter\nQuery.notStartsWith(\"name\", \"Once upon a time\")\n```\n```client-apple\nQuery.notStartsWith(\"name\", value: \"Once upon a time\")\n```\n```client-android-kotlin\nQuery.notStartsWith(\"name\", \"Once upon a time\")\n```\n```client-android-java\nQuery.notStartsWith(\"name\", \"Once upon a time\")\n```\n```server-python\nQuery.not_starts_with(\"name\", \"Once upon a time\")\n```\n```server-ruby\nQuery.not_starts_with(\"name\", \"Once upon a time\")\n```\n```server-deno\nQuery.notStartsWith(\"name\", \"Once upon a time\")\n```\n```server-nodejs\nQuery.notStartsWith(\"name\", \"Once upon a time\")\n```\n```server-php\nQuery::notStartsWith(\"name\", \"Once upon a time\")\n```\n```server-swift\nQuery.notStartsWith(\"name\", value: \"Once upon a time\")\n```\n```server-rust\nQuery::not_starts_with(\"name\", \"Once upon a time\").to_string()\n```\n```http\n{\"method\":\"notStartsWith\",\"column\":\"name\",\"values\":[\"Once upon a time\"]}\n```\n{% /multicode %}\n\n### Ends with {% #ends-with %}\n\nReturns rows if a string column ends with a substring.\n\n{% multicode %}\n```client-web\nQuery.endsWith(\"name\", \"happily ever after.\")\n```\n```client-flutter\nQuery.endsWith(\"name\", \"happily ever after.\")\n```\n```server-python\nQuery.ends_with(\"name\", \"happily ever after.\")\n```\n```server-ruby\nQuery.ends_with(\"name\", \"happily ever after.\")\n```\n```server-deno\nQuery.endsWith(\"name\", \"happily ever after.\")\n```\n```server-php\nQuery::endsWith(\"name\", \"happily ever after.\")\n```\n```client-apple\nQuery.endsWith(\"name\", value: \"happily ever after.\")\n```\n```server-go\nquery.EndsWith(\"name\", \"happily ever after.\")\n```\n```server-rust\nQuery::ends_with(\"name\", \"happily ever after.\").to_string()\n```\n```http\n{\"method\":\"endsWith\",\"column\":\"name\",\"values\":[\"happily ever after.\"]}\n```\n{% /multicode %}\n\n### Not ends with {% #not-ends-with %}\n\nReturns rows if a string column does not end with a substring.\n\n{% multicode %}\n```client-web\nQuery.notEndsWith(\"name\", \"happily ever after.\")\n```\n```client-flutter\nQuery.notEndsWith(\"name\", \"happily ever after.\")\n```\n```client-apple\nQuery.notEndsWith(\"name\", value: \"happily ever after.\")\n```\n```client-android-kotlin\nQuery.notEndsWith(\"name\", \"happily ever after.\")\n```\n```client-android-java\nQuery.notEndsWith(\"name\", \"happily ever after.\")\n```\n```server-python\nQuery.not_ends_with(\"name\", \"happily ever after.\")\n```\n```server-ruby\nQuery.not_ends_with(\"name\", \"happily ever after.\")\n```\n```server-deno\nQuery.notEndsWith(\"name\", \"happily ever after.\")\n```\n```server-nodejs\nQuery.notEndsWith(\"name\", \"happily ever after.\")\n```\n```server-php\nQuery::notEndsWith(\"name\", \"happily ever after.\")\n```\n```server-swift\nQuery.notEndsWith(\"name\", value: \"happily ever after.\")\n```\n```server-rust\nQuery::not_ends_with(\"name\", \"happily ever after.\").to_string()\n```\n```http\n{\"method\":\"notEndsWith\",\"column\":\"name\",\"values\":[\"happily ever after.\"]}\n```\n{% /multicode %}\n\n### Contains {% #contains %}\n\nReturns rows if the array column contains the specified elements or if a string column contains the specified substring. Also supported for spatial types.\n\n{% multicode %}\n```client-web\n// For arrays\nQuery.contains(\"ingredients\", ['apple', 'banana'])\n\n// For strings\nQuery.contains(\"name\", \"Tom\")\n```\n```client-flutter\n// For arrays\nQuery.contains(\"ingredients\", ['apple', 'banana'])\n\n// For strings\nQuery.contains(\"name\", \"Tom\")\n```\n```server-python\n# For arrays\nQuery.contains(\"ingredients\", ['apple', 'banana'])\n\n# For strings\nQuery.contains(\"name\", \"Tom\")\n```\n```server-ruby\n# For arrays\nQuery.contains(\"ingredients\", ['apple', 'banana'])\n\n# For strings\nQuery.contains(\"name\", \"Tom\")\n```\n```server-deno\n// For arrays\nQuery.contains(\"ingredients\", ['apple', 'banana'])\n\n// For strings\nQuery.contains(\"name\", \"Tom\")\n```\n```server-php\n// For arrays\nQuery::contains(\"ingredients\", ['apple', 'banana'])\n\n// For strings\nQuery::contains(\"name\", \"Tom\")\n```\n```client-apple\n// For arrays\nQuery.contains(\"ingredients\", value: [\"apple\", \"banana\"])\n\n// For strings\nQuery.contains(\"name\", value: \"Tom\")\n```server-go\n// For arrays\nquery.Contains(\"ingredients\", []string{\"apple\", \"banana\"})\n\n// For strings\nquery.Contains(\"name\", \"Tom\")\n```\n```server-rust\n// For arrays\nQuery::contains(\"ingredients\", Value::Array(vec![\n Value::String(\"apple\".to_string()),\n Value::String(\"banana\".to_string()),\n])).to_string()\n\n// For strings\nQuery::contains(\"name\", \"Tom\").to_string()\n```\n```http\n# For arrays\n{\"method\":\"contains\",\"column\":\"ingredients\",\"values\":[\"apple\",\"banana\"]}\n\n# For strings\n{\"method\":\"contains\",\"column\":\"name\",\"values\":[\"Tom\"]}\n```\n{% /multicode %}\n\n### Not contains {% #not-contains %}\n\nReturns rows if the array column does not contain the specified\nelements, or if a string column does not contain the specified\nsubstring. Also supported for spatial types.\n\n{% multicode %}\n```client-web\n// For arrays\nQuery.notContains(\"ingredients\", ['apple', 'banana'])\n\n// For strings\nQuery.notContains(\"name\", \"Tom\")\n```\n```client-flutter\n// For arrays\nQuery.notContains(\"ingredients\", ['apple', 'banana'])\n\n// For strings\nQuery.notContains(\"name\", \"Tom\")\n```\n```client-react-native\n// For arrays\nQuery.notContains(\"ingredients\", ['apple', 'banana'])\n\n// For strings\nQuery.notContains(\"name\", \"Tom\")\n```\n```client-apple\n// For arrays\nQuery.notContains(\"ingredients\", value: ['apple', 'banana'])\n\n// For strings\nQuery.notContains(\"name\", value: \"Tom\")\n```\n```client-android-kotlin\n// For arrays\nQuery.notContains(\"ingredients\", ['apple', 'banana'])\n\n// For strings\nQuery.notContains(\"name\", \"Tom\")\n```\n```client-android-java\n// For arrays\nQuery.notContains(\"ingredients\", Arrays.asList(\"apple\", \"banana\"))\n\n// For strings\nQuery.notContains(\"name\", \"Tom\")\n```\n```server-python\n# For arrays\nQuery.not_contains(\"ingredients\", ['apple', 'banana'])\n\n# For strings\nQuery.not_contains(\"name\", \"Tom\")\n```\n```server-ruby\n# For arrays\nQuery.not_contains(\"ingredients\", ['apple', 'banana'])\n\n# For strings\nQuery.not_contains(\"name\", \"Tom\")\n```\n```server-deno\n// For arrays\nQuery.notContains(\"ingredients\", ['apple', 'banana'])\n\n// For strings\nQuery.notContains(\"name\", \"Tom\")\n```\n```server-nodejs\n// For arrays\nQuery.notContains(\"ingredients\", ['apple', 'banana'])\n\n// For strings\nQuery.notContains(\"name\", \"Tom\")\n```\n```server-php\n// For arrays\nQuery::notContains(\"ingredients\", ['apple', 'banana'])\n\n// For strings\nQuery::notContains(\"name\", \"Tom\")\n```\n```server-dotnet\n// For arrays\nQuery.NotContains(\"ingredients\", new List { \"apple\", \"banana\" })\n\n// For strings\nQuery.NotContains(\"name\", \"Tom\")\n```\n```server-go\n// For arrays\nquery.NotContains(\"ingredients\", []string{\"apple\", \"banana\"})\n\n// For strings\nquery.NotContains(\"name\", \"Tom\")\n```server-dart\n// For arrays\nQuery.notContains(\"ingredients\", ['apple', 'banana'])\n\n// For strings\nQuery.notContains(\"name\", \"Tom\")\n```\n```server-swift\n// For arrays\nQuery.notContains(\"ingredients\", value: ['apple', 'banana'])\n\n// For strings\nQuery.notContains(\"name\", value: \"Tom\")\n```\n```server-kotlin\n// For arrays\nQuery.notContains(\"ingredients\", listOf(\"apple\", \"banana\"))\n\n// For strings\nQuery.notContains(\"name\", \"Tom\")\n```\n```server-rust\n// For arrays\nQuery::not_contains(\"ingredients\", Value::Array(vec![\n Value::String(\"apple\".to_string()),\n Value::String(\"banana\".to_string()),\n])).to_string()\n\n// For strings\nQuery::not_contains(\"name\", \"Tom\").to_string()\n```\n```http\n# For arrays\n{\"method\":\"notContains\",\"column\":\"ingredients\",\"values\":[\"apple\",\"banana\"]}\n\n# For strings\n{\"method\":\"notContains\",\"column\":\"name\",\"values\":[\"Tom\"]}\n```\n{% /multicode %}\n\n### Search {% #search %}\n\nSearches string 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.\n\n{% info title=\"Searching values with hyphens\" %}\nThe 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\"')`.\n{% /info %}\n\n{% multicode %}\n```client-web\nQuery.search(\"text\", \"key words\")\n```\n```client-flutter\nQuery.search(\"text\", \"key words\")\n```\n```server-python\nQuery.search(\"text\", \"key words\")\n```\n```server-ruby\nQuery.search(\"text\", \"key words\")\n```\n```server-deno\nQuery.search(\"text\", \"key words\")\n```\n```server-php\nQuery::search(\"text\", \"key words\")\n```\n```client-apple\nQuery.search(\"text\", value: \"key words\")\n```\n```server-go\nquery.Search(\"text\", \"key words\")\n```\n```server-rust\nQuery::search(\"text\", \"key words\").to_string()\n```\n```http\n{\"method\":\"search\",\"column\":\"text\",\"values\":[\"key words\"]}\n```\n{% /multicode %}\n\n### Not search {% #not-search %}\n\nReturns rows if a string column does not match the full-text search\nquery. Requires a [full-text index](/docs/products/databases/tables#indexes)\non queried columns. The search string must be at least **3 characters** to perform a search.\n\n{% info title=\"Searching values with hyphens\" %}\nThe 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(attribute, '\"SWT-2621-44\"')`.\n{% /info %}\n\n{% multicode %}\n```client-web\nQuery.notSearch(\"text\", \"key words\")\n```\n```client-flutter\nQuery.notSearch(\"text\", \"key words\")\n```\n```client-apple\nQuery.notSearch(\"text\", value: \"key words\")\n```\n```client-android-kotlin\nQuery.notSearch(\"text\", \"key words\")\n```\n```client-android-java\nQuery.notSearch(\"text\", \"key words\")\n```\n```server-python\nQuery.not_search(\"text\", \"key words\")\n```\n```server-ruby\nQuery.not_search(\"text\", \"key words\")\n```\n```server-deno\nQuery.notSearch(\"text\", \"key words\")\n```\n```server-nodejs\nQuery.notSearch(\"text\", \"key words\")\n```\n```server-php\nQuery::notSearch(\"text\", \"key words\")\n```\n```server-swift\nQuery.notSearch(\"text\", value: \"key words\")\n```\n```server-rust\nQuery::not_search(\"text\", \"key words\").to_string()\n```\n```http\n{\"method\":\"notSearch\",\"column\":\"text\",\"values\":[\"key words\"]}\n```\n{% /multicode %}\n\n## Logical operators {% #logical-operators %}\n\n### AND {% #and %}\n\nReturns row if it matches all of the nested sub-queries in the array passed in.\n\n{% multicode %}\n```client-web\nQuery.and([\n Query.lessThan(\"size\", 10),\n Query.greaterThan(\"size\", 5)\n])\n```\n```client-flutter\nQuery.and([\n Query.lessThan(\"size\", 10),\n Query.greaterThan(\"size\", 5)\n])\n```\n```server-python\nQuery.and_queries([\n Query.less_than(\"size\", 10),\n Query.greater_than(\"size\", 5)\n])\n```\n```server-ruby\nQuery.and([\n Query.less_than(\"size\", 10),\n Query.greater_than(\"size\", 5)\n])\n```\n```server-deno\nQuery.and([\n Query.lessThan(\"size\", 10),\n Query.greaterThan(\"size\", 5)\n])\n```\n```server-php\nQuery::and([\n Query::lessThan(\"size\", 10),\n Query::greaterThan(\"size\", 5)\n])\n```\n```client-apple\nQuery.and([\n Query.lessThan(\"size\", value: 10),\n Query.greaterThan(\"size\", value: 5)\n])\n```\n```server-go\nquery.And([]string{\n query.LessThan(\"size\", 10),\n query.GreaterThan(\"size\", 5),\n})\n```\n```server-rust\nQuery::and(vec![\n Query::less_than(\"size\", 10).to_string(),\n Query::greater_than(\"size\", 5).to_string(),\n]).to_string()\n```\n```http\n{\"method\":\"and\",\"values\":[{\"method\":\"lessThan\",\"column\":\"size\",\"values\":[10]},{\"method\":\"greaterThan\",\"column\":\"size\",\"values\":[5]}]}\n```\n{% /multicode %}\n\n### OR {% #or %}\n\nReturns row if it matches any of the nested sub-queries in the array passed in.\n\n{% multicode %}\n```client-web\nQuery.or([\n Query.lessThan(\"size\", 5),\n Query.greaterThan(\"size\", 10)\n])\n```\n```client-flutter\nQuery.or([\n Query.lessThan(\"size\", 5),\n Query.greaterThan(\"size\", 10)\n])\n```\n```server-python\nQuery.or_queries([\n Query.less_than(\"size\", 5),\n Query.greater_than(\"size\", 10)\n])\n```\n```server-ruby\nQuery.or([\n Query.less_than(\"size\", 5),\n Query.greater_than(\"size\", 10)\n])\n```\n```server-deno\nQuery.or([\n Query.lessThan(\"size\", 5),\n Query.greaterThan(\"size\", 10)\n])\n```\n```server-php\nQuery::or([\n Query::lessThan(\"size\", 5),\n Query::greaterThan(\"size\", 10)\n])\n```\n```client-apple\nQuery.or([\n Query.lessThan(\"size\", value: 5),\n Query.greaterThan(\"size\", value: 10)\n])\n```\n```server-go\nquery.Or([]string{\n query.LessThan(\"size\", 5),\n query.GreaterThan(\"size\", 10),\n})\n```\n```server-rust\nQuery::or(vec![\n Query::less_than(\"size\", 5).to_string(),\n Query::greater_than(\"size\", 10).to_string(),\n]).to_string()\n```\n```http\n{\"method\":\"or\",\"values\":[{\"method\":\"lessThan\",\"column\":\"size\",\"values\":[5]},{\"method\":\"greaterThan\",\"column\":\"size\",\"values\":[10]}]}\n```\n{% /multicode %}\n\n## Ordering {% #ordering %}\n\n### Order descending {% #order-desc %}\n\nOrders results in descending order by column. Column must be indexed.\n\n{% multicode %}\n```client-web\nQuery.orderDesc(\"column\")\n```\n```client-flutter\nQuery.orderDesc(\"column\")\n```\n```server-python\nQuery.order_desc(\"column\")\n```\n```server-ruby\nQuery.order_desc(\"column\")\n```\n```server-nodejs\nQuery.orderDesc(\"column\")\n```\n```server-php\nQuery::orderDesc(\"column\")\n```\n```client-apple\nQuery.orderDesc(\"column\")\n```\n```server-go\nquery.OrderDesc(\"attribute\")\n```\n```server-rust\nQuery::order_desc(\"column\").to_string()\n```\n```http\n{\"method\":\"orderDesc\",\"column\":\"column\"}\n```\n{% /multicode %}\n\n### Order ascending {% #order-asc %}\n\nOrders results in ascending order by column. Column must be indexed.\n\n{% multicode %}\n```client-web\nQuery.orderAsc(\"column\")\n```\n```client-flutter\nQuery.orderAsc(\"column\")\n```\n```server-python\nQuery.order_asc(\"column\")\n```\n```server-ruby\nQuery.order_asc(\"column\")\n```\n```server-nodejs\nQuery.orderAsc(\"column\")\n```\n```server-php\nQuery::orderAsc(\"column\")\n```\n```client-apple\nQuery.orderAsc(\"column\")\n```\n```server-go\nquery.OrderAsc(\"attribute\")\n```\n```server-rust\nQuery::order_asc(\"column\").to_string()\n```\n```http\n{\"method\":\"orderAsc\",\"column\":\"column\"}\n```\n{% /multicode %}\n\n### Order random {% #order-random %}\n\nOrders results in random order.\n\n{% multicode %}\n```client-web\nQuery.orderRandom()\n```\n```client-flutter\nQuery.orderRandom()\n```\n```server-python\nQuery.order_random()\n```\n```server-ruby\nQuery.order_random()\n```\n```server-nodejs\nQuery.orderRandom()\n```\n```server-php\nQuery::orderRandom()\n```\n```client-apple\nQuery.orderRandom()\n```\n```server-go\nquery.OrderRandom()\n```\n```server-rust\nQuery::order_random().to_string()\n```\n```http\n{\"method\":\"orderRandom\"}\n```\n{% /multicode %}\n\n## Pagination {% #pagination %}\n\n### Limit {% #limit %}\n\nLimits the number of results returned by the query. Used for [pagination](/docs/products/databases/pagination).\n\n{% multicode %}\n```client-web\nQuery.limit(25)\n```\n```client-flutter\nQuery.limit(25)\n```\n```server-python\nQuery.limit(25)\n```\n```server-ruby\nQuery.limit(25)\n```\n```server-deno\nQuery.limit(25)\n```\n```server-php\nQuery::limit(25)\n```\n```client-apple\nQuery.limit(25)\n```\n```server-go\nquery.Limit(25)\n```\n```server-rust\nQuery::limit(25).to_string()\n```\n```http\n{\"method\":\"limit\",\"values\":[25]}\n```\n{% /multicode %}\n\n### Offset {% #offset %}\n\nOffset the results returned by skipping some of the results. Used for [pagination](/docs/products/databases/pagination).\n\n{% multicode %}\n```client-web\nQuery.offset(0)\n```\n```client-flutter\nQuery.offset(0)\n```\n```server-python\nQuery.offset(0)\n```\n```server-ruby\nQuery.offset(0)\n```\n```server-deno\nQuery.offset(0)\n```\n```server-php\nQuery::offset(0)\n```\n```client-apple\nQuery.offset(0)\n```\n```server-go\nquery.Offset(0)\n```\n```server-rust\nQuery::offset(0).to_string()\n```\n```http\n{\"method\":\"offset\",\"values\":[0]}\n```\n{% /multicode %}\n\n### Cursor after {% #cursor-after %}\n\nPlaces the cursor after the specified resource ID. Used for [pagination](/docs/products/databases/pagination).\n\n{% multicode %}\n```client-web\nQuery.cursorAfter(\"62a7...f620\")\n```\n```client-flutter\nQuery.cursorAfter(\"62a7...f620\")\n```\n```server-python\nQuery.cursor_after(\"62a7...f620\")\n```\n```server-ruby\nQuery.cursor_after(\"62a7...f620\")\n```\n```server-deno\nQuery.cursorAfter(\"62a7...f620\")\n```\n```server-php\nQuery::cursorAfter(\"62a7...f620\")\n```\n```client-apple\nQuery.cursorAfter(\"62a7...f620\")\n```\n```server-go\nquery.CursorAfter(\"62a7...f620\")\n```\n```server-rust\nQuery::cursor_after(\"62a7...f620\").to_string()\n```\n```http\n{\"method\":\"cursorAfter\",\"values\":[\"62a7...f620\"]}\n```\n{% /multicode %}\n\n### Cursor before {% #cursor-before %}\n\nPlaces the cursor before the specified resource ID. Used for [pagination](/docs/products/databases/pagination).\n\n{% multicode %}\n```client-web\nQuery.cursorBefore(\"62a7...a600\")\n```\n```client-flutter\nQuery.cursorBefore(\"62a7...a600\")\n```\n```server-python\nQuery.cursor_before(\"62a7...a600\")\n```\n```server-ruby\nQuery.cursor_before(\"62a7...a600\")\n```\n```server-deno\nQuery.cursorBefore(\"62a7...a600\")\n```\n```server-php\nQuery::cursorBefore(\"62a7...a600\")\n```\n```client-apple\nQuery.cursorBefore(\"62a7...a600\")\n```\n```server-go\nquery.CursorBefore(\"62a7...a600\")\n```\n```server-rust\nQuery::cursor_before(\"62a7...a600\").to_string()\n```\n```http\n{\"method\":\"cursorBefore\",\"values\":[\"62a7...a600\"]}\n```\n{% /multicode %}\n\n# Time helpers {% #time-helpers %}\n\nBuilt-in helpers for filtering by creation and update timestamps using\nISO 8601 date-time strings (for example, \"2025-01-01T00:00:00Z\").\n\n### Created before {% #created-before %}\n\nReturns rows created before the given date.\n\n{% multicode %}\n```client-web\nQuery.createdBefore(\"2025-01-01T00:00:00Z\")\n```\n```client-flutter\nQuery.createdBefore(\"2025-01-01T00:00:00Z\")\n```\n```client-apple\nQuery.createdBefore(\"2025-01-01T00:00:00Z\")\n```\n```client-android-kotlin\nQuery.createdBefore(\"2025-01-01T00:00:00Z\")\n```\n```client-android-java\nQuery.createdBefore(\"2025-01-01T00:00:00Z\")\n```\n```server-python\nQuery.created_before(\"2025-01-01T00:00:00Z\")\n```\n```server-ruby\nQuery.created_before(\"2025-01-01T00:00:00Z\")\n```\n```server-deno\nQuery.createdBefore(\"2025-01-01T00:00:00Z\")\n```\n```server-nodejs\nQuery.createdBefore(\"2025-01-01T00:00:00Z\")\n```\n```server-php\nQuery::createdBefore(\"2025-01-01T00:00:00Z\")\n```\n```server-swift\nQuery.createdBefore(\"2025-01-01T00:00:00Z\")\n```\n```server-rust\nQuery::created_before(\"2025-01-01T00:00:00Z\").to_string()\n```\n```http\n{\"method\":\"createdBefore\",\"values\":[\"2025-01-01T00:00:00Z\"]}\n```\n{% /multicode %}\n\n### Created after {% #created-after %}\n\nReturns rows created after the given date.\n\n{% multicode %}\n```client-web\nQuery.createdAfter(\"2025-01-01T00:00:00Z\")\n```\n```client-flutter\nQuery.createdAfter(\"2025-01-01T00:00:00Z\")\n```\n```client-apple\nQuery.createdAfter(\"2025-01-01T00:00:00Z\")\n```\n```client-android-kotlin\nQuery.createdAfter(\"2025-01-01T00:00:00Z\")\n```\n```client-android-java\nQuery.createdAfter(\"2025-01-01T00:00:00Z\")\n```\n```server-python\nQuery.created_after(\"2025-01-01T00:00:00Z\")\n```\n```server-ruby\nQuery.created_after(\"2025-01-01T00:00:00Z\")\n```\n```server-deno\nQuery.createdAfter(\"2025-01-01T00:00:00Z\")\n```\n```server-nodejs\nQuery.createdAfter(\"2025-01-01T00:00:00Z\")\n```\n```server-php\nQuery::createdAfter(\"2025-01-01T00:00:00Z\")\n```\n```server-swift\nQuery.createdAfter(\"2025-01-01T00:00:00Z\")\n```\n```server-rust\nQuery::created_after(\"2025-01-01T00:00:00Z\").to_string()\n```\n```http\n{\"method\":\"createdAfter\",\"values\":[\"2025-01-01T00:00:00Z\"]}\n```\n{% /multicode %}\n\n### Updated before {% #updated-before %}\n\nReturns rows updated before the given date.\n\n{% multicode %}\n```client-web\nQuery.updatedBefore(\"2025-01-01T00:00:00Z\")\n```\n```client-flutter\nQuery.updatedBefore(\"2025-01-01T00:00:00Z\")\n```\n```client-apple\nQuery.updatedBefore(\"2025-01-01T00:00:00Z\")\n```\n```client-android-kotlin\nQuery.updatedBefore(\"2025-01-01T00:00:00Z\")\n```\n```client-android-java\nQuery.updatedBefore(\"2025-01-01T00:00:00Z\")\n```\n```server-python\nQuery.updated_before(\"2025-01-01T00:00:00Z\")\n```\n```server-ruby\nQuery.updated_before(\"2025-01-01T00:00:00Z\")\n```\n```server-deno\nQuery.updatedBefore(\"2025-01-01T00:00:00Z\")\n```\n```server-nodejs\nQuery.updatedBefore(\"2025-01-01T00:00:00Z\")\n```\n```server-php\nQuery::updatedBefore(\"2025-01-01T00:00:00Z\")\n```\n```server-swift\nQuery.updatedBefore(\"2025-01-01T00:00:00Z\")\n```\n```server-rust\nQuery::updated_before(\"2025-01-01T00:00:00Z\").to_string()\n```\n```http\n{\"method\":\"updatedBefore\",\"values\":[\"2025-01-01T00:00:00Z\"]}\n```\n{% /multicode %}\n\n### Updated after {% #updated-after %}\n\nReturns rows updated after the given date.\n\n{% multicode %}\n```client-web\nQuery.updatedAfter(\"2025-01-01T00:00:00Z\")\n```\n```client-flutter\nQuery.updatedAfter(\"2025-01-01T00:00:00Z\")\n```\n```client-apple\nQuery.updatedAfter(\"2025-01-01T00:00:00Z\")\n```\n```client-android-kotlin\nQuery.updatedAfter(\"2025-01-01T00:00:00Z\")\n```\n```client-android-java\nQuery.updatedAfter(\"2025-01-01T00:00:00Z\")\n```\n```server-python\nQuery.updated_after(\"2025-01-01T00:00:00Z\")\n```\n```server-ruby\nQuery.updated_after(\"2025-01-01T00:00:00Z\")\n```\n```server-deno\nQuery.updatedAfter(\"2025-01-01T00:00:00Z\")\n```\n```server-nodejs\nQuery.updatedAfter(\"2025-01-01T00:00:00Z\")\n```\n```server-php\nQuery::updatedAfter(\"2025-01-01T00:00:00Z\")\n```\n```server-swift\nQuery.updatedAfter(\"2025-01-01T00:00:00Z\")\n```\n```server-rust\nQuery::updated_after(\"2025-01-01T00:00:00Z\").to_string()\n```\n```http\n{\"method\":\"updatedAfter\",\"values\":[\"2025-01-01T00:00:00Z\"]}\n```\n{% /multicode %}\n# Geo queries and spatial operations {% #geo-queries %}\n\nGeo 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.\n\nFor conceptual information about spatial data types, spatial columns and indexing, see [Geo queries](/docs/products/databases/geo-queries).\n\n{% info title=\"Additional supported queries\" %}\nIn 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.\n{% /info %}\n\n## Distance equal {% #distance-equal %}\n\nReturns rows where the spatial column is exactly the specified distance from a point.\n\n{% multicode %}\n```client-web\n// Coordinates: [longitude, latitude]\nQuery.distanceEqual(\"location\", [-73.9851, 40.7589], 200)\n```\n```client-flutter\nQuery.distanceEqual(\"location\", [-73.9851, 40.7589], 200)\n```\n```client-react-native\nQuery.distanceEqual(\"location\", [-73.9851, 40.7589], 200)\n```\n```client-apple\n// Query.distanceEqual(column, coordinates, distance)\nQuery.distanceEqual(\"location\", values: [-73.9851, 40.7589], distance: 200)\n```\n```client-android-kotlin\nQuery.distanceEqual(\"location\", listOf(-73.9851, 40.7589), 200)\n```\n```client-android-java\nQuery.distanceEqual(\"location\", Arrays.asList(-73.9851, 40.7589), 200)\n```\n```server-nodejs\nconst sdk = require('node-appwrite');\nsdk.Query.distanceEqual(\"location\", [-73.9851, 40.7589], 200)\n```\n```server-python\nQuery.distance_equal(\"location\", [-73.9851, 40.7589], 200)\n```\n```server-ruby\nQuery.distance_equal(\"location\", [-73.9851, 40.7589], 200)\n```\n```server-deno\nQuery.distanceEqual(\"location\", [-73.9851, 40.7589], 200)\n```\n```server-php\nQuery::distanceEqual(\"location\", [-73.9851, 40.7589], 200)\n```\n```server-dotnet\nQuery.DistanceEqual(\"location\", new List { -73.9851, 40.7589 }, 200)\n```\n```server-go\n// query.DistanceEqual(column, coordinates, distance)\nquery.DistanceEqual(\"location\", []float64{-73.9851, 40.7589}, 200)\n```\n```server-dart\nQuery.distanceEqual(\"location\", [-73.9851, 40.7589], 200)\n```\n```server-swift\nQuery.distanceEqual(\"location\", [-73.9851, 40.7589], 200)\n```\n```server-kotlin\nQuery.distanceEqual(\"location\", listOf(-73.9851, 40.7589), 200)\n```\n```server-java\nQuery.distanceEqual(\"location\", Arrays.asList(-73.9851, 40.7589), 200)\n```\n```server-rust\nQuery::distance_equal(\n \"location\",\n Value::Array(vec![Value::from(-73.9851), Value::from(40.7589)]),\n 200,\n true,\n).to_string()\n```\n```http\n{\"method\":\"distanceEqual\",\"column\":\"location\",\"values\":[[-73.9851, 40.7589], 200]}\n```\n{% /multicode %}\n\n## Distance not equal {% #distance-not-equal %}\n\nReturns rows where the spatial column is not exactly the specified distance from a point.\n\n{% multicode %}\n```client-web\nQuery.distanceNotEqual(\"location\", [-73.9851, 40.7589], 200)\n```\n```client-flutter\nQuery.distanceNotEqual(\"location\", [-73.9851, 40.7589], 200)\n```\n```client-react-native\nQuery.distanceNotEqual(\"location\", [-73.9851, 40.7589], 200)\n```\n```client-apple\n// Query.distanceNotEqual(column, coordinates, distance)\nQuery.distanceNotEqual(\"location\", values: [-73.9851, 40.7589], distance: 200)\n```\n```client-android-kotlin\nQuery.distanceNotEqual(\"location\", listOf(-73.9851, 40.7589), 200)\n```\n```client-android-java\nQuery.distanceNotEqual(\"location\", Arrays.asList(-73.9851, 40.7589), 200)\n```\n```server-nodejs\nconst sdk = require('node-appwrite');\nsdk.Query.distanceNotEqual(\"location\", [-73.9851, 40.7589], 200)\n```\n```server-python\nQuery.distance_not_equal(\"location\", [-73.9851, 40.7589], 200)\n```\n```server-ruby\nQuery.distance_not_equal(\"location\", [-73.9851, 40.7589], 200)\n```\n```server-deno\nQuery.distanceNotEqual(\"location\", [-73.9851, 40.7589], 200)\n```\n```server-php\nQuery::distanceNotEqual(\"location\", [-73.9851, 40.7589], 200)\n```\n```server-dotnet\nQuery.DistanceNotEqual(\"location\", new List { -73.9851, 40.7589 }, 200)\n```\n```server-go\n// query.DistanceNotEqual(column, coordinates, distance)\nquery.DistanceNotEqual(\"location\", []float64{-73.9851, 40.7589}, 200)\n```\n```server-dart\nQuery.distanceNotEqual(\"location\", [-73.9851, 40.7589], 200)\n```\n```server-swift\nQuery.distanceNotEqual(\"location\", [-73.9851, 40.7589], 200)\n```\n```server-kotlin\nQuery.distanceNotEqual(\"location\", listOf(-73.9851, 40.7589), 200)\n```\n```server-java\nQuery.distanceNotEqual(\"location\", Arrays.asList(-73.9851, 40.7589), 200)\n```\n```server-rust\nQuery::distance_not_equal(\n \"location\",\n Value::Array(vec![Value::from(-73.9851), Value::from(40.7589)]),\n 200,\n true,\n).to_string()\n```\n```http\n{\"method\":\"distanceNotEqual\",\"column\":\"location\",\"values\":[[-73.9851, 40.7589], 200]}\n```\n{% /multicode %}\n\n## Distance greater than {% #distance-greater-than %}\n\nReturns rows where the spatial column is more than the specified distance from a point.\n\n{% multicode %}\n```client-web\nQuery.distanceGreaterThan(\"location\", [-73.9851, 40.7589], 200)\n```\n```client-flutter\nQuery.distanceGreaterThan(\"location\", [-73.9851, 40.7589], 200)\n```\n```client-react-native\nQuery.distanceGreaterThan(\"location\", [-73.9851, 40.7589], 200)\n```\n```client-apple\n// Query.distanceGreaterThan(column, coordinates, distance)\nQuery.distanceGreaterThan(\"location\", values: [-73.9851, 40.7589], distance: 200)\n```\n```client-android-kotlin\nQuery.distanceGreaterThan(\"location\", listOf(-73.9851, 40.7589), 200)\n```\n```client-android-java\nQuery.distanceGreaterThan(\"location\", Arrays.asList(-73.9851, 40.7589), 200)\n```\n```server-nodejs\nconst sdk = require('node-appwrite');\nsdk.Query.distanceGreaterThan(\"location\", [-73.9851, 40.7589], 200)\n```\n```server-python\nQuery.distance_greater_than(\"location\", [-73.9851, 40.7589], 200)\n```\n```server-ruby\nQuery.distance_greater_than(\"location\", [-73.9851, 40.7589], 200)\n```\n```server-deno\nQuery.distanceGreaterThan(\"location\", [-73.9851, 40.7589], 200)\n```\n```server-php\nQuery::distanceGreaterThan(\"location\", [-73.9851, 40.7589], 200)\n```\n```server-dotnet\nQuery.DistanceGreaterThan(\"location\", new List { -73.9851, 40.7589 }, 200)\n```\n```server-go\n// query.DistanceGreaterThan(column, coordinates, distance)\nquery.DistanceGreaterThan(\"location\", []float64{-73.9851, 40.7589}, 200)\n```\n```server-dart\nQuery.distanceGreaterThan(\"location\", [-73.9851, 40.7589], 200)\n```\n```server-swift\nQuery.distanceGreaterThan(\"location\", [-73.9851, 40.7589], 200)\n```\n```server-kotlin\nQuery.distanceGreaterThan(\"location\", listOf(-73.9851, 40.7589), 200)\n```\n```server-rust\nQuery::distance_greater_than(\n \"location\",\n Value::Array(vec![Value::from(-73.9851), Value::from(40.7589)]),\n 200,\n true,\n).to_string()\n```\n```http\n{\"method\":\"distanceGreaterThan\",\"column\":\"location\",\"values\":[[-73.9851, 40.7589], 200]}\n```\n{% /multicode %}\n\n## Distance less than {% #distance-less-than %}\n\nReturns rows where the spatial column is less than the specified distance from a point.\n\n{% multicode %}\n```client-web\nQuery.distanceLessThan(\"location\", [-73.9851, 40.7589], 200)\n```\n```client-flutter\nQuery.distanceLessThan(\"location\", [-73.9851, 40.7589], 200)\n```\n```client-react-native\nQuery.distanceLessThan(\"location\", [-73.9851, 40.7589], 200)\n```\n```client-apple\n// Query.distanceLessThan(column, coordinates, distance)\nQuery.distanceLessThan(\"location\", values: [-73.9851, 40.7589], distance: 200)\n```\n```client-android-kotlin\nQuery.distanceLessThan(\"location\", listOf(-73.9851, 40.7589), 200)\n```\n```client-android-java\nQuery.distanceLessThan(\"location\", Arrays.asList(-73.9851, 40.7589), 200)\n```\n```server-nodejs\nconst sdk = require('node-appwrite');\nsdk.Query.distanceLessThan(\"location\", [-73.9851, 40.7589], 200)\n```\n```server-python\nQuery.distance_less_than(\"location\", [-73.9851, 40.7589], 200)\n```\n```server-ruby\nQuery.distance_less_than(\"location\", [-73.9851, 40.7589], 200)\n```\n```server-deno\nQuery.distanceLessThan(\"location\", [-73.9851, 40.7589], 200)\n```\n```server-php\nQuery::distanceLessThan(\"location\", [-73.9851, 40.7589], 200)\n```\n```server-dotnet\nQuery.DistanceLessThan(\"location\", new List { -73.9851, 40.7589 }, 200)\n```\n```server-go\n// query.DistanceLessThan(column, coordinates, distance)\nquery.DistanceLessThan(\"location\", []float64{-73.9851, 40.7589}, 200)\n```\n```server-dart\nQuery.distanceLessThan(\"location\", [-73.9851, 40.7589], 200)\n```\n```server-swift\nQuery.distanceLessThan(\"location\", [-73.9851, 40.7589], 200)\n```\n```server-kotlin\nQuery.distanceLessThan(\"location\", listOf(-73.9851, 40.7589), 200)\n```\n```server-rust\nQuery::distance_less_than(\n \"location\",\n Value::Array(vec![Value::from(-73.9851), Value::from(40.7589)]),\n 200,\n true,\n).to_string()\n```\n```http\n{\"method\":\"distanceLessThan\",\"column\":\"location\",\"values\":[[-73.9851, 40.7589], 200]}\n```\n{% /multicode %}\n\n## Intersects {% #intersects %}\n\nReturns rows where the spatial column intersects with the provided geometry.\n\n{% multicode %}\n```client-web\nQuery.intersects(\"area\", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]])\n```\n```client-flutter\nQuery.intersects(\"area\", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]])\n```\n```client-react-native\nQuery.intersects(\"area\", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]])\n```\n```client-apple\n// Query.intersects(column, geometry)\nQuery.intersects(\"area\", value: [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]])\n```\n```client-android-kotlin\nQuery.intersects(\"area\", listOf(listOf(-73.9851, 40.7589), listOf(-73.9776, 40.7614), listOf(-73.9733, 40.7505), listOf(-73.9851, 40.7589)))\n```\n```client-android-java\nQuery.intersects(\"area\", Arrays.asList(\n Arrays.asList(-73.9851, 40.7589),\n Arrays.asList(-73.9776, 40.7614),\n Arrays.asList(-73.9733, 40.7505),\n Arrays.asList(-73.9851, 40.7589)\n))\n```\n```server-nodejs\nconst sdk = require('node-appwrite');\nsdk.Query.intersects(\"area\", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]])\n```\n```server-python\nQuery.intersects(\"area\", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]])\n```\n```server-ruby\nQuery.intersects(\"area\", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]])\n```\n```server-deno\nQuery.intersects(\"area\", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]])\n```\n```server-php\nQuery::intersects(\"area\", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]])\n```\n```server-dotnet\nQuery.Intersects(\"area\", new List>\n{\n new List { -73.9851, 40.7589 },\n new List { -73.9776, 40.7614 },\n new List { -73.9733, 40.7505 },\n new List { -73.9851, 40.7589 }\n})\n```\n```server-go\n// query.Intersects(column, geometry)\nquery.Intersects(\"area\", [][]float64{{-73.9851, 40.7589}, {-73.9776, 40.7614}, {-73.9733, 40.7505}, {-73.9851, 40.7589}})\n```\n```server-dart\nQuery.intersects(\"area\", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]])\n```\n```server-swift\nQuery.intersects(\"area\", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]])\n```\n```server-kotlin\nQuery.intersects(\"area\", listOf(listOf(-73.9851, 40.7589), listOf(-73.9776, 40.7614), listOf(-73.9733, 40.7505), listOf(-73.9851, 40.7589)))\n```\n```server-rust\nQuery::intersects(\"area\", serde_json::json!([\n [-73.9851, 40.7589],\n [-73.9776, 40.7614],\n [-73.9733, 40.7505],\n [-73.9851, 40.7589]\n])).to_string()\n```\n```http\n{\"method\":\"intersects\",\"column\":\"area\",\"values\":[[[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]]}\n```\n{% /multicode %}\n\n## Not intersects {% #not-intersects %}\n\nReturns rows where the spatial column does not intersect with the provided geometry.\n\n{% multicode %}\n```client-web\nQuery.notIntersects(\"area\", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]])\n```\n```client-flutter\nQuery.notIntersects(\"area\", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]])\n```\n```client-react-native\nQuery.notIntersects(\"area\", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]])\n```\n```client-apple\n// Query.notIntersects(column, geometry)\nQuery.notIntersects(\"area\", value: [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]])\n```\n```client-android-kotlin\nQuery.notIntersects(\"area\", listOf(listOf(-73.9851, 40.7589), listOf(-73.9776, 40.7614), listOf(-73.9733, 40.7505), listOf(-73.9851, 40.7589)))\n```\n```client-android-java\nQuery.notIntersects(\"area\", Arrays.asList(\n Arrays.asList(-73.9851, 40.7589),\n Arrays.asList(-73.9776, 40.7614),\n Arrays.asList(-73.9733, 40.7505),\n Arrays.asList(-73.9851, 40.7589)\n))\n```\n```server-nodejs\nconst sdk = require('node-appwrite');\nsdk.Query.notIntersects(\"area\", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]])\n```\n```server-python\nQuery.not_intersects(\"area\", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]])\n```\n```server-ruby\nQuery.not_intersects(\"area\", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]])\n```\n```server-deno\nQuery.notIntersects(\"area\", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]])\n```\n```server-php\nQuery::notIntersects(\"area\", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]])\n```\n```server-dotnet\nQuery.NotIntersects(\"area\", new List>\n{\n new List { -73.9851, 40.7589 },\n new List { -73.9776, 40.7614 },\n new List { -73.9733, 40.7505 },\n new List { -73.9851, 40.7589 }\n})\n```\n```server-go\n// query.NotIntersects(column, geometry)\nquery.NotIntersects(\"area\", [][]float64{{-73.9851, 40.7589}, {-73.9776, 40.7614}, {-73.9733, 40.7505}, {-73.9851, 40.7589}})\n```\n```server-dart\nQuery.notIntersects(\"area\", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]])\n```\n```server-swift\nQuery.notIntersects(\"area\", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]])\n```\n```server-kotlin\nQuery.notIntersects(\"area\", listOf(listOf(-73.9851, 40.7589), listOf(-73.9776, 40.7614), listOf(-73.9733, 40.7505), listOf(-73.9851, 40.7589)))\n```\n```server-rust\nQuery::not_intersects(\"area\", serde_json::json!([\n [-73.9851, 40.7589],\n [-73.9776, 40.7614],\n [-73.9733, 40.7505],\n [-73.9851, 40.7589]\n])).to_string()\n```\n```http\n{\"method\":\"notIntersects\",\"column\":\"area\",\"values\":[[[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]]}\n```\n{% /multicode %}\n\n## Overlaps {% #overlaps %}\n\nReturns rows where the spatial column overlaps with the provided geometry.\n\n{% multicode %}\n```client-web\nQuery.overlaps(\"zone\", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]])\n```\n```client-flutter\nQuery.overlaps(\"zone\", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]])\n```\n```client-react-native\nQuery.overlaps(\"zone\", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]])\n```\n```client-apple\n// Query.overlaps(column, geometry)\nQuery.overlaps(\"zone\", value: [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]])\n```\n```client-android-kotlin\nQuery.overlaps(\"zone\", listOf(listOf(-73.9851, 40.7589), listOf(-73.9776, 40.7614), listOf(-73.9733, 40.7505), listOf(-73.9851, 40.7589)))\n```\n```client-android-java\nQuery.overlaps(\"zone\", Arrays.asList(\n Arrays.asList(-73.9851, 40.7589),\n Arrays.asList(-73.9776, 40.7614),\n Arrays.asList(-73.9733, 40.7505),\n Arrays.asList(-73.9851, 40.7589)\n))\n```\n```server-nodejs\nconst sdk = require('node-appwrite');\nsdk.Query.overlaps(\"zone\", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]])\n```\n```server-python\nQuery.overlaps(\"zone\", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]])\n```\n```server-ruby\nQuery.overlaps(\"zone\", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]])\n```\n```server-deno\nQuery.overlaps(\"zone\", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]])\n```\n```server-php\nQuery::overlaps(\"zone\", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]])\n```\n```server-dotnet\nQuery.Overlaps(\"zone\", new List>\n{\n new List { -73.9851, 40.7589 },\n new List { -73.9776, 40.7614 },\n new List { -73.9733, 40.7505 },\n new List { -73.9851, 40.7589 }\n})\n```\n```server-go\n// query.Overlaps(column, geometry)\nquery.Overlaps(\"zone\", [][]float64{{-73.9851, 40.7589}, {-73.9776, 40.7614}, {-73.9733, 40.7505}, {-73.9851, 40.7589}})\n```\n```server-dart\nQuery.overlaps(\"zone\", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]])\n```\n```server-swift\nQuery.overlaps(\"zone\", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]])\n```\n```server-kotlin\nQuery.overlaps(\"zone\", listOf(listOf(-73.9851, 40.7589), listOf(-73.9776, 40.7614), listOf(-73.9733, 40.7505), listOf(-73.9851, 40.7589)))\n```\n```server-rust\nQuery::overlaps(\"zone\", serde_json::json!([\n [-73.9851, 40.7589],\n [-73.9776, 40.7614],\n [-73.9733, 40.7505],\n [-73.9851, 40.7589]\n])).to_string()\n```\n```http\n{\"method\":\"overlaps\",\"column\":\"zone\",\"values\":[[[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]]}\n```\n{% /multicode %}\n\n## Not overlaps {% #not-overlaps %}\n\nReturns rows where the spatial column does not overlap with the provided geometry.\n\n{% multicode %}\n```client-web\nQuery.notOverlaps(\"zone\", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]])\n```\n```client-flutter\nQuery.notOverlaps(\"zone\", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]])\n```\n```client-react-native\nQuery.notOverlaps(\"zone\", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]])\n```\n```client-apple\n// Query.notOverlaps(column, geometry)\nQuery.notOverlaps(\"zone\", value: [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]])\n```\n```client-android-kotlin\nQuery.notOverlaps(\"zone\", listOf(listOf(-73.9851, 40.7589), listOf(-73.9776, 40.7614), listOf(-73.9733, 40.7505), listOf(-73.9851, 40.7589)))\n```\n```client-android-java\nQuery.notOverlaps(\"zone\", Arrays.asList(\n Arrays.asList(-73.9851, 40.7589),\n Arrays.asList(-73.9776, 40.7614),\n Arrays.asList(-73.9733, 40.7505),\n Arrays.asList(-73.9851, 40.7589)\n))\n```\n```server-nodejs\nconst sdk = require('node-appwrite');\nsdk.Query.notOverlaps(\"zone\", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]])\n```\n```server-python\nQuery.not_overlaps(\"zone\", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]])\n```\n```server-ruby\nQuery.not_overlaps(\"zone\", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]])\n```\n```server-deno\nQuery.notOverlaps(\"zone\", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]])\n```\n```server-php\nQuery::notOverlaps(\"zone\", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]])\n```\n```server-dotnet\nQuery.NotOverlaps(\"zone\", new List>\n{\n new List { -73.9851, 40.7589 },\n new List { -73.9776, 40.7614 },\n new List { -73.9733, 40.7505 },\n new List { -73.9851, 40.7589 }\n})\n```\n```server-go\n// query.NotOverlaps(column, geometry)\nquery.NotOverlaps(\"zone\", [][]float64{{-73.9851, 40.7589}, {-73.9776, 40.7614}, {-73.9733, 40.7505}, {-73.9851, 40.7589}})\n```\n```server-dart\nQuery.notOverlaps(\"zone\", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]])\n```\n```server-swift\nQuery.notOverlaps(\"zone\", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]])\n```\n```server-kotlin\nQuery.notOverlaps(\"zone\", listOf(listOf(-73.9851, 40.7589), listOf(-73.9776, 40.7614), listOf(-73.9733, 40.7505), listOf(-73.9851, 40.7589)))\n```\n```server-rust\nQuery::not_overlaps(\"zone\", serde_json::json!([\n [-73.9851, 40.7589],\n [-73.9776, 40.7614],\n [-73.9733, 40.7505],\n [-73.9851, 40.7589]\n])).to_string()\n```\n```http\n{\"method\":\"notOverlaps\",\"column\":\"zone\",\"values\":[[[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]]}\n```\n{% /multicode %}\n\n## Touches {% #touches %}\n\nReturns rows where the spatial column touches the provided geometry.\n\n{% multicode %}\n```client-web\nQuery.touches(\"boundary\", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]])\n```\n```client-flutter\nQuery.touches(\"boundary\", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]])\n```\n```client-react-native\nQuery.touches(\"boundary\", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]])\n```\n```client-apple\n// Query.touches(column, geometry)\nQuery.touches(\"boundary\", value: [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]])\n```\n```client-android-kotlin\nQuery.touches(\"boundary\", listOf(listOf(-73.9851, 40.7589), listOf(-73.9776, 40.7614), listOf(-73.9733, 40.7505), listOf(-73.9851, 40.7589)))\n```\n```client-android-java\nQuery.touches(\"boundary\", Arrays.asList(\n Arrays.asList(-73.9851, 40.7589),\n Arrays.asList(-73.9776, 40.7614),\n Arrays.asList(-73.9733, 40.7505),\n Arrays.asList(-73.9851, 40.7589)\n))\n```\n```server-nodejs\nconst sdk = require('node-appwrite');\nsdk.Query.touches(\"boundary\", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]])\n```\n```server-python\nQuery.touches(\"boundary\", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]])\n```\n```server-ruby\nQuery.touches(\"boundary\", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]])\n```\n```server-deno\nQuery.touches(\"boundary\", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]])\n```\n```server-php\nQuery::touches(\"boundary\", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]])\n```\n```server-dotnet\nQuery.Touches(\"boundary\", new List>\n{\n new List { -73.9851, 40.7589 },\n new List { -73.9776, 40.7614 },\n new List { -73.9733, 40.7505 },\n new List { -73.9851, 40.7589 }\n})\n```\n```server-go\n// query.Touches(column, geometry)\nquery.Touches(\"boundary\", [][]float64{{-73.9851, 40.7589}, {-73.9776, 40.7614}, {-73.9733, 40.7505}, {-73.9851, 40.7589}})\n```\n```server-dart\nQuery.touches(\"boundary\", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]])\n```\n```server-swift\nQuery.touches(\"boundary\", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]])\n```\n```server-kotlin\nQuery.touches(\"boundary\", listOf(listOf(-73.9851, 40.7589), listOf(-73.9776, 40.7614), listOf(-73.9733, 40.7505), listOf(-73.9851, 40.7589)))\n```\n```server-rust\nQuery::touches(\"boundary\", serde_json::json!([\n [-73.9851, 40.7589],\n [-73.9776, 40.7614],\n [-73.9733, 40.7505],\n [-73.9851, 40.7589]\n])).to_string()\n```\n```http\n{\"method\":\"touches\",\"column\":\"boundary\",\"values\":[[[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]]}\n```\n{% /multicode %}\n\n## Not touches {% #not-touches %}\n\nReturns rows where the spatial column does not touch the provided geometry.\n\n{% multicode %}\n```client-web\nQuery.notTouches(\"boundary\", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]])\n```\n```client-flutter\nQuery.notTouches(\"boundary\", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]])\n```\n```client-react-native\nQuery.notTouches(\"boundary\", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]])\n```\n```client-apple\n// Query.notTouches(column, geometry)\nQuery.notTouches(\"boundary\", value: [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]])\n```\n```client-android-kotlin\nQuery.notTouches(\"boundary\", listOf(listOf(-73.9851, 40.7589), listOf(-73.9776, 40.7614), listOf(-73.9733, 40.7505), listOf(-73.9851, 40.7589)))\n```\n```client-android-java\nQuery.notTouches(\"boundary\", Arrays.asList(\n Arrays.asList(-73.9851, 40.7589),\n Arrays.asList(-73.9776, 40.7614),\n Arrays.asList(-73.9733, 40.7505),\n Arrays.asList(-73.9851, 40.7589)\n))\n```\n```server-nodejs\nconst sdk = require('node-appwrite');\nsdk.Query.notTouches(\"boundary\", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]])\n```\n```server-python\nQuery.not_touches(\"boundary\", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]])\n```\n```server-ruby\nQuery.not_touches(\"boundary\", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]])\n```\n```server-deno\nQuery.notTouches(\"boundary\", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]])\n```\n```server-php\nQuery::notTouches(\"boundary\", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]])\n```\n```server-dotnet\nQuery.NotTouches(\"boundary\", new List>\n{\n new List { -73.9851, 40.7589 },\n new List { -73.9776, 40.7614 },\n new List { -73.9733, 40.7505 },\n new List { -73.9851, 40.7589 }\n})\n```\n```server-go\n// query.NotTouches(column, geometry)\nquery.NotTouches(\"boundary\", [][]float64{{-73.9851, 40.7589}, {-73.9776, 40.7614}, {-73.9733, 40.7505}, {-73.9851, 40.7589}})\n```\n```server-dart\nQuery.notTouches(\"boundary\", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]])\n```\n```server-swift\nQuery.notTouches(\"boundary\", [[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]])\n```\n```server-kotlin\nQuery.notTouches(\"boundary\", listOf(listOf(-73.9851, 40.7589), listOf(-73.9776, 40.7614), listOf(-73.9733, 40.7505), listOf(-73.9851, 40.7589)))\n```\n```server-rust\nQuery::not_touches(\"boundary\", serde_json::json!([\n [-73.9851, 40.7589],\n [-73.9776, 40.7614],\n [-73.9733, 40.7505],\n [-73.9851, 40.7589]\n])).to_string()\n```\n```http\n{\"method\":\"notTouches\",\"column\":\"boundary\",\"values\":[[[-73.9851, 40.7589], [-73.9776, 40.7614], [-73.9733, 40.7505], [-73.9851, 40.7589]]]}\n```\n{% /multicode %}\n\n## Crosses {% #crosses %}\n\nReturns rows where the spatial column crosses the provided geometry.\n\n{% multicode %}\n```client-web\nQuery.crosses(\"route\", [[-73.9851, 40.7589], [-73.9776, 40.7614]])\n```\n```client-flutter\nQuery.crosses(\"route\", [[-73.9851, 40.7589], [-73.9776, 40.7614]])\n```\n```client-react-native\nQuery.crosses(\"route\", [[-73.9851, 40.7589], [-73.9776, 40.7614]])\n```\n```client-apple\n// Query.crosses(column, geometry)\nQuery.crosses(\"route\", value: [[-73.9851, 40.7589], [-73.9776, 40.7614]])\n```\n```client-android-kotlin\nQuery.crosses(\"route\", listOf(listOf(-73.9851, 40.7589), listOf(-73.9776, 40.7614)))\n```\n```client-android-java\nQuery.crosses(\"route\", Arrays.asList(\n Arrays.asList(-73.9851, 40.7589),\n Arrays.asList(-73.9776, 40.7614)\n))\n```\n```server-nodejs\nconst sdk = require('node-appwrite');\nsdk.Query.crosses(\"route\", [[-73.9851, 40.7589], [-73.9776, 40.7614]])\n```\n```server-python\nQuery.crosses(\"route\", [[-73.9851, 40.7589], [-73.9776, 40.7614]])\n```\n```server-ruby\nQuery.crosses(\"route\", [[-73.9851, 40.7589], [-73.9776, 40.7614]])\n```\n```server-deno\nQuery.crosses(\"route\", [[-73.9851, 40.7589], [-73.9776, 40.7614]])\n```\n```server-php\nQuery::crosses(\"route\", [[-73.9851, 40.7589], [-73.9776, 40.7614]])\n```\n```server-dotnet\nQuery.Crosses(\"route\", new List>\n{\n new List { -73.9851, 40.7589 },\n new List { -73.9776, 40.7614 }\n})\n```\n```server-go\n// query.Crosses(column, geometry)\nquery.Crosses(\"route\", [][]float64{{-73.9851, 40.7589}, {-73.9776, 40.7614}})\n```\n```server-dart\nQuery.crosses(\"route\", [[-73.9851, 40.7589], [-73.9776, 40.7614]])\n```\n```server-swift\nQuery.crosses(\"route\", [[-73.9851, 40.7589], [-73.9776, 40.7614]])\n```\n```server-kotlin\nQuery.crosses(\"route\", listOf(listOf(-73.9851, 40.7589), listOf(-73.9776, 40.7614)))\n```\n```server-rust\nQuery::crosses(\"route\", serde_json::json!([\n [-73.9851, 40.7589],\n [-73.9776, 40.7614]\n])).to_string()\n```\n```http\n{\"method\":\"crosses\",\"column\":\"route\",\"values\":[[[-73.9851, 40.7589], [-73.9776, 40.7614]]]}\n```\n{% /multicode %}\n\n## Not crosses {% #not-crosses %}\n\nReturns rows where the spatial column does not cross the provided geometry.\n\n{% multicode %}\n```client-web\nQuery.notCrosses(\"route\", [[-73.9851, 40.7589], [-73.9776, 40.7614]])\n```\n```client-flutter\nQuery.notCrosses(\"route\", [[-73.9851, 40.7589], [-73.9776, 40.7614]])\n```\n```client-react-native\nQuery.notCrosses(\"route\", [[-73.9851, 40.7589], [-73.9776, 40.7614]])\n```\n```client-apple\n// Query.notCrosses(column, geometry)\nQuery.notCrosses(\"route\", value: [[-73.9851, 40.7589], [-73.9776, 40.7614]])\n```\n```client-android-kotlin\nQuery.notCrosses(\"route\", listOf(listOf(-73.9851, 40.7589), listOf(-73.9776, 40.7614)))\n```\n```client-android-java\nQuery.notCrosses(\"route\", Arrays.asList(\n Arrays.asList(-73.9851, 40.7589),\n Arrays.asList(-73.9776, 40.7614)\n))\n```\n```server-nodejs\nconst sdk = require('node-appwrite');\nsdk.Query.notCrosses(\"route\", [[-73.9851, 40.7589], [-73.9776, 40.7614]])\n```\n```server-python\nQuery.not_crosses(\"route\", [[-73.9851, 40.7589], [-73.9776, 40.7614]])\n```\n```server-ruby\nQuery.not_crosses(\"route\", [[-73.9851, 40.7589], [-73.9776, 40.7614]])\n```\n```server-deno\nQuery.notCrosses(\"route\", [[-73.9851, 40.7589], [-73.9776, 40.7614]])\n```\n```server-php\nQuery::notCrosses(\"route\", [[-73.9851, 40.7589], [-73.9776, 40.7614]])\n```\n```server-dotnet\nQuery.NotCrosses(\"route\", new List>\n{\n new List { -73.9851, 40.7589 },\n new List { -73.9776, 40.7614 }\n})\n```\n```server-go\n// query.NotCrosses(column, geometry)\nquery.NotCrosses(\"route\", [][]float64{{-73.9851, 40.7589}, {-73.9776, 40.7614}})\n```\n```server-dart\nQuery.notCrosses(\"route\", [[-73.9851, 40.7589], [-73.9776, 40.7614]])\n```\n```server-swift\nQuery.notCrosses(\"route\", [[-73.9851, 40.7589], [-73.9776, 40.7614]])\n```\n```server-kotlin\nQuery.notCrosses(\"route\", listOf(listOf(-73.9851, 40.7589), listOf(-73.9776, 40.7614)))\n```\n```server-rust\nQuery::not_crosses(\"route\", serde_json::json!([\n [-73.9851, 40.7589],\n [-73.9776, 40.7614]\n])).to_string()\n```\n```http\n{\"method\":\"notCrosses\",\"column\":\"route\",\"values\":[[[-73.9851, 40.7589], [-73.9776, 40.7614]]]}\n```\n{% /multicode %}\n\n# Complex queries {% #complex-queries %}\n\nYou 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:\n\n{% multicode %}\n```client-web\nconst results = await tablesDB.listRows({\n databaseId: '',\n tableId: '',\n queries: [\n Query.or([\n Query.and([\n Query.equal('category', ['books']),\n Query.lessThan('price', 20)\n ]),\n Query.and([\n Query.equal('category', ['magazines']),\n Query.lessThan('price', 10)\n ])\n ])\n ]\n});\n```\n```client-flutter\nfinal results = await tablesDB.listRows(\n '',\n '',\n [\n Query.or([\n Query.and([\n Query.equal('category', ['books']),\n Query.lessThan('price', 20)\n ]),\n Query.and([\n Query.equal('category', ['magazines']),\n Query.lessThan('price', 10)\n ])\n ])\n ]\n);\n```\n```server-python\nresults = tablesDB.list_rows(\n database_id='',\n table_id='',\n queries=[\n Query.or_queries([\n Query.and_queries([\n Query.equal('category', ['books']),\n Query.less_than('price', 20)\n ]),\n Query.and_queries([\n Query.equal('category', ['magazines']),\n Query.less_than('price', 10)\n ])\n ])\n ]\n)\n```\n```server-go\nrows, err := tablesDB.ListRows(\n \"\",\n \"\",\n tablesDB.WithListRowsQueries([]string{\n query.Or([]string{\n query.And([]string{\n query.Equal(\"category\", []string{\"books\"}),\n query.LessThan(\"price\", 20),\n }),\n query.And([]string{\n query.Equal(\"category\", []string{\"magazines\"}),\n query.LessThan(\"price\", 10),\n }),\n }),\n }),\n)\nif err != nil {\n log.Fatal(err)\n}\n```\n```server-rust\nlet rows = tables_db.list_rows(\n \"\",\n \"\",\n Some(vec![\n Query::or(vec![\n Query::and(vec![\n Query::equal(\"category\", Value::Array(vec![Value::String(\"books\".to_string())])).to_string(),\n Query::less_than(\"price\", 20).to_string(),\n ]).to_string(),\n Query::and(vec![\n Query::equal(\"category\", Value::Array(vec![Value::String(\"magazines\".to_string())])).to_string(),\n Query::less_than(\"price\", 10).to_string(),\n ]).to_string(),\n ]).to_string(),\n ]),\n None,\n None,\n None,\n).await?;\n```\n```http\n{\"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]}]}]}\n```\n{% /multicode %}\n\nThis 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.\nEach 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."}, {"path": "docs/products/databases/quick-start", "title": "Start with Databases", "description": "Get started with Appwrite Databases. Follow a step-by-step guide to create your first database, define tables, and perform basic data operations.", "content": "{% section #create-database step=1 title=\"Create database\" %}\nHead to your [Appwrite Console](https://cloud.appwrite.io/console/) and create a database and name it `Oscar`.\nOptionally, add a custom database ID.\n{% /section %}\n\n{% section #create-table step=2 title=\"Create table\" %}\nCreate a table and name it `My books`. Optionally, add a custom table ID.\n\nNavigate to **Columns** and create columns by clicking **Create column** and select **String**.\nColumns define the structure of your table's rows. Enter **Column key** and **Size**. For example, `title` and `100`.\n\nNavigate to **Settings** > **Permissions** and add a new role **Any**.\nCheck the **CREATE** and **READ** permissions, so anyone can create and read rows.\n{% /section %}\n\n\n{% section #create-rows step=3 title=\"Create rows\" %}\nTo create a row use the `createRow` method.\n\nIn the **Settings** menu, find your project ID and replace `` in the example.\n\nNavigate to the `Oscar` database, copy the database ID, and replace ``.\nThen, in the `My books` table, copy the table ID, and replace ``.\n\n{% multicode %}\n```client-web\nimport { Client, ID, TablesDB } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nconst tablesDB = new TablesDB(client);\n\nconst promise = tablesDB.createRow({\n databaseId: '',\n tableId: '',\n rowId: ID.unique(),\n data: { title: \"Hamlet\" }\n});\n\npromise.then(function (response) {\n console.log(response);\n}, function (error) {\n console.log(error);\n});\n```\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nvoid main() async {\n final client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\n final tablesDB = TablesDB(client);\n\n try {\n final row = tablesDB.createRow(\n databaseId: '',\n tableId: '',\n rowId: ID.unique(),\n data: { \"title\": \"Hamlet\" }\n );\n } on AppwriteException catch(e) {\n print(e);\n }\n}\n```\n```client-apple\nimport Appwrite\nimport AppwriteModels\n\nfunc main() async throws {\n let client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\n let tablesDB = TablesDB(client)\n\n do {\n let row = try await tablesDB.createRow(\n databaseId: \"\",\n tableId: \"\",\n rowId: ID.unique(),\n data: [\"title\" : \"hamlet\"]\n )\n } catch {\n print(error.localizedDescription)\n }\n}\n```\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.TablesDB\n\nsuspend fun main() {\n val client = Client(applicationContext)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\n val tablesDB = TablesDB(client)\n\n try {\n val row = tablesDB.createRow(\n databaseId = \"\",\n tableId = \"\",\n rowId = ID.unique(),\n data = mapOf(\"title\" to \"hamlet\"),\n )\n } catch (e: Exception) {\n Log.e(\"Appwrite\", \"Error: \" + e.message)\n }\n}\n```\n{% /multicode %}\n\nThe response should look similar to this.\n\n```json\n{\n \"title\": \"Hamlet\",\n \"$id\": \"65013138dcd8618e80c4\",\n \"$permissions\": [],\n \"$createdAt\": \"2023-09-13T03:49:12.905+00:00\",\n \"$updatedAt\": \"2023-09-13T03:49:12.905+00:00\",\n \"$databaseId\": \"650125c64b3c25ce4bc4\",\n \"$tableId\": \"650125cff227cf9f95ad\"\n}\n```\n\n{% /section %}\n\n{% section #list-rows step=4 title=\"List rows\" %}\nTo read and query data from your table, use the `listRows` endpoint.\n\nLike the previous step, replace ``, ``, and `` with their respective IDs.\n{% multicode %}\n```client-web\nimport { Client, Query, TablesDB } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nconst tablesDB = new TablesDB(client);\n\nconst promise = tablesDB.listRows({\n databaseId: \"\",\n tableId: \"\",\n queries: [\n Query.equal('title', 'Hamlet')\n ]\n});\n\npromise.then(function (response) {\n console.log(response);\n}, function (error) {\n console.log(error);\n});\n```\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nvoid main() async {\n final client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\n final tablesDB = TablesDB(client);\n\n try {\n final rows = await tablesDB.listRows(\n databaseId: '',\n tableId: '',\n queries: [\n Query.equal('title', 'Hamlet')\n ]\n );\n } on AppwriteException catch(e) {\n print(e);\n }\n}\n```\n```client-apple\nimport Appwrite\nimport AppwriteModels\n\nfunc main() async throws{\n let client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\n let tablesDB = TablesDB(client)\n\n do {\n let rows = try await tablesDB.listRows(\n databaseId: \"\",\n tableId: \"\",\n queries: [\n Query.equal(\"title\", value: \"Hamlet\")\n ]\n )\n } catch {\n print(error.localizedDescription)\n }\n}\n```\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.Query\nimport io.appwrite.services.TablesDB\n\nsuspend fun main() {\n val client = Client(applicationContext)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\n val tablesDB = TablesDB(client)\n\n try {\n val rows = tablesDB.listRows(\n databaseId = \"\",\n tableId = \"\",\n queries = listOf(\n Query.equal(\"title\", \"Hamlet\")\n )\n )\n } catch (e: AppwriteException) {\n Log.e(\"Appwrite\", \"Error: \" + e.message)\n }\n}\n```\n\n{% /multicode %}\n{% /section %}\n\n{% section #type-safety step=5 title=\"Type safety with models\" %}\nFor added type safety and better development experience, mobile and native SDKs support custom model types with the `nestedType` parameter.\n\nDefine a data class or model that matches your table structure:\n\n{% multicode %}\n```client-android-kotlin\ndata class Book(\n val title: String,\n val author: String? = null,\n val pages: Int? = null,\n val isAvailable: Boolean = true\n)\n\nval tablesDB = TablesDB(client)\n\ntry {\n // Use nestedType for type-safe responses\n val books = tablesDB.listRows(\n databaseId = \"\",\n tableId = \"\",\n nestedType = Book::class.java\n )\n\n for (book in books.rows) {\n Log.d(\"Appwrite\", \"Book: ${book.title} by ${book.author}\")\n }\n} catch (e: AppwriteException) {\n Log.e(\"Appwrite\", \"Error: ${e.message}\")\n}\n```\n```client-apple\nstruct Book: Codable {\n let title: String\n let author: String?\n let pages: Int?\n let isAvailable: Bool\n}\n\nlet tablesDB = TablesDB(client)\n\ndo {\n // Use nestedType for type-safe responses\n let books = try await tablesDB.listRows(\n databaseId: \"\",\n tableId: \"\",\n nestedType: Book.self\n )\n\n for book in books.rows {\n print(\"Book: \\(book.title) by \\(book.author ?? \"Unknown\")\")\n }\n} catch {\n print(error.localizedDescription)\n}\n```\n```client-web\n// Web SDK supports generics for type safety\ninterface Book {\n title: string;\n author?: string;\n pages?: number;\n isAvailable: boolean;\n}\n\nconst tablesDB = new TablesDB(client);\n\ntry {\n const books = await tablesDB.listRows({\n databaseId: '',\n tableId: ''\n });\n\n books.rows.forEach(book => {\n console.log(`Book: ${book.title} by ${book.author}`);\n });\n} catch (error) {\n console.log(error);\n}\n```\n{% /multicode %}\n\n{% info title=\"Automatic type generation\" %}\nYou can automatically generate type definitions for your tables using the [Appwrite CLI type generation](/docs/products/databases/type-generation) feature. Run `appwrite types collection` to generate models for your collections.\n{% /info %}\n\n### Model methods\n\nModels returned by native SDKs include helpful methods for data manipulation:\n\n{% tabs %}\n{% tabsitem #kotlin title=\"Kotlin/Java\" %}\n```kotlin\nval book = books.rows.first()\n\n// Convert to Map for debugging or manual manipulation\nval bookMap = book.toMap()\nLog.d(\"Appwrite\", bookMap.toString())\n\n// Create model from Map\nval bookData = mapOf(\n \"title\" to \"The Great Gatsby\",\n \"author\" to \"F. Scott Fitzgerald\"\n)\nval newBook = Book.from(bookData, Book::class.java)\n```\n{% /tabsitem %}\n\n{% tabsitem #swift title=\"Swift\" %}\n```swift\nlet book = books.rows.first!\n\n// Convert to dictionary for debugging\nlet bookMap = book.toMap()\nprint(bookMap)\n\n// Create model from dictionary\nlet bookData: [String: Any] = [\n \"title\": \"The Great Gatsby\",\n \"author\": \"F. Scott Fitzgerald\"\n]\nlet newBook = Book.from(map: bookData)\n\n// Encode to JSON\nlet jsonData = try JSONEncoder().encode(book)\nlet jsonString = String(data: jsonData, encoding: .utf8)\n```\n{% /tabsitem %}\n{% /tabs %}\n\n{% /section %}"}, {"path": "docs/products/databases/relationships", "title": "Relationships", "description": "Manage complex data relationships with Appwrite Databases. Discover how to define and work with relationships between rows for interconnected data.", "content": "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.\n\nThese types of association between entities can be modeled in Appwrite using relationships.\n\n# Relationship columns {% #relationship-columns %}\n\nRelationships are represented in a table using **relationship columns**.\nThe relationship column contains the ID of related rows, which it references during read, update, and delete operations.\nThis column is **null** if a row has no related rows.\n\n# When to use a relationship {% #when-to-use-relationships %}\n\nRelationships 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.\n\n# Benefits of relationships {% #benefit-of-relationships %}\n\nDuplicated 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.\n\n# Opt-in loading {% #performance-loading %}\n\nBy 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.\n\n{% arrow_link href=\"/docs/products/databases/queries#relationship-select\" %}\nLearn how to load relationships with queries\n{% /arrow_link %}\n\n# Directionality {% #directionality %}\n\nAppwrite relationships can be one-way or two-way.\n\n| Type | Description |\n| -------- | ----------------------------------------------------------------------------------------------------------------- |\n| One-way | The relationship is only visible to one side of the relation. This is similar to a tree data structure. |\n| Two-way | The relationship is visible to both sides of the relationship. This is similar to a graph data structure. |\n\n# Types {% #types %}\n\nAppwrite provides four different relationship types to enforce different associative rules between rows.\n\n| Type | Description |\n| ----------- | ----------------------------------------------------------------------- |\n| One-to-one | A row can only be related to one and only one row. |\n| One-to-many | A row can be related to many other rows. |\n| Many-to-one | Many rows can be related to a single row. |\n| Many-to-many| A row can be related to many other rows. |\n\n\n# On-delete {% #on-delete %}\n\nAppwrite also allows you to define the behavior of a relationship when a row is deleted.\n\n| Type | Description |\n| ---------- | ---------------------------------------------------------------------- |\n| Restrict | If a row has at least one related row, it cannot be deleted.|\n| Cascade | If a row has related rows, when it is deleted, the related rows are also deleted.|\n| Set null | If a row has related rows, when it is deleted, the related rows are kept with their relationship column set to null.|\n\n# Creating relationships {% #create-relationships %}\nYou can define relationships in the Appwrite Console, or using a [Server SDK](/docs/sdks#server)\n\n{% tabs %}\n{% tabsitem #console title=\"Console\" %}\n\nYou can create relationships in the Appwrite Console by adding a relationship column to a table.\n\n1. In your project, navigate to **Databases** > **Select your database** > **Select your table** > **Columns** > **Create column**.\n2. Select **Relationship** as the column type.\n3. In the **Relationship** modal, select the [relationship type](#types) and pick the related table and columns.\n4. 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.\n5. Select desired [on delete](#on-delete) behavior.\n6. Click the **Create** button to create the relationship.\n{% /tabsitem %}\n\n{% tabsitem #sdk title=\"SDK\" %}\nHere's an example that adds a relationship between the tables **movies** and **reviews**.\nA 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.\n\n{% multicode %}\n```js\nconst { Client, TablesDB } = require('node-appwrite');\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject(''); // Your project ID\n\nconst tablesDB = new TablesDB(client);\n\ntablesDB.createRelationshipColumn({\n databaseId: 'marvel', // Database ID\n tableId: 'movies', // Table ID\n relatedTableId: 'reviews', // Related table ID\n type: 'oneToMany', // Relationship type\n twoWay: true, // Is two-way\n key: 'reviews', // Column key\n twoWayKey: 'movie', // Two-way column key\n onDelete: 'cascade' // On delete action\n});\n```\n\n\n```php\nuse \\Appwrite\\Client;\nuse \\Appwrite\\Services\\TablesDB;\n\n$client = (new Client())\n ->setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n ->setProject(''); // Your project ID\n\n$tablesDB = new TablesDB($client);\n\n$tables->createRelationshipColumn(\n databaseId: 'marvel', // Database ID\n tableId: 'movies', // Table ID\n relatedTableId: 'reviews', // Related table ID\n type: 'oneToMany', // Relationship type\n twoWay: true, // Is two-way\n key: 'reviews', // Column key\n twoWayKey: 'movie', // Two-way column key\n onDelete: 'cascade' // On delete action\n);\n```\n\n\n```python\nfrom appwrite.client import Client\nfrom appwrite.services.tables_db import TablesDB\n\nclient = (Client()\n .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('')) # Your project ID\n\ntablesDB = TablesDB(client)\n\ntablesDB.create_relationship_column(\n database_id='marvel', # Database ID\n table_id='movies', # Table ID\n related_table_id='reviews', # Related table ID\n type='oneToMany', # Relationship type\n two_way=True, # Is two-way\n key='reviews', # Column key\n two_way_key='movie', # Two-way column key\n on_delete='cascade' # On delete action\n)\n```\n\n\n```ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://.cloud.appwrite.io/v1')# Your API Endpoint\n .set_project('') # Your project ID\n\ntablesDB = TablesDB.new(client)\n\ntablesDB.create_relationship_column(\n database_id: 'marvel', # Database ID\n table_id: 'movies', # Table ID\n related_table_id: 'reviews', # Related table ID\n type: 'oneToMany', # Relationship type\n two_way: true, # Is two-way\n key: 'reviews', # Column key\n two_way_key: 'movie', # Two-way column key\n on_delete: 'cascade' # On delete action\n)\n```\n\n\n```deno\nimport { Client, TablesDB } from \"npm:node-appwrite\";\n\nconst client = new Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\"); // Your project ID\n\nconst tablesDB = new TablesDB(client);\n\ntablesDB.createRelationshipColumn(\n \"marvel\", // Database ID\n \"movies\", // Table ID\n \"reviews\", // Related table ID\n \"oneToMany\", // Relationship type\n true, // Is two-way\n \"reviews\", // Column key\n \"movie\", // Two-way column key\n \"cascade\" // On delete action\n);\n```\n\n\n```dart\nimport 'package:appwrite/appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject(''); // Your project ID\n\nfinal tablesDB = TablesDB(client);\n\nawait tablesDB.createRelationshipColumn(\n databaseId: 'marvel', // Database ID\n tableId: 'movies', // Table ID\n relatedTableId: 'reviews', // Related table ID\n type: 'oneToMany', // Relationship type\n twoWay: true, // Is two-way\n key: 'reviews', // Column key\n twoWayKey: 'movie', // Two-way column key\n onDelete: 'cascade', // On delete action\n);\n```\n\n\n```kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.TablesDB\n\nval client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n\nval tablesDB = TablesDB(client)\n\ntablesDB.createRelationshipColumn(\n databaseId = \"marvel\", // Database ID\n tableId = \"movies\", // Table ID\n relatedTableId = \"reviews\", // Related table ID\n type = \"oneToMany\", // Relationship type\n twoWay = true, // Is two-way\n key = \"reviews\", // Column key\n twoWayKey = \"movie\", // Two-way column key\n onDelete = \"cascade\" // On delete action\n)\n```\n\n\n```swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n\nlet tablesDB = TablesDB(client)\n\ntablesDB.createRelationshipColumn(\n databaseId: \"marvel\", // Database ID\n tableId: \"movies\", // Table ID\n relatedTableId: \"reviews\", // Related table ID\n type: \"oneToMany\", // Relationship type\n twoWay: true, // Is two-way\n key: \"reviews\", // Column key\n twoWayKey: \"movie\", // Two-way column key\n onDelete: \"cascade\" // On delete action\n)\n```\n\n\n```csharp\nusing Appwrite;\nusing Appwrite.Services;\n\nvar client = new Client()\n .SetEndpoint(\"https://.cloud.appwrite.io/v1\")\n .SetProject(\"\");\n\nvar tablesDB = new TablesDB(client);\n\nawait tablesDB.CreateRelationshipColumn(\n databaseId: \"marvel\",\n tableId: \"movies\",\n relatedTableId: \"reviews\",\n type: \"oneToMany\",\n twoWay: true,\n key: \"reviews\",\n twoWayKey: \"movie\",\n onDelete: \"cascade\");\n```\n{% /multicode %}\n{% /tabsitem %}\n{% /tabs %}\n\n# Creating rows {% #create-rows %}\nIf a table has relationship columns, you can create rows in two ways.\nYou create both parent and child at the same time using a **nested** syntax or link parent and child rows through **references***.\n\n{% tabs %}\n{% tabsitem #nested title=\"Nested\" %}\nYou can create both the **parent** and **child** at once in a relationship by nesting data.\n\n{% multicode %}\n```js\nconst { Client, ID, TablesDB } = require('node-appwrite');\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject(''); // Your project ID\n\nconst tablesDB = new TablesDB(client);\n\nawait tablesDB.createRow({\n databaseId: 'marvel',\n tableId: 'movies',\n rowId: ID.unique(),\n data: {\n title: 'Spiderman',\n year: 2002,\n reviews: [\n { author: 'Bob', text: 'Great movie!' },\n { author: 'Alice', text: 'Loved it!' }\n ]\n }\n});\n```\n\n```dart\nimport 'package:appwrite/appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject(''); // Your project ID\n\nfinal tablesDB = TablesDB(client);\n\nawait tablesDB.createRow(\n databaseId: 'marvel',\n tableId: 'movies',\n rowId: ID.unique(),\n data: {\n 'title': 'Spiderman',\n 'year': 2002,\n 'reviews': [\n { 'author': 'Bob', 'text': 'Great movie!' },\n { 'author': 'Alice', 'text': 'Loved it!' }\n ]\n },\n)\n```\n\n```swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n\nlet tablesDB = TablesDB(client: client)\n\ntablesDB.createRow(\n databaseId: \"marvel\",\n tableId: \"movies\",\n rowId: ID.unique(),\n data: [\n \"title\": \"Spiderman\",\n \"year\": 2002,\n \"reviews\": [\n [ \"author\": \"Bob\", \"text\": \"Great movie!\" ],\n [ \"author\": \"Alice\", \"text\": \"Loved it!\" ]\n ]\n ]\n)\n```\n\n```kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.TablesDB\nimport io.appwrite.ID\n\nval client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n\nval tablesDB = TablesDB(client)\n\ntablesDB.createRow(\n databaseId = \"marvel\",\n tableId = \"movies\",\n rowId = ID.unique(),\n data = mapOf(\n \"title\" to \"Spiderman\",\n \"year\" to 2002,\n \"reviews\" to listOf(\n mapOf(\"author\" to \"Bob\", \"text\" to \"Great movie!\"),\n mapOf(\"author\" to \"Alice\", \"text\" to \"Loved it!\")\n )\n )\n)\n```\n{% /multicode %}\n\n## Edge case behaviors {% #edge-case-behaviors %}\n- If a nested child row is included and **no child row ID** is provided, the child row will be given a unique ID.\n- If a nested child row is included and **no conflicting child row ID** exists, the child row will be **created**.\n- If a nested child row is included and the **child row ID already exists**, the child row will be **updated**.\n\n{% /tabsitem %}\n{% tabsitem #reference title=\"Reference\" %}\nIf the child rows are already present in the related table, you can create the parent and **reference the child rows** using their IDs.\nHere's an example connecting reviews to a movie.\n{% multicode %}\n```js\nconst { Client, ID, TablesDB } = require('node-appwrite');\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject(''); // Your project ID\n\nconst tablesDB = new TablesDB(client);\n\nawait tablesDB.createRow({\n databaseId: 'marvel',\n tableId: 'movies',\n rowId: ID.unique(),\n data: {\n title: 'Spiderman',\n year: 2002,\n reviews: [\n '',\n ''\n ]\n }\n});\n```\n\n```dart\nimport 'package:appwrite/appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint \n .setProject(''); // Your project ID \n\nfinal tablesDB = TablesDB(client);\n\nawait tablesDB.createRow(\n databaseId: 'marvel',\n tableId: 'movies',\n rowId: ID.unique(),\n data: {\n 'title': 'Spiderman',\n 'year': 2002,\n 'reviews': [\n '',\n ''\n ]\n },\n)\n```\n\n```swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint \n .setProject(\"\") // Your project ID \n\nlet tablesDB = TablesDB(client: client)\n\ntablesDB.createRow(\n databaseId: \"marvel\",\n tableId: \"movies\",\n rowId: ID.unique(),\n data: [\n \"title\": \"Spiderman\",\n \"year\": 2002,\n \"reviews\": [\n \"\",\n \"\"\n ]\n ]\n)\n```\n\n```kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.TablesDB\nimport io.appwrite.ID\n\nval client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint \n .setProject(\"\") // Your project ID \n\nval tablesDB = TablesDB(client)\n\ntablesDB.createRow(\n databaseId = \"marvel\",\n tableId = \"movies\",\n rowId = ID.unique(),\n data = mapOf(\n \"title\" to \"Spiderman\",\n \"year\" to 2002,\n \"reviews\" to listOf(\n \"\",\n \"\"\n )\n )\n)\n```\n{% /multicode %}\n{% /tabsitem %}\n{% /tabs %}\n\n# Queries {% #queries %}\n\nYou 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.\n\nUse the format `relationshipKey.field` to reference fields on related rows.\n\n{% multicode %}\n```js\nconst { Client, TablesDB, Query } = require('node-appwrite');\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject(''); // Your project ID\n\nconst tablesDB = new TablesDB(client);\n\nawait tablesDB.listRows({\n databaseId: 'marvel',\n tableId: 'movies',\n queries: [\n Query.equal('reviews.author', ['Bob'])\n ],\n});\n```\n\n```dart\nimport 'package:appwrite/appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nfinal tablesDB = TablesDB(client);\n\nawait tablesDB.listRows(\n databaseId: 'marvel',\n tableId: 'movies',\n queries: [\n Query.equal('reviews.author', ['Bob']),\n ],\n);\n```\n\n```swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nlet tablesDB = TablesDB(client)\n\ntablesDB.listRows(\n databaseId: \"marvel\",\n tableId: \"movies\",\n queries: [\n Query.equal(\"reviews.author\", value: [\"Bob\"])\n ]\n)\n```\n\n```kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.TablesDB\nimport io.appwrite.Query\n\nval client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nval tablesDB = TablesDB(client)\n\ntablesDB.listRows(\n databaseId = \"marvel\",\n tableId = \"movies\",\n queries = listOf(\n Query.equal(\"reviews.author\", listOf(\"Bob\"))\n )\n)\n```\n{% /multicode %}\n\nAll filter queries are supported on relationship fields, including `equal`, `notEqual`, `greaterThan`, `lessThan`, `between`, `contains`, and other [comparison operators](/docs/products/databases/queries#comparison).\n\n{% arrow_link href=\"/docs/products/databases/queries#relationship-select\" %}\nLearn how to select and load relationship data\n{% /arrow_link %}\n\n# Update relationships {% #update %}\nRelationships can be updated by updating the relationship column.\n\n{% multicode %}\n```js\nconst { Client, TablesDB } = require('node-appwrite');\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject(''); // Your project ID\n\nconst tablesDB = new TablesDB(client);\n\nawait tablesDB.updateRow({\n databaseId: 'marvel',\n tableId: 'movies',\n rowId: 'spiderman',\n data: {\n title: 'Spiderman',\n year: 2002,\n reviews: [\n 'review4',\n 'review5'\n ]\n }\n});\n```\n\n```dart\nimport 'package:appwrite/appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nfinal tablesDB = TablesDB(client);\n\nawait tablesDB.updateRow(\n databaseId: 'marvel',\n tableId: 'movies',\n rowId: 'spiderman',\n data: {\n 'title': 'Spiderman',\n 'year': 2002,\n 'reviews': [\n 'review4',\n 'review5'\n ]\n },\n);\n```\n\n```swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nlet tablesDB = TablesDB(client: client)\n\ntablesDB.updateRow(\n databaseId: \"marvel\",\n tableId: \"movies\",\n rowId: \"spiderman\",\n data: [\n \"title\": \"Spiderman\",\n \"year\": 2002,\n \"reviews\": [\n \"review4\",\n \"review5\"\n ]\n ]\n)\n```\n\n```kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.TablesDB\n\nval client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nval tablesDB = TablesDB(client)\n\ntablesDB.updateRow(\n databaseId = \"marvel\",\n tableId = \"movies\",\n rowId = \"spiderman\",\n data = mapOf(\n \"title\" to \"Spiderman\",\n \"year\" to 2002,\n \"reviews\" to listOf(\n \"review4\",\n \"review5\"\n )\n )\n)\n```\n\n{% /multicode %}\n\n# Delete relationships {% #delete %}\n## Unlink relationships, retain rows {% #unlink %}\n\nIf 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.\n\nIf a row can be related to **only one row**, you can delete the relationship by setting the relationship column to `null`.\n\nIf a row can be related to **more than one row**, you can delete the relationship by setting the relationship column to an empty list.\n\n## Delete relationships and rows {% #delete-both %}\n\nIf 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.\n\nIf the on-delete behavior is **restrict**, the link between the rows needs to be deleted first before the rows can be deleted **individually**.\n\nIf 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**.\n\nIf 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.\n\n{% multicode %}\n```js\nconst { Client, TablesDB } = require('node-appwrite');\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject(''); // Your project ID\n\nconst tablesDB = new TablesDB(client);\n\nawait tablesDB.deleteRow({\n databaseId: 'marvel',\n tableId: 'movies',\n rowId: 'spiderman'\n});\n```\n\n```dart\nimport 'package:appwrite/appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nfinal tablesDB = TablesDB(client);\n\nawait tablesDB.deleteRow(\n databaseId: 'marvel',\n tableId: 'movies',\n rowId: 'spiderman'\n);\n```\n\n```swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nlet tablesDB = TablesDB(client: client)\n\ntablesDB.deleteRow(\n databaseId: \"marvel\",\n tableId: \"movies\",\n rowId: \"spiderman\"\n)\n```\n\n```kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.TablesDB\n\nval client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nval tablesDB = TablesDB(client)\n\ntablesDB.deleteRow(\n databaseId = \"marvel\",\n tableId = \"movies\",\n rowId = \"spiderman\"\n)\n```\n{% /multicode %}\n\n# Permissions {% #permissions %}\n\nTo access rows in a relationship, you must have permission to access both the parent and child rows.\n\nWhen creating both the parent and child rows, the child row will **inherit permissions** from its parent.\n\nYou can also provide explicit permissions to the child row if they should be **different from their parent**.\n\n{% multicode %}\n```js\nconst { Client, ID, TablesDB } = require('node-appwrite');\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject(''); // Your project ID\n\nconst tablesDB = new TablesDB(client);\n\nawait tablesDB.createRow({\n databaseId: 'marvel',\n tableId: 'movies',\n rowId: ID.unique(),\n data: {\n title: 'Spiderman',\n year: 2002,\n reviews: [\n {\n author: 'Bob',\n text: 'Great movie!',\n $permissions: [\n Permission.read(Role.any())\n ]\n },\n ]\n }\n});\n```\n```dart\nimport 'package:appwrite/appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nfinal tablesDB = TablesDB(client);\n\nawait tablesDB.createRow(\n databaseId: 'marvel',\n tableId: 'movies',\n rowId: ID.unique(),\n data: {\n 'title': 'Spiderman',\n 'year': 2002,\n 'reviews': [\n {\n 'author': 'Bob',\n 'text': 'Great movie!',\n '\\$permissions': [\n Permission.read(Role.any())\n ]\n },\n ]\n },\n);\n```\n```swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nlet tablesDB = TablesDB(client: client)\n\ntablesDB.createRow(\n databaseId: \"marvel\",\n tableId: \"movies\",\n rowId: ID.unique(),\n data: [\n \"title\": \"Spiderman\",\n \"year\": 2002,\n \"reviews\": [\n [\n \"author\": \"Bob\",\n \"text\": \"Great movie!\",\n \"$permissions\": [\n Permission.read(Role.any())\n ]\n ],\n ]\n ]\n);\n```\n```kotlin\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nlet tablesDB = TablesDB(client: client)\n\ntablesDB.createRow(\n databaseId: \"marvel\",\n tableId: \"movies\",\n rowId: ID.unique(),\n data: [\n \"title\": \"Spiderman\",\n \"year\": 2002,\n \"reviews\": [\n [\n \"author\": \"Bob\",\n \"text\": \"Great movie!\",\n \"$permissions\": [\n Permission.read(Role.any())\n ]\n ],\n ]\n ]\n);\n```\n{% /multicode %}\n\nWhen creating, updating, or deleting in a relationship, you must have permission to access all rows referenced.\nIf the user does not have read permission to any row, an exception will be thrown.\n\n# Limitations {% #limitations %}\n\nRelationships can be nested between tables, but are restricted to a **max depth of three levels**.\nRelationship column key, type, and directionality can't be updated.\nOn-delete behavior is the only option that can be updated for relationship columns."}, {"path": "docs/products/databases/rows", "title": "Rows", "description": "Master row management with Appwrite Databases. Learn how to create, update, upsert, and query rows within your tables for dynamic data storage.", "content": "Each piece of data or information in Appwrite Databases is a row.\nRows have a structure defined by the parent table.\n\n# Create rows {% #create-rows %}\n{% info title=\"Permissions required\" %}\nYou must grant _create_ permissions to users at the _table level_ before users can create rows.\n[Learn more about permissions](#permissions)\n{% /info %}\n\nIn most use cases, you will create rows programmatically.\n\n{% multicode %}\n```client-web\nimport { Client, ID, TablesDB } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nconst tablesDB = new TablesDB(client);\n\nconst promise = tablesDB.createRow({\n databaseId: '',\n tableId: '',\n rowId: ID.unique(),\n data: {}\n});\n\npromise.then(function (response) {\n console.log(response);\n}, function (error) {\n console.log(error);\n});\n```\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nvoid main() async {\n final client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\n final tablesDB = TablesDB(client);\n\n try {\n final row = tablesDB.createRow(\n databaseId: '',\n tableId: '',\n rowId: ID.unique(),\n data: {}\n );\n } on AppwriteException catch(e) {\n print(e);\n }\n}\n```\n```client-apple\nimport Appwrite\nimport AppwriteModels\n\nfunc main() async throws {\n let client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\n let tablesDB = TablesDB(client)\n\n do {\n let row = try await tablesDB.createRow(\n databaseId: \"\",\n tableId: \"\",\n rowId: ID.unique(),\n data: [:]\n )\n } catch {\n print(error.localizedDescription)\n }\n}\n```\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.TablesDB\n\nsuspend fun main() {\n val client = Client(applicationContext)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\n val tablesDB = TablesDB(client)\n\n try {\n val row = tablesDB.createRow(\n databaseId = \"\",\n tableId = \"\",\n rowId = ID.unique(),\n data = mapOf(\"a\" to \"b\"),\n )\n } catch (e: AppwriteException) {\n Log.e(\"Appwrite\", \"Error: \" + e.message)\n }\n}\n```\n```graphql\nmutation {\n tablesCreateRow(\n databaseId: \"\",\n tableId: \"\",\n rowId: \"\",\n data: \"{}\"\n ) {\n _id\n _tableId\n _databaseId\n _createdAt\n _updatedAt\n _permissions\n data\n }\n}\n```\n{% /multicode %}\n\nDuring testing, you might prefer to create rows in the Appwrite Console.\nTo do so, navigate to the **Rows** tab of your table and click the **Add row** button.\n\n# List rows {% #list-rows %}\n\n{% info title=\"Permissions required\" %}\nYou must grant _read_ permissions to users at the _table level_ before users can read rows.\n[Learn more about permissions](#permissions)\n{% /info %}\n\nRows can be retrieved using the [List rows](/docs/references/cloud/client-web/tables#listRows) endpoint.\n\nResults can be filtered, sorted, and paginated using Appwrite's shared set of query methods.\nYou can find a full guide on querying in the [Queries Guide](/docs/products/databases/queries).\n\nBy default, results are limited to the _first 25 items_.\nYou can change this through [pagination](/docs/products/databases/pagination).\n\n{% info title=\"Speed up lists by skipping totals\" %}\nIf 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`.\nThis reduces latency for large tables and filtered queries. Learn more in [Pagination: Skip totals](/docs/products/databases/pagination#skip-totals).\n{% /info %}\n\n{% multicode %}\n```client-web\nimport { Client, Query, TablesDB } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\nconst tablesDB = new TablesDB(client);\n\nlet promise = tablesDB.listRows({\n databaseId: \"\",\n tableId: \"\",\n queries: [\n Query.equal('title', 'Avatar')\n ]\n});\n\npromise.then(function (response) {\n console.log(response);\n}, function (error) {\n console.log(error);\n});\n```\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nvoid main() async {\n final client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\n final tablesDB = TablesDB(client);\n\n try {\n final rows = await tablesDB.listRows(\n databaseId: '',\n tableId: '',\n queries: [\n Query.equal('title', 'Avatar')\n ]\n );\n } on AppwriteException catch(e) {\n print(e);\n }\n}\n```\n```client-apple\nimport Appwrite\nimport AppwriteModels\n\nfunc main() async throws {\n let client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\n let tablesDB = TablesDB(client)\n\n do {\n let rows = try await tablesDB.listRows(\n databaseId: \"\",\n tableId: \"\",\n queries: [\n Query.equal(\"title\", value: \"Avatar\")\n ]\n )\n } catch {\n print(error.localizedDescription)\n }\n}\n```\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.Query\nimport io.appwrite.services.TablesDB\n\nsuspend fun main() {\n val client = Client(applicationContext)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\n val tablesDB = TablesDB(client)\n\n try {\n val rows = tablesDB.listRows(\n databaseId = \"\",\n tableId = \"\",\n queries = listOf(\n Query.equal(\"title\", \"Avatar\")\n )\n )\n } catch (e: AppwriteException) {\n Log.e(\"Appwrite\", \"Error: \" + e.message)\n }\n}\n```\n```graphql\nquery {\n tablesListRows(\n databaseId: \"\",\n tableId: \"\",\n queries: [\"equal(\\\"title\\\", [\\\"Avatar\\\"])\"]\n ) {\n total\n rows {\n _id\n data\n }\n }\n}\n```\n{% /multicode %}\n\n# Cache list responses {% #cache-list-responses %}\n\nYou 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.\n\nSet `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`.\n\n{% multicode %}\n```client-web\nimport { Client, Query, TablesDB } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\");\n\nconst tablesDB = new TablesDB(client);\n\nconst rows = await tablesDB.listRows({\n databaseId: \"\",\n tableId: \"\",\n queries: [\n Query.equal('title', 'Avatar')\n ],\n ttl: 60 // Cache for 60 seconds\n});\n```\n```server-nodejs\nconst sdk = require('node-appwrite');\n\nconst client = new sdk.Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nconst tablesDB = new sdk.TablesDB(client);\n\nconst rows = await tablesDB.listRows({\n databaseId: '',\n tableId: '',\n queries: [\n sdk.Query.equal('title', 'Avatar')\n ],\n ttl: 60 // Cache for 60 seconds\n});\n```\n```server-python\nfrom appwrite.client import Client\nfrom appwrite.services.tables_db import TablesDB\nfrom appwrite.query import Query\n\nclient = Client()\nclient.set_endpoint('https://.cloud.appwrite.io/v1')\nclient.set_project('')\nclient.set_key('')\n\ntables_db = TablesDB(client)\n\nrows = tables_db.list_rows(\n database_id='',\n table_id='',\n queries=[\n Query.equal('title', 'Avatar')\n ],\n ttl=60 # Cache for 60 seconds\n)\n```\n```server-ruby\nrequire 'appwrite'\n\nclient = Appwrite::Client.new\n .set_endpoint('https://.cloud.appwrite.io/v1')\n .set_project('')\n .set_key('')\n\ntables_db = Appwrite::TablesDB.new(client)\n\nrows = tables_db.list_rows(\n database_id: '',\n table_id: '',\n queries: [\n Appwrite::Query.equal('title', 'Avatar')\n ],\n ttl: 60 # Cache for 60 seconds\n)\n```\n```server-deno\nimport { Client, Query, TablesDB } from \"https://deno.land/x/appwrite/mod.ts\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nconst tablesDB = new TablesDB(client);\n\nconst rows = await tablesDB.listRows({\n databaseId: '',\n tableId: '',\n queries: [\n Query.equal('title', 'Avatar')\n ],\n ttl: 60 // Cache for 60 seconds\n});\n```\n```server-php\nsetEndpoint('https://.cloud.appwrite.io/v1')\n ->setProject('')\n ->setKey('');\n\n$tablesDB = new TablesDB($client);\n\n$rows = $tablesDB->listRows(\n databaseId: '',\n tableId: '',\n queries: [\n Query::equal('title', ['Avatar'])\n ],\n ttl: 60 // Cache for 60 seconds\n);\n```\n```server-go\npackage main\n\nimport (\n \"fmt\"\n \"github.com/appwrite/sdk-for-go/client\"\n \"github.com/appwrite/sdk-for-go/tablesdb\"\n \"github.com/appwrite/sdk-for-go/query\"\n)\n\nfunc main() {\n clt := client.New(\n client.WithEndpoint(\"https://.cloud.appwrite.io/v1\"),\n client.WithProject(\"\"),\n client.WithKey(\"\"),\n )\n\n tablesDB := tablesdb.New(clt)\n\n rows, err := tablesDB.ListRows(\n \"\",\n \"\",\n tablesDB.WithListRowsQueries([]string{\n query.Equal(\"title\", []interface{}{\"Avatar\"}),\n }),\n tablesDB.WithListRowsTtl(60), // Cache for 60 seconds\n )\n\n if err != nil {\n fmt.Println(err)\n }\n _ = rows\n}\n```\n```server-swift\nimport Appwrite\nimport AppwriteModels\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\")\n\nlet tablesDB = TablesDB(client)\n\nlet rows = try await tablesDB.listRows(\n databaseId: \"\",\n tableId: \"\",\n queries: [\n Query.equal(\"title\", value: \"Avatar\")\n ],\n ttl: 60 // Cache for 60 seconds\n)\n```\n```server-kotlin\nimport io.appwrite.Client\nimport io.appwrite.Query\nimport io.appwrite.services.TablesDB\n\nsuspend fun main() {\n val client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\")\n\n val tablesDB = TablesDB(client)\n\n val rows = tablesDB.listRows(\n databaseId = \"\",\n tableId = \"\",\n queries = listOf(\n Query.equal(\"title\", \"Avatar\")\n ),\n ttl = 60 // Cache for 60 seconds\n )\n}\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::TablesDB;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box> {\n let client = Client::new();\n client.set_endpoint(\"https://.cloud.appwrite.io/v1\");\n client.set_project(\"\");\n client.set_key(\"\");\n\n let tables_db = TablesDB::new(&client);\n\n let rows = tables_db.list_rows(\n \"\",\n \"\",\n Some(vec![\n \"equal(\\\"title\\\", [\\\"Avatar\\\"])\".to_string(),\n ]),\n None, // transaction_id\n None, // total\n Some(60), // ttl - Cache for 60 seconds\n ).await?;\n\n let _ = rows;\n Ok(())\n}\n```\n```server-dotnet\nusing Appwrite;\nusing Appwrite.Models;\nusing Appwrite.Services;\n\nClient client = new Client()\n .SetEndPoint(\"https://.cloud.appwrite.io/v1\")\n .SetProject(\"\")\n .SetKey(\"\");\n\nTablesDB tablesDB = new TablesDB(client);\n\nRowList rows = await tablesDB.ListRows(\n databaseId: \"\",\n tableId: \"\",\n queries: new List {\n Query.Equal(\"title\", new List { \"Avatar\" })\n },\n ttl: 60 // Cache for 60 seconds\n);\n```\n```server-dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\n\nClient client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nTablesDB tablesDB = TablesDB(client);\n\nRowList rows = await tablesDB.listRows(\n databaseId: '',\n tableId: '',\n queries: [\n Query.equal('title', 'Avatar')\n ],\n ttl: 60, // Cache for 60 seconds\n);\n```\n```server-java\nimport io.appwrite.Client;\nimport io.appwrite.Query;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.TablesDB;\n\nClient client = new Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\");\n\nTablesDB tablesDB = new TablesDB(client);\n\ntablesDB.listRows(\n \"\",\n \"\",\n List.of(Query.equal(\"title\", List.of(\"Avatar\"))),\n null, // transactionId\n null, // total\n 60, // ttl - Cache for 60 seconds\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n System.out.println(result);\n })\n);\n```\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nvoid main() async {\n final client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\");\n\n final tablesDB = TablesDB(client);\n\n final rows = await tablesDB.listRows(\n databaseId: '',\n tableId: '',\n queries: [\n Query.equal('title', 'Avatar')\n ],\n ttl: 60, // Cache for 60 seconds\n );\n}\n```\n```client-apple\nimport Appwrite\nimport AppwriteModels\n\nfunc main() async throws {\n let client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\n let tablesDB = TablesDB(client)\n\n let rows = try await tablesDB.listRows(\n databaseId: \"\",\n tableId: \"\",\n queries: [\n Query.equal(\"title\", value: \"Avatar\")\n ],\n ttl: 60 // Cache for 60 seconds\n )\n}\n```\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.Query\nimport io.appwrite.services.TablesDB\n\nsuspend fun main() {\n val client = Client(applicationContext)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\n val tablesDB = TablesDB(client)\n\n val rows = tablesDB.listRows(\n databaseId = \"\",\n tableId = \"\",\n queries = listOf(\n Query.equal(\"title\", \"Avatar\")\n ),\n ttl = 60 // Cache for 60 seconds\n )\n}\n```\n```graphql\nquery {\n tablesListRows(\n databaseId: \"\",\n tableId: \"\",\n queries: [\"equal(\\\"title\\\", [\\\"Avatar\\\"])\"],\n ttl: 60\n ) {\n total\n rows {\n _id\n data\n }\n }\n}\n```\n{% /multicode %}\n\n## Purge cache {% #purge-cache %}\n\nRow 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.\n\nTo force an immediate cache purge, call `updateTable` with `purge` set to `true` using a [Server SDK](/docs/sdks#server).\n\n{% multicode %}\n```server-nodejs\nconst sdk = require('node-appwrite');\n\nconst client = new sdk.Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nconst tablesDB = new sdk.TablesDB(client);\n\nawait tablesDB.updateTable({\n databaseId: '',\n tableId: '',\n purge: true\n});\n```\n```server-python\nfrom appwrite.client import Client\nfrom appwrite.services.tables_db import TablesDB\n\nclient = Client()\nclient.set_endpoint('https://.cloud.appwrite.io/v1')\nclient.set_project('')\nclient.set_key('')\n\ntables_db = TablesDB(client)\n\ntables_db.update_table(\n database_id='',\n table_id='',\n purge=True\n)\n```\n```server-php\nsetEndpoint('https://.cloud.appwrite.io/v1')\n ->setProject('')\n ->setKey('');\n\n$tablesDB = new TablesDB($client);\n\n$tablesDB->updateTable(\n databaseId: '',\n tableId: '',\n purge: true\n);\n```\n```server-ruby\nrequire 'appwrite'\n\nclient = Appwrite::Client.new\n .set_endpoint('https://.cloud.appwrite.io/v1')\n .set_project('')\n .set_key('')\n\ntables_db = Appwrite::TablesDB.new(client)\n\ntables_db.update_table(\n database_id: '',\n table_id: '',\n purge: true\n)\n```\n```server-deno\nimport { Client, TablesDB } from \"https://deno.land/x/appwrite/mod.ts\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nconst tablesDB = new TablesDB(client);\n\nawait tablesDB.updateTable({\n databaseId: '',\n tableId: '',\n purge: true\n});\n```\n```server-dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nfinal tablesDB = TablesDB(client);\n\nawait tablesDB.updateTable(\n databaseId: '',\n tableId: '',\n purge: true,\n);\n```\n```server-swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\")\n\nlet tablesDB = TablesDB(client)\n\nlet _ = try await tablesDB.updateTable(\n databaseId: \"\",\n tableId: \"\",\n purge: true\n)\n```\n```server-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.TablesDB\n\nsuspend fun main() {\n val client = Client(applicationContext)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\")\n\n val tablesDB = TablesDB(client)\n\n tablesDB.updateTable(\n databaseId = \"\",\n tableId = \"\",\n purge = true\n )\n}\n```\n```server-go\npackage main\n\nimport (\n \"github.com/appwrite/sdk-for-go/client\"\n \"github.com/appwrite/sdk-for-go/tablesdb\"\n)\n\nfunc main() {\n clt := client.New(\n client.WithEndpoint(\"https://.cloud.appwrite.io/v1\"),\n client.WithProject(\"\"),\n client.WithKey(\"\"),\n )\n\n tablesDB := tablesdb.New(clt)\n\n tablesDB.UpdateTable(\n \"\",\n \"\",\n tablesDB.WithUpdateTablePurge(true),\n )\n}\n```\n```server-java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.TablesDB;\n\nClient client = new Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\");\n\nTablesDB tablesDB = new TablesDB(client);\n\ntablesDB.updateTable(\n \"\",\n \"\",\n null, // name\n null, // permissions\n null, // rowSecurity\n null, // enabled\n true, // purge\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n System.out.println(result);\n })\n);\n```\n```server-dotnet\nusing Appwrite;\nusing Appwrite.Services;\n\nvar client = new Client()\n .SetEndpoint(\"https://.cloud.appwrite.io/v1\")\n .SetProject(\"\")\n .SetKey(\"\");\n\nvar tablesDB = new TablesDB(client);\n\nawait tablesDB.UpdateTable(\n databaseId: \"\",\n tableId: \"\",\n purge: true\n);\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::tables_db::TablesDB;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box> {\n let client = Client::new()\n .set_endpoint(\"https://.cloud.appwrite.io/v1\")\n .set_project(\"\")\n .set_key(\"\");\n\n let tables_db = TablesDB::new(&client);\n\n tables_db.update_table(\n \"\",\n \"\",\n None, // name\n None, // permissions\n None, // row_security\n None, // enabled\n Some(true), // purge\n ).await?;\n\n Ok(())\n}\n```\n{% /multicode %}\n\n# Update row {% #update-row %}\n{% info title=\"Permissions required\" %}\nYou must grant _update_ permissions to users at the _table level_ or _row level_ before users can update rows.\n[Learn more about permissions](#permissions)\n{% /info %}\n\nIn most use cases, you will update rows programmatically.\n\n{% multicode %}\n```client-web\nimport { Client, TablesDB } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nconst tablesDB = new TablesDB(client);\n\nconst promise = tablesDB.updateRow(\n '',\n '',\n '',\n { title: 'Updated Title' }\n);\n\npromise.then(function (response) {\n console.log(response);\n}, function (error) {\n console.log(error);\n});\n```\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nvoid main() async {\n final client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\n final tablesDB = TablesDB(client);\n\n try {\n final row = await tablesDB.updateRow(\n databaseId: '',\n tableId: '',\n rowId: '',\n data: { 'title': 'Updated Title' }\n );\n } on AppwriteException catch(e) {\n print(e);\n }\n}\n```\n```client-apple\nimport Appwrite\nimport AppwriteModels\n\nfunc main() async throws {\n let client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\n let tablesDB = TablesDB(client)\n\n do {\n let row = try await tablesDB.updateRow(\n databaseId: \"\",\n tableId: \"\",\n rowId: \"\",\n data: [\"title\": \"Updated Title\"]\n )\n } catch {\n print(error.localizedDescription)\n }\n}\n```\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.TablesDB\n\nsuspend fun main() {\n val client = Client(applicationContext)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\n val tablesDB = TablesDB(client)\n\n try {\n val row = tablesDB.updateRow(\n databaseId = \"\",\n tableId = \"\",\n rowId = \"\",\n data = mapOf(\"title\" to \"Updated Title\"),\n )\n } catch (e: AppwriteException) {\n Log.e(\"Appwrite\", \"Error: \" + e.message)\n }\n}\n```graphql\nmutation {\n tablesDBUpdateRow(\n databaseId: \"\",\n tableId: \"\",\n rowId: \"\",\n data: \"{\\\"title\\\": \\\"Updated Title\\\"}\"\n ) {\n _id\n _tableId\n _databaseId\n _createdAt\n _updatedAt\n _permissions\n data\n }\n}\n```\n{% /multicode %}\n\n# Upsert rows {% #upsert-rows %}\n\nUpsert 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.\n\nIn most use cases, you will upsert rows programmatically.\n\n{% info title=\"Permissions required\" %}\nYou 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.\n[Learn more about permissions](#permissions)\n{% /info %}\n{% multicode %}\n```client-web\nimport { Client, ID, TablesDB } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nconst tablesDB = new TablesDB(client);\n\nconst promise = tablesDB.upsertRow(\n '',\n '',\n ID.unique(),\n {}\n);\n\npromise.then(function (response) {\n console.log(response);\n}, function (error) {\n console.log(error);\n});\n```\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nvoid main() async {\n final client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\n final tablesDB = TablesDB(client);\n\n try {\n final row = tablesDB.upsertRow(\n databaseId: '',\n tableId: '',\n rowId: ID.unique(),\n data: {}\n );\n } on AppwriteException catch(e) {\n print(e);\n }\n}\n```\n```client-apple\nimport Appwrite\nimport AppwriteModels\n\nfunc main() async throws {\n let client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\n let tablesDB = TablesDB(client)\n\n do {\n let row = try await tablesDB.upsertRow(\n databaseId: \"\",\n tableId: \"\",\n rowId: ID.unique(),\n data: [:]\n )\n } catch {\n print(error.localizedDescription)\n }\n}\n```\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.TablesDB\n\nsuspend fun main() {\n val client = Client(applicationContext)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n\n val tablesDB = TablesDB(client)\n\n try {\n val row = tablesDB.upsertRow(\n databaseId = \"\",\n tableId = \"\",\n rowId = ID.unique(),\n data = mapOf(\"a\" to \"b\"),\n )\n } catch (e: AppwriteException) {\n Log.e(\"Appwrite\", \"Error: \" + e.message)\n }\n}\n```\n```graphql\nmutation {\n tablesUpsertRow(\n databaseId: \"\",\n tableId: \"\",\n rowId: \"\",\n data: \"{}\"\n ) {\n _id\n _tableId\n _databaseId\n _createdAt\n _updatedAt\n _permissions\n data\n }\n}\n```\n{% /multicode %}\n\n# Type safety with models {% #type-safety %}\n\nMobile 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.\n\n## Define your model\n\nCreate a data class or struct that matches your table structure:\n\n{% tabs %}\n{% tabsitem #kotlin title=\"Kotlin/Java\" %}\n```kotlin\ndata class Book(\n val title: String,\n val author: String,\n val publishedYear: Int? = null,\n val genre: List? = null,\n val isAvailable: Boolean = true\n)\n```\n{% /tabsitem %}\n\n{% tabsitem #swift title=\"Swift\" %}\n```swift\nstruct Book: Codable {\n let title: String\n let author: String\n let publishedYear: Int?\n let genre: [String]?\n let isAvailable: Bool\n}\n```\n{% /tabsitem %}\n\n{% tabsitem #web title=\"Web/Node\" %}\n```typescript\ninterface Book {\n title: string;\n author: string;\n publishedYear?: number;\n genre?: string[];\n isAvailable: boolean;\n}\n```\n{% /tabsitem %}\n{% /tabs %}\n\n## Using type-safe operations\n\nUse the `nestedType` parameter for full type safety in native SDKs, or generics in web SDKs:\n\n{% multicode %}\n```client-android-kotlin\nval tablesDB = TablesDB(client)\n\ntry {\n // Create with type safety\n val newBook = tablesDB.createRow(\n databaseId = \"\",\n tableId = \"\",\n rowId = ID.unique(),\n data = mapOf(\n \"title\" to \"The Great Gatsby\",\n \"author\" to \"F. Scott Fitzgerald\",\n \"isAvailable\" to true\n ),\n nestedType = Book::class.java\n )\n\n // List with type safety\n val books = tablesDB.listRows(\n databaseId = \"\",\n tableId = \"\",\n nestedType = Book::class.java\n )\n\n // Now you have full type safety\n for (book in books.rows) {\n Log.d(\"Appwrite\", \"Book: ${book.title} by ${book.author}\")\n if (book.isAvailable) {\n Log.d(\"Appwrite\", \"Available for checkout\")\n }\n }\n} catch (e: AppwriteException) {\n Log.e(\"Appwrite\", \"Error: ${e.message}\")\n}\n```\n```client-apple\nlet tablesDB = TablesDB(client)\n\ndo {\n // Create with type safety\n let newBook = try await tablesDB.createRow(\n databaseId: \"\",\n tableId: \"\",\n rowId: ID.unique(),\n data: [\n \"title\": \"The Great Gatsby\",\n \"author\": \"F. Scott Fitzgerald\",\n \"isAvailable\": true\n ],\n nestedType: Book.self\n )\n\n // List with type safety\n let books = try await tablesDB.listRows(\n databaseId: \"\",\n tableId: \"\",\n nestedType: Book.self\n )\n\n // Now you have full type safety\n for book in books.rows {\n print(\"Book: \\(book.title) by \\(book.author)\")\n if book.isAvailable {\n print(\"Available for checkout\")\n }\n }\n} catch {\n print(error.localizedDescription)\n}\n```\n```client-web\nconst tablesDB = new TablesDB(client);\n\ntry {\n // Create with generics\n const newBook = await tablesDB.createRow({\n databaseId: '',\n tableId: '',\n rowId: ID.unique(),\n data: {\n title: \"The Great Gatsby\",\n author: \"F. Scott Fitzgerald\",\n isAvailable: true\n }\n });\n\n // List with generics\n const books = await tablesDB.listRows({\n databaseId: '',\n tableId: ''\n });\n\n // TypeScript provides full type safety\n books.rows.forEach(book => {\n console.log(`Book: ${book.title} by ${book.author}`);\n if (book.isAvailable) {\n console.log(\"Available for checkout\");\n }\n });\n} catch (error) {\n console.log(error);\n}\n```\n{% /multicode %}\n\n## Model methods\n\nModels returned by native SDKs include helpful utility methods:\n\n{% tabs %}\n{% tabsitem #kotlin-methods title=\"Kotlin/Java\" %}\n```kotlin\nval book = books.rows.first()\n\n// Convert model to Map for debugging or manual manipulation\nval bookMap = book.toMap()\nLog.d(\"Appwrite\", \"Book data: ${bookMap}\")\n\n// Create model instance from Map data\nval bookData = mapOf(\n \"title\" to \"1984\",\n \"author\" to \"George Orwell\",\n \"isAvailable\" to false\n)\nval newBook = Book.from(bookData, Book::class.java)\n\n// JSON serialization using Gson (used internally by SDK)\nimport com.google.gson.Gson\nval gson = Gson()\nval jsonString = gson.toJson(book)\nval bookFromJson = gson.fromJson(jsonString, Book::class.java)\n```\n{% /tabsitem %}\n\n{% tabsitem #swift-methods title=\"Swift\" %}\n```swift\nlet book = books.rows.first!\n\n// Convert model to dictionary for debugging\nlet bookMap = book.toMap()\nprint(\"Book data: \\(bookMap)\")\n\n// Create model instance from dictionary\nlet bookData: [String: Any] = [\n \"title\": \"1984\",\n \"author\": \"George Orwell\",\n \"isAvailable\": false\n]\nlet newBook = Book.from(map: bookData)\n\n// JSON encoding using Swift's Codable\nlet jsonData = try JSONEncoder().encode(book)\nlet jsonString = String(data: jsonData, encoding: .utf8)\n\n// JSON decoding\nif let jsonString = jsonString,\n let data = jsonString.data(using: .utf8) {\n let bookFromJson = try JSONDecoder().decode(Book.self, from: data)\n}\n```\n{% /tabsitem %}\n{% /tabs %}\n\n{% info title=\"Generate types automatically\" %}\nYou can automatically generate model definitions for your tables using the [Appwrite CLI](/docs/products/databases/type-generation). Run `appwrite types collection` to generate types based on your database schema.\n{% /info %}\n\n# Permissions {% #permissions %}\nIn Appwrite, permissions can be granted at the table level and the row level.\nBefore a user can create a row, you need to grant create permissions to the user.\n\nRead, update, and delete permissions can be granted at both the table and row level.\nUsers only need to be granted access at either the table or row level to access rows.\n\n[Learn about configuring permissions](/docs/products/databases/permissions).\n\n# Use transactions {% #use-transactions %}\n\nAll 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).\n\n{% multicode %}\n```client-web\n// Create row inside a transaction\nawait tablesDB.createRow({\n databaseId: '',\n tableId: '',\n rowId: '',\n data: { title: 'Draft' },\n transactionId: ''\n});\n\n// Update row inside a transaction\nawait tablesDB.updateRow({\n databaseId: '',\n tableId: '',\n rowId: '',\n data: { title: 'Published' },\n transactionId: ''\n});\n```\n```client-flutter\n// Create row inside a transaction\nawait tablesDB.createRow(\n databaseId: '',\n tableId: '',\n rowId: '',\n data: { 'title': 'Draft' },\n transactionId: ''\n);\n\n// Update row inside a transaction\nawait tablesDB.updateRow(\n databaseId: '',\n tableId: '',\n rowId: '',\n data: { 'title': 'Published' },\n transactionId: ''\n);\n```\n```client-apple\n// Create row inside a transaction\nlet _ = try await tablesDB.createRow(\n databaseId: \"\",\n tableId: \"\",\n rowId: \"\",\n data: [\"title\": \"Draft\"],\n transactionId: \"\"\n)\n\n// Update row inside a transaction\nlet _2 = try await tablesDB.updateRow(\n databaseId: \"\",\n tableId: \"\",\n rowId: \"\",\n data: [\"title\": \"Published\"],\n transactionId: \"\"\n)\n```\n```client-android-kotlin\n// Create row inside a transaction\nval _ = tablesDB.createRow(\n databaseId = \"\",\n tableId = \"\",\n rowId = \"\",\n data = mapOf(\"title\" to \"Draft\"),\n transactionId = \"\"\n)\n\n// Update row inside a transaction\nval _2 = tablesDB.updateRow(\n databaseId = \"\",\n tableId = \"\",\n rowId = \"\",\n data = mapOf(\"title\" to \"Published\"),\n transactionId = \"\"\n)\n```\n```client-android-java\n// Create row inside a transaction (asynchronous)\nMap data = new HashMap<>();\ndata.put(\"title\", \"Draft\");\n\ntablesDB.createRow(\n \"\",\n \"\",\n \"\",\n data,\n \"\",\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return null;\n }\n System.out.println(result);\n return null;\n })\n);\n\n// Update row inside a transaction (asynchronous)\nMap update = new HashMap<>();\nupdate.put(\"title\", \"Published\");\n\ntablesDB.updateRow(\n \"\",\n \"\",\n \"\",\n update,\n \"\",\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return null;\n }\n System.out.println(result);\n return null;\n })\n);\n```\n```client-react-native\n// Create row inside a transaction\nawait tablesDB.createRow({\n databaseId: '',\n tableId: '',\n rowId: '',\n data: { title: 'Draft' },\n transactionId: ''\n});\n\n// Update row inside a transaction\nawait tablesDB.updateRow({\n databaseId: '',\n tableId: '',\n rowId: '',\n data: { title: 'Published' },\n transactionId: ''\n});\n```\n```server-nodejs\n// Delete row inside a transaction\nawait tablesDB.deleteRow({\n databaseId: '',\n tableId: '',\n rowId: '',\n transactionId: ''\n});\n```\n```server-deno\n// Delete row inside a transaction\nawait tablesDB.deleteRow({\n databaseId: '',\n tableId: '',\n rowId: '',\n transactionId: ''\n});\n```\n```server-python\n# Delete row inside a transaction\ntablesDB.delete_row(\n database_id = '',\n table_id = '',\n row_id = '',\n transaction_id = ''\n)\n```\n```server-php\n// Delete row inside a transaction\n$tablesDB->deleteRow(\n databaseId: '',\n tableId: '',\n rowId: '',\n transactionId: ''\n);\n```\n```server-ruby\n# Delete row inside a transaction\ntablesDB.delete_row(\n database_id: '',\n table_id: '',\n row_id: '',\n transaction_id: ''\n)\n```\n```server-dotnet\n// Delete row inside a transaction\nawait tablesDB.DeleteRow(\n databaseId: \"\",\n tableId: \"\",\n rowId: \"\",\n transactionId: \"\"\n);\n```\n```server-dart\n// Delete row inside a transaction\nawait tablesDB.deleteRow(\n databaseId: '',\n tableId: '',\n rowId: '',\n transactionId: ''\n);\n```\n```server-swift\n// Delete row inside a transaction\nlet _ = try await tablesDB.deleteRow(\n databaseId: \"\",\n tableId: \"\",\n rowId: \"\",\n transactionId: \"\"\n)\n```\n```server-kotlin\n// Delete row inside a transaction\nval _ = tablesDB.deleteRow(\n databaseId = \"\",\n tableId = \"\",\n rowId = \"\",\n transactionId = \"\"\n)\n```\n```server-java\n// Delete row inside a transaction (asynchronous)\ntablesDB.deleteRow(\n \"\",\n \"\",\n \"\",\n \"\",\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return null;\n }\n System.out.println(result);\n return null;\n })\n);\n```\n\n```server-rust\n// Delete row inside a transaction\nuse appwrite::Client;\nuse appwrite::services::tables_db::TablesDB;\n\nlet client = Client::new()\n .set_endpoint(\"https://.cloud.appwrite.io/v1\")\n .set_project(\"\")\n .set_key(\"\");\n\nlet tables_db = TablesDB::new(&client);\n\ntables_db.delete_row(\n \"\",\n \"\",\n \"\",\n Some(\"\"),\n).await?;\n```\n{% /multicode %}\n\n# Next steps {% #next-steps %}\n\nContinue learning with these related guides:\n\n{% cards %}\n{% cards_item href=\"/docs/products/databases/queries\" title=\"Queries\" %}\nLearn how to filter, sort, and search your rows with various query operators.\n{% /cards_item %}\n\n{% cards_item href=\"/docs/products/databases/pagination\" title=\"Pagination\" %}\nHandle large datasets by implementing pagination in your row queries.\n{% /cards_item %}\n\n{% cards_item href=\"/docs/products/databases/bulk-operations\" title=\"Bulk operations\" %}\nPerform create, update, and delete operations on multiple rows simultaneously.\n{% /cards_item %}\n\n{% cards_item href=\"/docs/products/databases/timestamp-overrides\" title=\"Timestamp overrides\" %}\nSet custom creation and update timestamps when migrating data or backdating records.\n{% /cards_item %}\n{% /cards %}"}, {"path": "docs/products/databases/tables", "title": "Tables", "description": "Organize your data with Appwrite Tables. Explore how to create and configure tables to store and structure your data effectively.", "content": "Appwrite uses tables as containers of rows. Each tables contains many rows identical in structure.\nThe 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.\n\nThat said, Appwrite is designed to support both SQL and NoSQL database adapters like MariaDB, MySQL, or MongoDB in future versions.\n\n# Create table {% #create-table %}\nYou can create tables using the Appwrite Console, a [Server SDK](/docs/sdks#server), or using the [CLI](/docs/tooling/command-line/installation).\n{% tabs %}\n\n{% tabsitem #console title=\"Console\" %}\nYou can create a table by heading to the **Databases** page, navigate to a [database](/docs/products/databases/databases), and click **Create table**.\n\n{% /tabsitem %}\n\n{% tabsitem #server-sdk title=\"Server SDK\" %}\nYou can also create tables programmatically using a [Server SDK](/docs/sdks#server). Appwrite [Server SDKs](/docs/sdks#server) require an [API key](/docs/advanced/security/api-keys).\n\n{% multicode %}\n\n```server-nodejs\nconst sdk = require('node-appwrite');\n\n// Init SDK\nconst client = new sdk.Client();\n\nconst tablesDB = new sdk.TablesDB(client);\n\nclient\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n;\n\nconst promise = tablesDB.createTable({\n databaseId: '',\n tableId: '',\n name: '',\n columns: [\n {\n key: 'email',\n type: 'email',\n required: true\n },\n {\n key: 'name',\n type: 'varchar',\n size: 255,\n required: true\n },\n {\n key: 'bio',\n type: 'text',\n required: false\n },\n {\n key: 'content',\n type: 'mediumtext',\n required: false\n },\n {\n key: 'data',\n type: 'longtext',\n required: false\n },\n {\n key: 'age',\n type: 'integer',\n required: false\n },\n {\n key: 'score',\n type: 'float',\n required: false\n },\n {\n key: 'total_views',\n type: 'bigint',\n required: false\n },\n {\n key: 'is_active',\n type: 'boolean',\n required: true\n },\n {\n key: 'created_at',\n type: 'datetime',\n required: false\n },\n {\n key: 'status',\n type: 'enum',\n elements: ['draft', 'published', 'archived'],\n required: true\n },\n {\n key: 'ip_address',\n type: 'ip',\n required: false\n },\n {\n key: 'website',\n type: 'url',\n required: false\n },\n {\n key: 'location',\n type: 'point',\n required: false\n },\n {\n key: 'path',\n type: 'line',\n required: false\n },\n {\n key: 'area',\n type: 'polygon',\n required: false\n },\n {\n key: 'related_items',\n type: 'relationship',\n relatedTableId: '',\n relationType: 'manyToMany',\n twoWay: true,\n twoWayKey: 'items',\n onDelete: 'cascade',\n required: false\n }\n ],\n indexes: [\n {\n key: 'idx_email',\n type: 'unique',\n attributes: ['email']\n },\n {\n key: 'idx_name',\n type: 'key',\n attributes: ['name']\n },\n {\n key: 'idx_name_fulltext',\n type: 'fulltext',\n attributes: ['name']\n }\n ]\n});\n\npromise.then(function (response) {\n console.log(response);\n}, function (error) {\n console.log(error);\n});\n```\n```deno\nimport * as sdk from \"npm:node-appwrite\";\n\n// Init SDK\nlet client = new sdk.Client();\n\nlet tablesDB = new sdk.TablesDB(client);\n\nclient\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n;\n\nlet promise = tablesDB.createTable({\n databaseId: '',\n tableId: '',\n name: '',\n columns: [\n {\n key: 'email',\n type: 'email',\n required: true\n },\n {\n key: 'name',\n type: 'varchar',\n size: 255,\n required: true\n },\n {\n key: 'bio',\n type: 'text',\n required: false\n },\n {\n key: 'content',\n type: 'mediumtext',\n required: false\n },\n {\n key: 'data',\n type: 'longtext',\n required: false\n },\n {\n key: 'age',\n type: 'integer',\n required: false\n },\n {\n key: 'score',\n type: 'float',\n required: false\n },\n {\n key: 'total_views',\n type: 'bigint',\n required: false\n },\n {\n key: 'is_active',\n type: 'boolean',\n required: true\n },\n {\n key: 'created_at',\n type: 'datetime',\n required: false\n },\n {\n key: 'status',\n type: 'enum',\n elements: ['draft', 'published', 'archived'],\n required: true\n },\n {\n key: 'ip_address',\n type: 'ip',\n required: false\n },\n {\n key: 'website',\n type: 'url',\n required: false\n },\n {\n key: 'location',\n type: 'point',\n required: false\n },\n {\n key: 'path',\n type: 'line',\n required: false\n },\n {\n key: 'area',\n type: 'polygon',\n required: false\n },\n {\n key: 'related_items',\n type: 'relationship',\n relatedTableId: '',\n relationType: 'manyToMany',\n twoWay: true,\n twoWayKey: 'items',\n onDelete: 'cascade',\n required: false\n }\n ],\n indexes: [\n {\n key: 'idx_email',\n type: 'unique',\n attributes: ['email']\n },\n {\n key: 'idx_name',\n type: 'key',\n attributes: ['name']\n },\n {\n key: 'idx_name_fulltext',\n type: 'fulltext',\n attributes: ['name']\n }\n ]\n});\n\npromise.then(function (response) {\n console.log(response);\n}, function (error) {\n console.log(error);\n});\n```\n```php\nsetEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n ->setProject('') // Your project ID\n ->setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n;\n\n$tablesDB = new TablesDB($client);\n\n$result = $tablesDB->createTable(\n databaseId: '',\n tableId: '',\n name: '',\n columns: [\n [\n 'key' => 'email',\n 'type' => 'email',\n 'required' => true\n ],\n [\n 'key' => 'name',\n 'type' => 'varchar',\n 'size' => 255,\n 'required' => true\n ],\n [\n 'key' => 'bio',\n 'type' => 'text',\n 'required' => false\n ],\n [\n 'key' => 'content',\n 'type' => 'mediumtext',\n 'required' => false\n ],\n [\n 'key' => 'data',\n 'type' => 'longtext',\n 'required' => false\n ],\n [\n 'key' => 'age',\n 'type' => 'integer',\n 'required' => false\n ],\n [\n 'key' => 'score',\n 'type' => 'float',\n 'required' => false\n ],\n [\n 'key' => 'total_views',\n 'type' => 'bigint',\n 'required' => false\n ],\n [\n 'key' => 'is_active',\n 'type' => 'boolean',\n 'required' => true\n ],\n [\n 'key' => 'created_at',\n 'type' => 'datetime',\n 'required' => false\n ],\n [\n 'key' => 'status',\n 'type' => 'enum',\n 'elements' => ['draft', 'published', 'archived'],\n 'required' => true\n ],\n [\n 'key' => 'ip_address',\n 'type' => 'ip',\n 'required' => false\n ],\n [\n 'key' => 'website',\n 'type' => 'url',\n 'required' => false\n ],\n [\n 'key' => 'location',\n 'type' => 'point',\n 'required' => false\n ],\n [\n 'key' => 'path',\n 'type' => 'line',\n 'required' => false\n ],\n [\n 'key' => 'area',\n 'type' => 'polygon',\n 'required' => false\n ],\n [\n 'key' => 'related_items',\n 'type' => 'relationship',\n 'relatedTableId' => '',\n 'relationType' => 'manyToMany',\n 'twoWay' => true,\n 'twoWayKey' => 'items',\n 'onDelete' => 'cascade',\n 'required' => false\n ]\n ],\n indexes: [\n [\n 'key' => 'idx_email',\n 'type' => 'unique',\n 'attributes' => ['email']\n ],\n [\n 'key' => 'idx_name',\n 'type' => 'key',\n 'attributes' => ['name']\n ],\n [\n 'key' => 'idx_name_fulltext',\n 'type' => 'fulltext',\n 'attributes' => ['name']\n ]\n ]\n);\n```\n```python\nfrom appwrite.client import Client\nfrom appwrite.services.tables_db import TablesDB\n\nclient = Client()\n\n(client\n .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('') # Your project ID\n .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key\n)\n\ntablesDB = TablesDB(client)\n\nresult = tablesDB.create_table(\n database_id='',\n table_id='',\n name='',\n columns=[\n {\n 'key': 'email',\n 'type': 'email',\n 'required': True\n },\n {\n 'key': 'name',\n 'type': 'varchar',\n 'size': 255,\n 'required': True\n },\n {\n 'key': 'bio',\n 'type': 'text',\n 'required': False\n },\n {\n 'key': 'content',\n 'type': 'mediumtext',\n 'required': False\n },\n {\n 'key': 'data',\n 'type': 'longtext',\n 'required': False\n },\n {\n 'key': 'age',\n 'type': 'integer',\n 'required': False\n },\n {\n 'key': 'score',\n 'type': 'float',\n 'required': False\n },\n {\n 'key': 'total_views',\n 'type': 'bigint',\n 'required': False\n },\n {\n 'key': 'is_active',\n 'type': 'boolean',\n 'required': True\n },\n {\n 'key': 'created_at',\n 'type': 'datetime',\n 'required': False\n },\n {\n 'key': 'status',\n 'type': 'enum',\n 'elements': ['draft', 'published', 'archived'],\n 'required': True\n },\n {\n 'key': 'ip_address',\n 'type': 'ip',\n 'required': False\n },\n {\n 'key': 'website',\n 'type': 'url',\n 'required': False\n },\n {\n 'key': 'location',\n 'type': 'point',\n 'required': False\n },\n {\n 'key': 'path',\n 'type': 'line',\n 'required': False\n },\n {\n 'key': 'area',\n 'type': 'polygon',\n 'required': False\n },\n {\n 'key': 'related_items',\n 'type': 'relationship',\n 'relatedTableId': '',\n 'relationType': 'manyToMany',\n 'twoWay': True,\n 'twoWayKey': 'items',\n 'onDelete': 'cascade',\n 'required': False\n }\n ],\n indexes=[\n {\n 'key': 'idx_email',\n 'type': 'unique',\n 'attributes': ['email']\n },\n {\n 'key': 'idx_name',\n 'type': 'key',\n 'attributes': ['name']\n },\n {\n 'key': 'idx_name_fulltext',\n 'type': 'fulltext',\n 'attributes': ['name']\n }\n ]\n)\n```\n```ruby\nrequire 'Appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('') # Your project ID\n .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key\n\ntablesDB = TablesDB.new(client)\n\nresponse = tablesDB.create_table(\n database_id: '',\n table_id: '',\n name: '',\n columns: [\n {\n key: 'email',\n type: 'email',\n required: true\n },\n {\n key: 'name',\n type: 'varchar',\n size: 255,\n required: true\n },\n {\n key: 'bio',\n type: 'text',\n required: false\n },\n {\n key: 'content',\n type: 'mediumtext',\n required: false\n },\n {\n key: 'data',\n type: 'longtext',\n required: false\n },\n {\n key: 'age',\n type: 'integer',\n required: false\n },\n {\n key: 'score',\n type: 'float',\n required: false\n },\n {\n key: 'total_views',\n type: 'bigint',\n required: false\n },\n {\n key: 'is_active',\n type: 'boolean',\n required: true\n },\n {\n key: 'created_at',\n type: 'datetime',\n required: false\n },\n {\n key: 'status',\n type: 'enum',\n elements: ['draft', 'published', 'archived'],\n required: true\n },\n {\n key: 'ip_address',\n type: 'ip',\n required: false\n },\n {\n key: 'website',\n type: 'url',\n required: false\n },\n {\n key: 'location',\n type: 'point',\n required: false\n },\n {\n key: 'path',\n type: 'line',\n required: false\n },\n {\n key: 'area',\n type: 'polygon',\n required: false\n },\n {\n key: 'related_items',\n type: 'relationship',\n relatedTableId: '',\n relationType: 'manyToMany',\n twoWay: true,\n twoWayKey: 'items',\n onDelete: 'cascade',\n required: false\n }\n ],\n indexes: [\n {\n key: 'idx_email',\n type: 'unique',\n attributes: ['email']\n },\n {\n key: 'idx_name',\n type: 'key',\n attributes: ['name']\n },\n {\n key: 'idx_name_fulltext',\n type: 'fulltext',\n attributes: ['name']\n }\n ]\n)\n\nputs response.inspect\n```\n```csharp\nusing Appwrite;\nusing Appwrite.Services;\nusing Appwrite.Models;\n\nvar client = new Client()\n .SetEndPoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .SetProject(\"\") // Your project ID\n .SetKey(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\nvar tablesDB = new TablesDB(client);\n\nTable result = await tablesDB.CreateTable(\n databaseId: \"\",\n tableId: \"\",\n name: \"\",\n columns: new List>\n {\n new Dictionary\n {\n { \"key\", \"email\" },\n { \"type\", \"email\" },\n { \"required\", true }\n },\n new Dictionary\n {\n { \"key\", \"name\" },\n { \"type\", \"varchar\" },\n { \"size\", 255 },\n { \"required\", true }\n },\n new Dictionary\n {\n { \"key\", \"bio\" },\n { \"type\", \"text\" },\n { \"required\", false }\n },\n new Dictionary\n {\n { \"key\", \"content\" },\n { \"type\", \"mediumtext\" },\n { \"required\", false }\n },\n new Dictionary\n {\n { \"key\", \"data\" },\n { \"type\", \"longtext\" },\n { \"required\", false }\n },\n new Dictionary\n {\n { \"key\", \"age\" },\n { \"type\", \"integer\" },\n { \"required\", false }\n },\n new Dictionary\n {\n { \"key\", \"score\" },\n { \"type\", \"float\" },\n { \"required\", false }\n },\n new Dictionary\n {\n { \"key\", \"total_views\" },\n { \"type\", \"bigint\" },\n { \"required\", false }\n },\n new Dictionary\n {\n { \"key\", \"is_active\" },\n { \"type\", \"boolean\" },\n { \"required\", true }\n },\n new Dictionary\n {\n { \"key\", \"created_at\" },\n { \"type\", \"datetime\" },\n { \"required\", false }\n },\n new Dictionary\n {\n { \"key\", \"status\" },\n { \"type\", \"enum\" },\n { \"elements\", new List { \"draft\", \"published\", \"archived\" } },\n { \"required\", true }\n },\n new Dictionary\n {\n { \"key\", \"ip_address\" },\n { \"type\", \"ip\" },\n { \"required\", false }\n },\n new Dictionary\n {\n { \"key\", \"website\" },\n { \"type\", \"url\" },\n { \"required\", false }\n },\n new Dictionary\n {\n { \"key\", \"location\" },\n { \"type\", \"point\" },\n { \"required\", false }\n },\n new Dictionary\n {\n { \"key\", \"path\" },\n { \"type\", \"line\" },\n { \"required\", false }\n },\n new Dictionary\n {\n { \"key\", \"area\" },\n { \"type\", \"polygon\" },\n { \"required\", false }\n },\n new Dictionary\n {\n { \"key\", \"related_items\" },\n { \"type\", \"relationship\" },\n { \"relatedTableId\", \"\" },\n { \"relationType\", \"manyToMany\" },\n { \"twoWay\", true },\n { \"twoWayKey\", \"items\" },\n { \"onDelete\", \"cascade\" },\n { \"required\", false }\n }\n },\n indexes: new List>\n {\n new Dictionary\n {\n { \"key\", \"idx_email\" },\n { \"type\", \"unique\" },\n { \"attributes\", new List { \"email\" } }\n },\n new Dictionary\n {\n { \"key\", \"idx_name\" },\n { \"type\", \"key\" },\n { \"attributes\", new List { \"name\" } }\n },\n new Dictionary\n {\n { \"key\", \"idx_name_fulltext\" },\n { \"type\", \"fulltext\" },\n { \"attributes\", new List { \"name\" } }\n }\n });\n```\n```dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\n\nvoid main() { // Init SDK\n Client client = Client();\n Databases tablesDB = TablesDB(client);\n\n client\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n ;\n\n Future result = tablesDB.createTable(\n databaseId: '',\n tableId: '',\n name: '',\n columns: [\n {\n 'key': 'email',\n 'type': 'email',\n 'required': true\n },\n {\n 'key': 'name',\n 'type': 'varchar',\n 'size': 255,\n 'required': true\n },\n {\n 'key': 'bio',\n 'type': 'text',\n 'required': false\n },\n {\n 'key': 'content',\n 'type': 'mediumtext',\n 'required': false\n },\n {\n 'key': 'data',\n 'type': 'longtext',\n 'required': false\n },\n {\n 'key': 'age',\n 'type': 'integer',\n 'required': false\n },\n {\n 'key': 'score',\n 'type': 'float',\n 'required': false\n },\n {\n 'key': 'total_views',\n 'type': 'bigint',\n 'required': false\n },\n {\n 'key': 'is_active',\n 'type': 'boolean',\n 'required': true\n },\n {\n 'key': 'created_at',\n 'type': 'datetime',\n 'required': false\n },\n {\n 'key': 'status',\n 'type': 'enum',\n 'elements': ['draft', 'published', 'archived'],\n 'required': true\n },\n {\n 'key': 'ip_address',\n 'type': 'ip',\n 'required': false\n },\n {\n 'key': 'website',\n 'type': 'url',\n 'required': false\n },\n {\n 'key': 'location',\n 'type': 'point',\n 'required': false\n },\n {\n 'key': 'path',\n 'type': 'line',\n 'required': false\n },\n {\n 'key': 'area',\n 'type': 'polygon',\n 'required': false\n },\n {\n 'key': 'related_items',\n 'type': 'relationship',\n 'relatedTableId': '',\n 'relationType': 'manyToMany',\n 'twoWay': true,\n 'twoWayKey': 'items',\n 'onDelete': 'cascade',\n 'required': false\n }\n ],\n indexes: [\n {\n 'key': 'idx_email',\n 'type': 'unique',\n 'attributes': ['email']\n },\n {\n 'key': 'idx_name',\n 'type': 'key',\n 'attributes': ['name']\n },\n {\n 'key': 'idx_name_fulltext',\n 'type': 'fulltext',\n 'attributes': ['name']\n }\n ],\n );\n\n result\n .then((response) {\n print(response);\n }).catchError((error) {\n print(error.response);\n });\n}\n```\n```kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.TablesDB\n\nval client = Client(context)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\") // Your secret API key\n\nval tablesDB = TablesDB(client)\n\nval response = tablesDB.createTable(\n databaseId = \"\",\n tableId = \"\",\n name = \"\",\n columns = listOf(\n mapOf(\n \"key\" to \"email\",\n \"type\" to \"email\",\n \"required\" to true\n ),\n mapOf(\n \"key\" to \"name\",\n \"type\" to \"varchar\",\n \"size\" to 255,\n \"required\" to true\n ),\n mapOf(\n \"key\" to \"bio\",\n \"type\" to \"text\",\n \"required\" to false\n ),\n mapOf(\n \"key\" to \"content\",\n \"type\" to \"mediumtext\",\n \"required\" to false\n ),\n mapOf(\n \"key\" to \"data\",\n \"type\" to \"longtext\",\n \"required\" to false\n ),\n mapOf(\n \"key\" to \"age\",\n \"type\" to \"integer\",\n \"required\" to false\n ),\n mapOf(\n \"key\" to \"score\",\n \"type\" to \"float\",\n \"required\" to false\n ),\n mapOf(\n \"key\" to \"total_views\",\n \"type\" to \"bigint\",\n \"required\" to false\n ),\n mapOf(\n \"key\" to \"is_active\",\n \"type\" to \"boolean\",\n \"required\" to true\n ),\n mapOf(\n \"key\" to \"created_at\",\n \"type\" to \"datetime\",\n \"required\" to false\n ),\n mapOf(\n \"key\" to \"status\",\n \"type\" to \"enum\",\n \"elements\" to listOf(\"draft\", \"published\", \"archived\"),\n \"required\" to true\n ),\n mapOf(\n \"key\" to \"ip_address\",\n \"type\" to \"ip\",\n \"required\" to false\n ),\n mapOf(\n \"key\" to \"website\",\n \"type\" to \"url\",\n \"required\" to false\n ),\n mapOf(\n \"key\" to \"location\",\n \"type\" to \"point\",\n \"required\" to false\n ),\n mapOf(\n \"key\" to \"path\",\n \"type\" to \"line\",\n \"required\" to false\n ),\n mapOf(\n \"key\" to \"area\",\n \"type\" to \"polygon\",\n \"required\" to false\n ),\n mapOf(\n \"key\" to \"related_items\",\n \"type\" to \"relationship\",\n \"relatedTableId\" to \"\",\n \"relationType\" to \"manyToMany\",\n \"twoWay\" to true,\n \"twoWayKey\" to \"items\",\n \"onDelete\" to \"cascade\",\n \"required\" to false\n )\n ),\n indexes = listOf(\n mapOf(\n \"key\" to \"idx_email\",\n \"type\" to \"unique\",\n \"attributes\" to listOf(\"email\")\n ),\n mapOf(\n \"key\" to \"idx_name\",\n \"type\" to \"key\",\n \"attributes\" to listOf(\"name\")\n ),\n mapOf(\n \"key\" to \"idx_name_fulltext\",\n \"type\" to \"fulltext\",\n \"attributes\" to listOf(\"name\")\n )\n )\n)\n```\n```java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.TablesDB;\nimport java.util.*;\n\nClient client = new Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\nDatabases tablesDB = new TablesDB(client);\n\nList> columns = Arrays.asList(\n new HashMap() {{\n put(\"key\", \"email\");\n put(\"type\", \"email\");\n put(\"required\", true);\n }},\n new HashMap() {{\n put(\"key\", \"name\");\n put(\"type\", \"varchar\");\n put(\"size\", 255);\n put(\"required\", true);\n }},\n new HashMap() {{\n put(\"key\", \"bio\");\n put(\"type\", \"text\");\n put(\"required\", false);\n }},\n new HashMap() {{\n put(\"key\", \"content\");\n put(\"type\", \"mediumtext\");\n put(\"required\", false);\n }},\n new HashMap() {{\n put(\"key\", \"data\");\n put(\"type\", \"longtext\");\n put(\"required\", false);\n }},\n new HashMap() {{\n put(\"key\", \"age\");\n put(\"type\", \"integer\");\n put(\"required\", false);\n }},\n new HashMap() {{\n put(\"key\", \"score\");\n put(\"type\", \"float\");\n put(\"required\", false);\n }},\n new HashMap() {{\n put(\"key\", \"total_views\");\n put(\"type\", \"bigint\");\n put(\"required\", false);\n }},\n new HashMap() {{\n put(\"key\", \"is_active\");\n put(\"type\", \"boolean\");\n put(\"required\", true);\n }},\n new HashMap() {{\n put(\"key\", \"created_at\");\n put(\"type\", \"datetime\");\n put(\"required\", false);\n }},\n new HashMap() {{\n put(\"key\", \"status\");\n put(\"type\", \"enum\");\n put(\"elements\", Arrays.asList(\"draft\", \"published\", \"archived\"));\n put(\"required\", true);\n }},\n new HashMap() {{\n put(\"key\", \"ip_address\");\n put(\"type\", \"ip\");\n put(\"required\", false);\n }},\n new HashMap() {{\n put(\"key\", \"website\");\n put(\"type\", \"url\");\n put(\"required\", false);\n }},\n new HashMap() {{\n put(\"key\", \"location\");\n put(\"type\", \"point\");\n put(\"required\", false);\n }},\n new HashMap() {{\n put(\"key\", \"path\");\n put(\"type\", \"line\");\n put(\"required\", false);\n }},\n new HashMap() {{\n put(\"key\", \"area\");\n put(\"type\", \"polygon\");\n put(\"required\", false);\n }},\n new HashMap() {{\n put(\"key\", \"related_items\");\n put(\"type\", \"relationship\");\n put(\"relatedTableId\", \"\");\n put(\"relationType\", \"manyToMany\");\n put(\"twoWay\", true);\n put(\"twoWayKey\", \"items\");\n put(\"onDelete\", \"cascade\");\n put(\"required\", false);\n }}\n);\n\nList> indexes = Arrays.asList(\n new HashMap() {{\n put(\"key\", \"idx_email\");\n put(\"type\", \"unique\");\n put(\"attributes\", Arrays.asList(\"email\"));\n }},\n new HashMap() {{\n put(\"key\", \"idx_name\");\n put(\"type\", \"key\");\n put(\"attributes\", Arrays.asList(\"name\"));\n }},\n new HashMap() {{\n put(\"key\", \"idx_name_fulltext\");\n put(\"type\", \"fulltext\");\n put(\"attributes\", Arrays.asList(\"name\"));\n }}\n);\n\ntablesDB.createTable(\n \"\",\n \"\",\n \"\",\n columns,\n indexes,\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(result);\n })\n);\n```\n```swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\") // Your secret API key\n\nlet tablesDB = TablesDB(client)\n\nlet table = try await tablesDB.createTable(\n databaseId: \"\",\n tableId: \"\",\n name: \"\",\n columns: [\n [\n \"key\": \"email\",\n \"type\": \"email\",\n \"required\": true\n ],\n [\n \"key\": \"name\",\n \"type\": \"varchar\",\n \"size\": 255,\n \"required\": true\n ],\n [\n \"key\": \"bio\",\n \"type\": \"text\",\n \"required\": false\n ],\n [\n \"key\": \"content\",\n \"type\": \"mediumtext\",\n \"required\": false\n ],\n [\n \"key\": \"data\",\n \"type\": \"longtext\",\n \"required\": false\n ],\n [\n \"key\": \"age\",\n \"type\": \"integer\",\n \"required\": false\n ],\n [\n \"key\": \"score\",\n \"type\": \"float\",\n \"required\": false\n ],\n [\n \"key\": \"total_views\",\n \"type\": \"bigint\",\n \"required\": false\n ],\n [\n \"key\": \"is_active\",\n \"type\": \"boolean\",\n \"required\": true\n ],\n [\n \"key\": \"created_at\",\n \"type\": \"datetime\",\n \"required\": false\n ],\n [\n \"key\": \"status\",\n \"type\": \"enum\",\n \"elements\": [\"draft\", \"published\", \"archived\"],\n \"required\": true\n ],\n [\n \"key\": \"ip_address\",\n \"type\": \"ip\",\n \"required\": false\n ],\n [\n \"key\": \"website\",\n \"type\": \"url\",\n \"required\": false\n ],\n [\n \"key\": \"location\",\n \"type\": \"point\",\n \"required\": false\n ],\n [\n \"key\": \"path\",\n \"type\": \"line\",\n \"required\": false\n ],\n [\n \"key\": \"area\",\n \"type\": \"polygon\",\n \"required\": false\n ],\n [\n \"key\": \"related_items\",\n \"type\": \"relationship\",\n \"relatedTableId\": \"\",\n \"relationType\": \"manyToMany\",\n \"twoWay\": true,\n \"twoWayKey\": \"items\",\n \"onDelete\": \"cascade\",\n \"required\": false\n ]\n ],\n indexes: [\n [\n \"key\": \"idx_email\",\n \"type\": \"unique\",\n \"attributes\": [\"email\"]\n ],\n [\n \"key\": \"idx_name\",\n \"type\": \"key\",\n \"attributes\": [\"name\"]\n ],\n [\n \"key\": \"idx_name_fulltext\",\n \"type\": \"fulltext\",\n \"attributes\": [\"name\"]\n ]\n ]\n)\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::tables_db::TablesDB;\nuse serde_json::json;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box> {\n let client = Client::new()\n .set_endpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .set_project(\"\") // Your project ID\n .set_key(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\n let tables_db = TablesDB::new(&client);\n\n let table = tables_db.create_table(\n \"\",\n \"\",\n \"\",\n None, // permissions\n None, // row_security\n None, // enabled\n Some(vec![\n json!({\n \"key\": \"email\",\n \"type\": \"email\",\n \"required\": true\n }),\n json!({\n \"key\": \"name\",\n \"type\": \"varchar\",\n \"size\": 255,\n \"required\": true\n }),\n json!({\n \"key\": \"bio\",\n \"type\": \"text\",\n \"required\": false\n }),\n json!({\n \"key\": \"content\",\n \"type\": \"mediumtext\",\n \"required\": false\n }),\n json!({\n \"key\": \"data\",\n \"type\": \"longtext\",\n \"required\": false\n }),\n json!({\n \"key\": \"age\",\n \"type\": \"integer\",\n \"required\": false\n }),\n json!({\n \"key\": \"score\",\n \"type\": \"float\",\n \"required\": false\n }),\n json!({\n \"key\": \"total_views\",\n \"type\": \"bigint\",\n \"required\": false\n }),\n json!({\n \"key\": \"is_active\",\n \"type\": \"boolean\",\n \"required\": true\n }),\n json!({\n \"key\": \"created_at\",\n \"type\": \"datetime\",\n \"required\": false\n }),\n json!({\n \"key\": \"status\",\n \"type\": \"enum\",\n \"elements\": [\"draft\", \"published\", \"archived\"],\n \"required\": true\n }),\n json!({\n \"key\": \"ip_address\",\n \"type\": \"ip\",\n \"required\": false\n }),\n json!({\n \"key\": \"website\",\n \"type\": \"url\",\n \"required\": false\n }),\n json!({\n \"key\": \"location\",\n \"type\": \"point\",\n \"required\": false\n }),\n json!({\n \"key\": \"path\",\n \"type\": \"line\",\n \"required\": false\n }),\n json!({\n \"key\": \"area\",\n \"type\": \"polygon\",\n \"required\": false\n }),\n json!({\n \"key\": \"related_items\",\n \"type\": \"relationship\",\n \"relatedTableId\": \"\",\n \"relationType\": \"manyToMany\",\n \"twoWay\": true,\n \"twoWayKey\": \"items\",\n \"onDelete\": \"cascade\",\n \"required\": false\n }),\n ]),\n Some(vec![\n json!({\n \"key\": \"idx_email\",\n \"type\": \"unique\",\n \"attributes\": [\"email\"]\n }),\n json!({\n \"key\": \"idx_name\",\n \"type\": \"key\",\n \"attributes\": [\"name\"]\n }),\n json!({\n \"key\": \"idx_name_fulltext\",\n \"type\": \"fulltext\",\n \"attributes\": [\"name\"]\n }),\n ]),\n ).await?;\n\n println!(\"{:?}\", table);\n Ok(())\n}\n```\n{% /multicode %}\n\nYou can also configure **permissions** in the `createTable` method. Learn more about the `createTable` method in the [API references](/docs/references).\n{% /tabsitem %}\n\n{% tabsitem #cli title=\"CLI\" %}\n\n{% partial file=\"cli-disclaimer.md\" /%}\n\nTo create your table using the CLI, first use the `appwrite init tables` command to initialize your table.\n\n```sh\nappwrite init tables\n```\n\nThen push your table using the `appwrite push tables` command.\n\n```sh\nappwrite push tables\n```\n\nThis will create your table in the Console with all of your `appwrite.json` configurations.\n\n{% arrow_link href=\"/docs/tooling/command-line/tables#commands\" %}\nLearn more about the CLI tables commands\n{% /arrow_link %}\n\n{% /tabsitem %}\n\n{% /tabs %}\n\n{% info title=\"AI suggestions\" %}\nEnable **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).\n{% /info %}\n\n# Permissions {% #permissions %}\nAppwrite uses permissions to control data access.\nFor security, only users that are granted permissions can access a resource.\nThis helps prevent accidental data leaks by forcing you to make more conscious decisions around permissions.\n\nBy default, Appwrite doesn't grant permissions to any users when a new table is created.\nThis means users can't create new rows or read, update, and delete existing rows.\n\n[Learn about configuring permissions](/docs/products/databases/permissions).\n\n# Columns {% #columns %}\nAll rows in a table follow the same structure.\nColumns are used to define the structure of your rows and help the Appwrite's API validate your users' input.\nAdd your first column by clicking the **Add column** button.\n\nYou can choose between the following types.\n\n| Column | Description |\n|--------------|------------------------------------------------------------------|\n| `varchar` | String column. Fully indexable if size < 768. Maximum 16,383 characters. |\n| `text` | Text column. Prefix indexing only. Maximum 16,383 characters. |\n| `mediumtext` | Text column. Prefix indexing only. Maximum 4,194,303 characters. |\n| `longtext` | Text column. Prefix indexing only. Maximum 1,073,741,823 characters. |\n| `integer` | Integer column. 32-bit signed, range -2,147,483,648 to 2,147,483,647. |\n| `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. |\n| `float` | Float column. |\n| `boolean` | Boolean column. |\n| `datetime` | Datetime column formatted as an ISO 8601 string. |\n| `enum` | Enum column. |\n| `ip` | IP address column for IPv4 and IPv6. |\n| `email` | Email address column. |\n| `url` | URL column. |\n| `point` | Geographic point specified as `[longitude, latitude]`. |\n| `line` | Geographic line represented by an ordered list of coordinates. |\n| `polygon` | Geographic polygon representing a closed area; supports interior holes. |\n| `relationship` | Relationship column relates one table to another. [Learn more about relationships.](/docs/products/databases/relationships) |\n| `string` | **Deprecated.** Use `varchar`, `text`, `mediumtext`, or `longtext` instead. |\n\nIf an column must be populated in all rows, set it as `required`.\nIf not, you may optionally set a default value.\nAdditionally, decide if the column should be a single value or an array of values.\n\nIf needed, you can change an column's key, default value, size (for text columns), and whether it is required or not after creation.\n\nYou 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.\n\n# Indexes {% #indexes %}\n\nDatabases use indexes to quickly locate data without having to search through every row for matches.\nTo ensure the best performance, Appwrite recommends an index for every column queried.\nIf you plan to query multiple columns in a single query, creating an index with **all** queried columns will yield optimal performance.\n\nThe following indexes are currently supported:\n\n| Type | Description |\n|------------|--------------------------------------------------------------------------------------------------------------|\n| `key` | Plain Index to allow queries. |\n| `unique` | Unique Index to disallow duplicates. |\n| `fulltext` | For searching within text columns. Required for the [search query method](/docs/products/databases/queries#query-class). |\n\nYou can create an index by navigating to your table's **Indexes** tab or by using your favorite [Server SDK](/docs/sdks#server)."}, {"path": "docs/products/databases/timestamp-overrides", "title": "Timestamp overrides", "description": "Set custom $createdAt and $updatedAt timestamps for your documents when using server SDKs.", "content": "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.\n\n{% info title=\"Server SDKs required\" %}\nTo 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).\n{% /info %}\n\n# Setting custom timestamps {% #setting-custom-timestamps %}\n\nYou 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.\n\nCustom timestamps work with all document operations: create, update, upsert, and their bulk variants.\n\n## Single document operations {% #single-document-operations %}\n\nWhen working with individual documents, you can set custom timestamps during create, update, and upsert operations.\n\n### Create with custom timestamps {% #create-custom %}\n\n{% multicode %}\n```server-nodejs\nconst sdk = require('node-appwrite');\n\nconst client = new sdk.Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nconst databases = new sdk.Databases(client);\n\nawait databases.createDocument(\n '',\n '',\n sdk.ID.unique(),\n {\n '$createdAt': new Date('2025-08-10T12:34:56.000Z').toISOString(),\n '$updatedAt': new Date('2025-08-10T12:34:56.000Z').toISOString(),\n // ...your attributes\n }\n);\n```\n```server-php\nuse Appwrite\\Client;\nuse Appwrite\\ID;\nuse Appwrite\\Services\\Databases;\n\n$client = (new Client())\n ->setEndpoint('https://.cloud.appwrite.io/v1')\n ->setProject('')\n ->setKey('');\n\n$databases = new Databases($client);\n\n$databases->createDocument(\n databaseId: '',\n collectionId: '',\n documentId: '',\n [\n '$createdAt' => (new DateTime(''))->format(DATE_ATOM),\n '$updatedAt' => (new DateTime(''))->format(DATE_ATOM),\n // ...your attributes\n ]\n);\n```\n```server-swift\nimport Appwrite\nimport Foundation\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\")\n\nlet databases = Databases(client)\n\nlet isoFormatter = ISO8601DateFormatter()\nisoFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]\nlet customDate = isoFormatter.date(from: \"\") ?? Date()\nlet createdAt = isoFormatter.string(from: customDate)\nlet updatedAt = isoFormatter.string(from: customDate)\n\ndo {\n let created = try await databases.createDocument(\n databaseId: \"\",\n collectionId: \"\",\n documentId: \"\",\n data: [\n \"$createdAt\": createdAt,\n \"$updatedAt\": updatedAt,\n // ...your attributes\n ]\n )\n print(\"Created:\", created)\n} catch {\n print(\"Create error:\", error)\n}\n```\n```server-python\nfrom appwrite.client import Client\nfrom appwrite.services.databases import Databases\nfrom appwrite.id import ID\nfrom datetime import datetime, timezone\n\nclient = Client()\nclient.set_endpoint('https://.cloud.appwrite.io/v1')\nclient.set_project('')\nclient.set_key('')\n\ndatabases = Databases(client)\n\niso = datetime(2025, 8, 10, 12, 34, 56, tzinfo=timezone.utc).isoformat()\n\ndatabases.create_document(\n database_id='',\n collection_id='',\n document_id=ID.unique(),\n data={\n '$createdAt': iso,\n '$updatedAt': iso,\n # ...your attributes\n }\n)\n```\n```server-ruby\nrequire 'appwrite'\nrequire 'time'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://.cloud.appwrite.io/v1')\n .set_project('')\n .set_key('')\n\ndatabases = Databases.new(client)\n\ncustom_date = Time.parse('2025-08-10T12:34:56.000Z').iso8601\n\ndatabases.create_document(\n database_id: '',\n collection_id: '',\n document_id: ID.unique(),\n data: {\n '$createdAt' => custom_date,\n '$updatedAt' => custom_date,\n # ...your attributes\n }\n)\n```\n```server-dotnet\nusing Appwrite;\nusing Appwrite.Models;\nusing Appwrite.Services;\n\nClient client = new Client()\n .SetEndpoint(\"https://.cloud.appwrite.io/v1\")\n .SetProject(\"\")\n .SetKey(\"\");\n\nDatabases databases = new Databases(client);\n\nstring customDate = DateTimeOffset.Parse(\"2025-08-10T12:34:56.000Z\").ToString(\"O\");\n\nawait databases.CreateDocument(\n databaseId: \"\",\n collectionId: \"\",\n documentId: ID.Unique(),\n data: new Dictionary\n {\n [\"$createdAt\"] = customDate,\n [\"$updatedAt\"] = customDate,\n // ...your attributes\n }\n);\n```\n```server-dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\n\nClient client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nDatabases databases = Databases(client);\n\nString customDate = DateTime.parse('2025-08-10T12:34:56.000Z').toIso8601String();\n\nawait databases.createDocument(\n databaseId: '',\n collectionId: '',\n documentId: ID.unique(),\n data: {\n '\\$createdAt': customDate,\n '\\$updatedAt': customDate,\n // ...your attributes\n },\n);\n```\n```rust\nuse appwrite::Client;\nuse appwrite::services::databases::Databases;\nuse appwrite::id::ID;\nuse serde_json::json;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box> {\n let client = Client::new()\n .set_endpoint(\"https://.cloud.appwrite.io/v1\")\n .set_project(\"\")\n .set_key(\"\");\n\n let databases = Databases::new(&client);\n\n let result = databases.create_document(\n \"\",\n \"\",\n &ID::unique(),\n json!({\n \"$createdAt\": \"2025-08-10T12:34:56.000Z\",\n \"$updatedAt\": \"2025-08-10T12:34:56.000Z\"\n // ...your attributes\n }),\n None,\n None,\n ).await?;\n\n println!(\"Created: {:?}\", result);\n Ok(())\n}\n```\n{% /multicode %}\n\n### Update with custom timestamps {% #update-custom %}\n\nWhen updating documents, you can also set a custom `$updatedAt` timestamp:\n\n{% multicode %}\n```server-nodejs\nawait databases.updateDocument(\n '',\n '',\n '',\n {\n '$updatedAt': new Date('2025-08-10T12:34:56.000Z').toISOString(),\n // ...your attributes\n }\n);\n```\n```server-php\n$databases->updateDocument(\n databaseId: '',\n collectionId: '',\n documentId: '',\n [\n '$updatedAt' => (new DateTime(''))->format(DATE_ATOM),\n // ...your attributes\n ]\n);\n```\n```server-python\nfrom datetime import datetime, timezone\n\ndatabases.update_document(\n database_id='',\n collection_id='',\n document_id='',\n data={\n '$updatedAt': datetime(2025, 8, 10, 12, 34, 56, tzinfo=timezone.utc).isoformat(),\n # ...your attributes\n }\n)\n```\n```server-swift\nimport Appwrite\nimport Foundation\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\")\n\nlet databases = Databases(client)\n\nlet isoFormatter = ISO8601DateFormatter()\nisoFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]\nlet updatedAt = isoFormatter.string(from: isoFormatter.date(from: \"\") ?? Date())\n\ndo {\n let updated = try await databases.updateDocument(\n databaseId: \"\",\n collectionId: \"\",\n documentId: \"\",\n data: [\n \"$updatedAt\": updatedAt,\n // ...your attributes\n ]\n )\n print(\"Updated:\", updated)\n} catch {\n print(\"Update error:\", error)\n}\n```\n```server-ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://.cloud.appwrite.io/v1')\n .set_project('')\n .set_key('')\n\ndatabases = Databases.new(client)\n\ncustom_date = Time.parse('').iso8601\n\ndatabases.update_document(\n database_id: '',\n collection_id: '',\n document_id: '',\n data: {\n '$updatedAt' => custom_date,\n # ...your attributes\n }\n)\n```\n```server-dotnet\nusing Appwrite;\nusing Appwrite.Models;\nusing Appwrite.Services;\n\nClient client = new Client()\n .SetEndpoint(\"https://.cloud.appwrite.io/v1\")\n .SetProject(\"\")\n .SetKey(\"\");\n\nDatabases databases = new Databases(client);\n\nstring customDate = DateTimeOffset.Parse(\"\").ToString(\"O\");\n\nawait databases.UpdateDocument(\n databaseId: \"\",\n collectionId: \"\",\n documentId: \"\",\n data: new Dictionary\n {\n [\"$updatedAt\"] = customDate,\n // ...your attributes\n }\n);\n```\n```server-dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\n\nClient client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nDatabases databases = Databases(client);\n\nString customDate = DateTime.parse('').toIso8601String();\n\nawait databases.updateDocument(\n databaseId: '',\n collectionId: '',\n documentId: '',\n data: {\n '\\$updatedAt': customDate,\n // ...your attributes\n },\n);\n```\n```rust\nuse appwrite::Client;\nuse appwrite::services::databases::Databases;\nuse serde_json::json;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box> {\n let client = Client::new()\n .set_endpoint(\"https://.cloud.appwrite.io/v1\")\n .set_project(\"\")\n .set_key(\"\");\n\n let databases = Databases::new(&client);\n\n let result = databases.update_document(\n \"\",\n \"\",\n \"\",\n Some(json!({\n \"$updatedAt\": \"2025-08-10T12:34:56.000Z\"\n // ...your attributes\n })),\n None,\n None,\n ).await?;\n\n println!(\"Updated: {:?}\", result);\n Ok(())\n}\n```\n{% /multicode %}\n\n## Bulk operations {% #bulk-operations %}\n\nCustom timestamps also work with bulk operations, allowing you to set different timestamps for each document in the batch:\n\n### Bulk create {% #bulk-create %}\n\n{% multicode %}\n```server-nodejs\nawait databases.createDocuments(\n '',\n '',\n [\n {\n '$id': sdk.ID.unique(),\n '$createdAt': new Date('2024-01-01T00:00:00.000Z').toISOString(),\n '$updatedAt': new Date('2024-01-01T00:00:00.000Z').toISOString(),\n // ...your attributes\n },\n {\n '$id': sdk.ID.unique(),\n '$createdAt': new Date('2024-02-01T00:00:00.000Z').toISOString(),\n '$updatedAt': new Date('2024-02-01T00:00:00.000Z').toISOString(),\n // ...your attributes\n }\n ]\n);\n```\n```server-python\ndatabases.create_documents(\n database_id='',\n collection_id='',\n documents=[\n {\n '$id': ID.unique(),\n '$createdAt': datetime(2024, 1, 1, tzinfo=timezone.utc).isoformat(),\n '$updatedAt': datetime(2024, 1, 1, tzinfo=timezone.utc).isoformat(),\n # ...your attributes\n },\n {\n '$id': ID.unique(),\n '$createdAt': datetime(2024, 2, 1, tzinfo=timezone.utc).isoformat(),\n '$updatedAt': datetime(2024, 2, 1, tzinfo=timezone.utc).isoformat(),\n # ...your attributes\n }\n ]\n)\n```\n```server-php\nuse Appwrite\\Client;\nuse Appwrite\\ID;\nuse Appwrite\\Services\\Databases;\n\n$client = (new Client())\n ->setEndpoint('https://.cloud.appwrite.io/v1')\n ->setProject('')\n ->setKey('');\n\n$databases = new Databases($client);\n\n$databases->createDocuments(\n databaseId: '',\n collectionId: '',\n documents: [\n [\n '$id' => ID::unique(),\n '$createdAt' => (new DateTime(''))->format(DATE_ATOM),\n '$updatedAt' => (new DateTime(''))->format(DATE_ATOM),\n // ...your attributes\n ],\n [\n '$id' => ID::unique(),\n '$createdAt' => (new DateTime(''))->format(DATE_ATOM),\n '$updatedAt' => (new DateTime(''))->format(DATE_ATOM),\n // ...your attributes\n ],\n ]\n);\n```\n```server-swift\nimport Appwrite\nimport Foundation\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\")\n\nlet databases = Databases(client)\n\nlet isoFormatter = ISO8601DateFormatter()\nisoFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]\n\nlet first = isoFormatter.string(from: isoFormatter.date(from: \"\") ?? Date())\nlet second = isoFormatter.string(from: isoFormatter.date(from: \"\") ?? Date())\n\ndo {\n let bulkCreated = try await databases.createDocuments(\n databaseId: \"\",\n collectionId: \"\",\n documents: [\n [\n \"$id\": ID.unique(),\n \"$createdAt\": first,\n \"$updatedAt\": first,\n // ...your attributes\n ],\n [\n \"$id\": ID.unique(),\n \"$createdAt\": second,\n \"$updatedAt\": second,\n // ...your attributes\n ]\n ]\n )\n print(\"Bulk create:\", bulkCreated)\n} catch {\n print(\"Bulk create error:\", error)\n}\n```\n```server-ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://.cloud.appwrite.io/v1')\n .set_project('')\n .set_key('')\n\ndatabases = Databases.new(client)\n\nfirst = Time.parse('').iso8601\nsecond = Time.parse('').iso8601\n\ndatabases.create_documents(\n database_id: '',\n collection_id: '',\n documents: [\n {\n '$id' => ID.unique(),\n '$createdAt' => first,\n '$updatedAt' => first,\n # ...your attributes\n },\n {\n '$id' => ID.unique(),\n '$createdAt' => second,\n '$updatedAt' => second,\n # ...your attributes\n }\n ]\n)\n```\n```server-dotnet\nusing Appwrite;\nusing Appwrite.Models;\nusing Appwrite.Services;\n\nClient client = new Client()\n .SetEndpoint(\"https://.cloud.appwrite.io/v1\")\n .SetProject(\"\")\n .SetKey(\"\");\n\nDatabases databases = new Databases(client);\n\nstring first = DateTimeOffset.Parse(\"\").ToString(\"O\");\nstring second = DateTimeOffset.Parse(\"\").ToString(\"O\");\n\nawait databases.CreateDocuments(\n databaseId: \"\",\n collectionId: \"\",\n documents: new List\n {\n new Dictionary\n {\n [\"$id\"] = ID.Unique(),\n [\"$createdAt\"] = first,\n [\"$updatedAt\"] = first,\n // ...your attributes\n },\n new Dictionary\n {\n [\"$id\"] = ID.Unique(),\n [\"$createdAt\"] = second,\n [\"$updatedAt\"] = second,\n // ...your attributes\n }\n }\n);\n```\n```server-dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\n\nClient client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nDatabases databases = Databases(client);\n\nString first = DateTime.parse('').toIso8601String();\nString second = DateTime.parse('').toIso8601String();\n\nawait databases.createDocuments(\n databaseId: '',\n collectionId: '',\n documents: [\n {\n '\\$id': ID.unique(),\n '\\$createdAt': first,\n '\\$updatedAt': first,\n // ...your attributes\n },\n {\n '\\$id': ID.unique(),\n '\\$createdAt': second,\n '\\$updatedAt': second,\n // ...your attributes\n }\n ],\n);\n```\n```rust\nuse appwrite::Client;\nuse appwrite::services::databases::Databases;\nuse appwrite::id::ID;\nuse serde_json::json;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box> {\n let client = Client::new()\n .set_endpoint(\"https://.cloud.appwrite.io/v1\")\n .set_project(\"\")\n .set_key(\"\");\n\n let databases = Databases::new(&client);\n\n let result = databases.create_documents(\n \"\",\n \"\",\n vec![\n json!({\n \"$id\": ID::unique(),\n \"$createdAt\": \"2024-01-01T00:00:00.000Z\",\n \"$updatedAt\": \"2024-01-01T00:00:00.000Z\"\n // ...your attributes\n }),\n json!({\n \"$id\": ID::unique(),\n \"$createdAt\": \"2024-02-01T00:00:00.000Z\",\n \"$updatedAt\": \"2024-02-01T00:00:00.000Z\"\n // ...your attributes\n }),\n ],\n None,\n ).await?;\n\n println!(\"Bulk create: {:?}\", result);\n Ok(())\n}\n```\n{% /multicode %}\n\n### Bulk upsert {% #bulk-upsert %}\n\n{% multicode %}\n```server-nodejs\nawait databases.upsertDocuments(\n '',\n '',\n [\n {\n '$id': '',\n '$createdAt': new Date('2024-01-01T00:00:00.000Z').toISOString(),\n '$updatedAt': new Date('2025-01-01T00:00:00.000Z').toISOString(),\n // ...your attributes\n }\n ]\n);\n```\n```server-python\ndatabases.upsert_documents(\n database_id='',\n collection_id='',\n documents=[\n {\n '$id': '',\n '$createdAt': datetime(2024, 1, 1, tzinfo=timezone.utc).isoformat(),\n '$updatedAt': datetime(2025, 1, 1, tzinfo=timezone.utc).isoformat(),\n # ...your attributes\n }\n ]\n)\n```\n```server-php\nuse Appwrite\\Client;\nuse Appwrite\\ID;\nuse Appwrite\\Services\\Databases;\n\n$client = (new Client())\n ->setEndpoint('https://.cloud.appwrite.io/v1')\n ->setProject('')\n ->setKey('');\n\n$databases = new Databases($client);\n\n$databases->upsertDocuments(\n databaseId: '',\n collectionId: '',\n documents: [\n [\n '$id' => '',\n '$createdAt' => (new DateTime(''))->format(DATE_ATOM),\n '$updatedAt' => (new DateTime(''))->format(DATE_ATOM),\n // ...your attributes\n ],\n ]\n);\n```\n```server-swift\nimport Appwrite\nimport Foundation\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\")\n\nlet databases = Databases(client)\n\nlet isoFormatter = ISO8601DateFormatter()\nisoFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]\nlet createdAt = isoFormatter.string(from: isoFormatter.date(from: \"\") ?? Date())\nlet updatedAt = isoFormatter.string(from: isoFormatter.date(from: \"\") ?? Date())\n\ndo {\n let bulkUpserted = try await databases.upsertDocuments(\n databaseId: \"\",\n collectionId: \"\",\n documents: [\n [\n \"$id\": \"\",\n \"$createdAt\": createdAt,\n \"$updatedAt\": updatedAt,\n // ...your attributes\n ]\n ]\n )\n print(\"Bulk upsert:\", bulkUpserted)\n} catch {\n print(\"Bulk upsert error:\", error)\n}\n```\n```server-ruby\nrequire 'appwrite'\nrequire 'time'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://.cloud.appwrite.io/v1')\n .set_project('')\n .set_key('')\n\ndatabases = Databases.new(client)\n\ncustom_date = Time.parse('').iso8601\n\ndatabases.upsert_documents(\n database_id: '',\n collection_id: '',\n documents: [\n {\n '$id' => '',\n '$createdAt' => custom_date,\n '$updatedAt' => custom_date,\n # ...your attributes\n }\n ]\n)\n```\n```server-dotnet\nusing Appwrite;\nusing Appwrite.Models;\nusing Appwrite.Services;\n\nClient client = new Client()\n .SetEndpoint(\"https://.cloud.appwrite.io/v1\")\n .SetProject(\"\")\n .SetKey(\"\");\n\nDatabases databases = new Databases(client);\n\nstring createdAt = DateTimeOffset.Parse(\"\").ToString(\"O\");\nstring updatedAt = DateTimeOffset.Parse(\"\").ToString(\"O\");\n\nawait databases.UpsertDocuments(\n databaseId: \"\",\n collectionId: \"\",\n documents: new List\n {\n new Dictionary\n {\n [\"$id\"] = \"\",\n [\"$createdAt\"] = createdAt,\n [\"$updatedAt\"] = updatedAt,\n // ...your attributes\n }\n }\n);\n```\n```server-dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\n\nClient client = Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('')\n .setKey('');\n\nDatabases databases = Databases(client);\n\nString createdAt = DateTime.parse('').toIso8601String();\nString updatedAt = DateTime.parse('').toIso8601String();\n\nawait databases.upsertDocuments(\n databaseId: '',\n collectionId: '',\n documents: [\n {\n '\\$id': '',\n '\\$createdAt': createdAt,\n '\\$updatedAt': updatedAt,\n // ...your attributes\n }\n ],\n);\n```\n```rust\nuse appwrite::Client;\nuse appwrite::services::databases::Databases;\nuse serde_json::json;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box> {\n let client = Client::new()\n .set_endpoint(\"https://.cloud.appwrite.io/v1\")\n .set_project(\"\")\n .set_key(\"\");\n\n let databases = Databases::new(&client);\n\n let result = databases.upsert_documents(\n \"\",\n \"\",\n vec![\n json!({\n \"$id\": \"\",\n \"$createdAt\": \"2024-01-01T00:00:00.000Z\",\n \"$updatedAt\": \"2025-01-01T00:00:00.000Z\"\n // ...your attributes\n }),\n ],\n None,\n ).await?;\n\n println!(\"Bulk upsert: {:?}\", result);\n Ok(())\n}\n```\n{% /multicode %}\n\n# Common use cases {% #use-cases %}\n\nCustom timestamps are particularly useful in several scenarios:\n\n## Data migration {% #data-migration %}\nWhen migrating existing data from another system, you can preserve the original\ncreation and modification times:\n\n{% multicode %}\n```server-nodejs\nawait databases.createDocument(\n '',\n 'blog_posts',\n sdk.ID.unique(),\n {\n '$createdAt': '',\n '$updatedAt': '',\n title: '',\n content: '<CONTENT>'\n }\n)\n```\n```server-php\n$databases->createDocument(\n databaseId: '<DATABASE_ID>',\n collectionId: 'blog_posts',\n documentId: ID::unique(),\n [\n '$createdAt' => '<ORIGINAL_CREATED_AT_ISO>',\n '$updatedAt' => '<LAST_MODIFIED_ISO>',\n 'title' => '<TITLE>',\n 'content' => '<CONTENT>'\n ]\n);\n```\n```server-swift\nlet _ = try await databases.createDocument(\n databaseId: \"<DATABASE_ID>\",\n collectionId: \"blog_posts\",\n documentId: ID.unique(),\n data: [\n \"$createdAt\": \"<ORIGINAL_CREATED_AT_ISO>\",\n \"$updatedAt\": \"<LAST_MODIFIED_ISO>\",\n \"title\": \"<TITLE>\",\n \"content\": \"<CONTENT>\"\n ]\n)\n```\n```server-python\ndatabases.create_document(\n database_id='<DATABASE_ID>',\n collection_id='blog_posts',\n document_id=ID.unique(),\n data={\n '$createdAt': '<ORIGINAL_CREATED_AT_ISO>',\n '$updatedAt': '<LAST_MODIFIED_ISO>',\n 'title': '<TITLE>',\n 'content': '<CONTENT>'\n }\n)\n```\n```server-ruby\ndatabases.create_document(\n database_id: '<DATABASE_ID>',\n collection_id: 'blog_posts',\n document_id: ID.unique(),\n data: {\n '$createdAt' => '<ORIGINAL_CREATED_AT_ISO>',\n '$updatedAt' => '<LAST_MODIFIED_ISO>',\n 'title' => '<TITLE>',\n 'content' => '<CONTENT>'\n }\n)\n```\n```server-dotnet\nawait databases.CreateDocument(\n databaseId: \"<DATABASE_ID>\",\n collectionId: \"blog_posts\",\n documentId: ID.Unique(),\n data: new Dictionary<string, object>\n {\n [\"$createdAt\"] = \"<ORIGINAL_CREATED_AT_ISO>\",\n [\"$updatedAt\"] = \"<LAST_MODIFIED_ISO>\",\n [\"title\"] = \"<TITLE>\",\n [\"content\"] = \"<CONTENT>\"\n }\n);\n```\n```server-dart\nawait databases.createDocument(\n databaseId: '<DATABASE_ID>',\n collectionId: 'blog_posts',\n documentId: ID.unique(),\n data: {\n '\\$createdAt': '<ORIGINAL_CREATED_AT_ISO>',\n '\\$updatedAt': '<LAST_MODIFIED_ISO>',\n 'title': '<TITLE>',\n 'content': '<CONTENT>'\n },\n);\n```\n```rust\nuse appwrite::Client;\nuse appwrite::services::databases::Databases;\nuse appwrite::id::ID;\nuse serde_json::json;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n let client = Client::new()\n .set_endpoint(\"https://<REGION>.cloud.appwrite.io/v1\")\n .set_project(\"<PROJECT_ID>\")\n .set_key(\"<API_KEY>\");\n\n let databases = Databases::new(&client);\n\n let result = databases.create_document(\n \"<DATABASE_ID>\",\n \"blog_posts\",\n &ID::unique(),\n json!({\n \"$createdAt\": \"<ORIGINAL_CREATED_AT_ISO>\",\n \"$updatedAt\": \"<LAST_MODIFIED_ISO>\",\n \"title\": \"<TITLE>\",\n \"content\": \"<CONTENT>\"\n }),\n None,\n None,\n ).await?;\n\n println!(\"Created: {:?}\", result);\n Ok(())\n}\n```\n{% /multicode %}\n\n## Backdating records {% #backdating %}\nFor historical data entry or when creating records that represent past events:\n\n{% multicode %}\n```server-nodejs\nawait databases.createDocument(\n '<DATABASE_ID>',\n 'transactions',\n sdk.ID.unique(),\n {\n '$createdAt': '2023-12-31T23:59:59.000Z',\n '$updatedAt': '2023-12-31T23:59:59.000Z',\n amount: 1000,\n type: 'year-end-bonus'\n }\n)\n```\n```server-php\n$databases->createDocument(\n databaseId: '<DATABASE_ID>',\n collectionId: 'transactions',\n documentId: ID::unique(),\n [\n '$createdAt' => '2023-12-31T23:59:59.000Z',\n '$updatedAt' => '2023-12-31T23:59:59.000Z',\n 'amount' => 1000,\n 'type' => 'year-end-bonus'\n ]\n);\n```\n```server-swift\nlet _ = try await databases.createDocument(\n databaseId: \"<DATABASE_ID>\",\n collectionId: \"transactions\",\n documentId: ID.unique(),\n data: [\n \"$createdAt\": \"2023-12-31T23:59:59.000Z\",\n \"$updatedAt\": \"2023-12-31T23:59:59.000Z\",\n \"amount\": 1000,\n \"type\": \"year-end-bonus\"\n ]\n)\n```\n```server-python\ndatabases.create_document(\n database_id='<DATABASE_ID>',\n collection_id='transactions',\n document_id=ID.unique(),\n data={\n '$createdAt': '2023-12-31T23:59:59.000Z',\n '$updatedAt': '2023-12-31T23:59:59.000Z',\n 'amount': 1000,\n 'type': 'year-end-bonus'\n }\n)\n```\n```server-ruby\ndatabases.create_document(\n database_id: '<DATABASE_ID>',\n collection_id: 'transactions',\n document_id: ID.unique(),\n data: {\n '$createdAt' => '2023-12-31T23:59:59.000Z',\n '$updatedAt' => '2023-12-31T23:59:59.000Z',\n 'amount' => 1000,\n 'type' => 'year-end-bonus'\n }\n)\n```\n```server-dotnet\nawait databases.CreateDocument(\n databaseId: \"<DATABASE_ID>\",\n collectionId: \"transactions\",\n documentId: ID.Unique(),\n data: new Dictionary<string, object>\n {\n [\"$createdAt\"] = \"2023-12-31T23:59:59.000Z\",\n [\"$updatedAt\"] = \"2023-12-31T23:59:59.000Z\",\n [\"amount\"] = 1000,\n [\"type\"] = \"year-end-bonus\"\n }\n);\n```\n```server-dart\nawait databases.createDocument(\n databaseId: '<DATABASE_ID>',\n collectionId: 'transactions',\n documentId: ID.unique(),\n data: {\n '\\$createdAt': '2023-12-31T23:59:59.000Z',\n '\\$updatedAt': '2023-12-31T23:59:59.000Z',\n 'amount': 1000,\n 'type': 'year-end-bonus'\n },\n);\n```\n```rust\nuse appwrite::Client;\nuse appwrite::services::databases::Databases;\nuse appwrite::id::ID;\nuse serde_json::json;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n let client = Client::new()\n .set_endpoint(\"https://<REGION>.cloud.appwrite.io/v1\")\n .set_project(\"<PROJECT_ID>\")\n .set_key(\"<API_KEY>\");\n\n let databases = Databases::new(&client);\n\n let result = databases.create_document(\n \"<DATABASE_ID>\",\n \"transactions\",\n &ID::unique(),\n json!({\n \"$createdAt\": \"2023-12-31T23:59:59.000Z\",\n \"$updatedAt\": \"2023-12-31T23:59:59.000Z\",\n \"amount\": 1000,\n \"type\": \"year-end-bonus\"\n }),\n None,\n None,\n ).await?;\n\n println!(\"Created: {:?}\", result);\n Ok(())\n}\n```\n{% /multicode %}\n\n## Synchronization {% #synchronization %}\nWhen synchronizing data between systems while maintaining timestamp consistency:\n\n{% multicode %}\n```server-nodejs\nawait databases.upsertDocument(\n '<DATABASE_ID>',\n 'users',\n '<DOCUMENT_ID_OR_NEW_ID>',\n {\n '$updatedAt': '<EXTERNAL_LAST_MODIFIED_ISO>',\n profile: '<PROFILE_DATA>'\n }\n)\n```\n```server-php\n$databases->upsertDocument(\n databaseId: '<DATABASE_ID>',\n collectionId: 'users',\n documentId: '<DOCUMENT_ID_OR_NEW_ID>',\n [\n '$updatedAt' => '<EXTERNAL_LAST_MODIFIED_ISO>',\n 'profile' => '<PROFILE_DATA>'\n ]\n);\n```\n```server-swift\nlet _ = try await databases.upsertDocument(\n databaseId: \"<DATABASE_ID>\",\n collectionId: \"users\",\n documentId: \"<DOCUMENT_ID_OR_NEW_ID>\",\n data: [\n \"$updatedAt\": \"<EXTERNAL_LAST_MODIFIED_ISO>\",\n \"profile\": \"<PROFILE_DATA>\"\n ]\n)\n```\n```server-python\ndatabases.upsert_document(\n database_id='<DATABASE_ID>',\n collection_id='users',\n document_id='<DOCUMENT_ID_OR_NEW_ID>',\n data={\n '$updatedAt': '<EXTERNAL_LAST_MODIFIED_ISO>',\n 'profile': '<PROFILE_DATA>'\n }\n)\n```\n```server-ruby\ndatabases.upsert_document(\n database_id: '<DATABASE_ID>',\n collection_id: 'users',\n document_id: '<DOCUMENT_ID_OR_NEW_ID>',\n data: {\n '$updatedAt' => '<EXTERNAL_LAST_MODIFIED_ISO>',\n 'profile' => '<PROFILE_DATA>'\n }\n)\n```\n```server-dotnet\nawait databases.UpsertDocument(\n databaseId: \"<DATABASE_ID>\",\n collectionId: \"users\",\n documentId: \"<DOCUMENT_ID_OR_NEW_ID>\",\n data: new Dictionary<string, object>\n {\n [\"$updatedAt\"] = \"<EXTERNAL_LAST_MODIFIED_ISO>\",\n [\"profile\"] = \"<PROFILE_DATA>\"\n }\n);\n```\n```server-dart\nawait databases.upsertDocument(\n databaseId: '<DATABASE_ID>',\n collectionId: 'users',\n documentId: '<DOCUMENT_ID_OR_NEW_ID>',\n data: {\n '\\$updatedAt': '<EXTERNAL_LAST_MODIFIED_ISO>',\n 'profile': '<PROFILE_DATA>'\n },\n);\n```\n```rust\nuse appwrite::Client;\nuse appwrite::services::databases::Databases;\nuse serde_json::json;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n let client = Client::new()\n .set_endpoint(\"https://<REGION>.cloud.appwrite.io/v1\")\n .set_project(\"<PROJECT_ID>\")\n .set_key(\"<API_KEY>\");\n\n let databases = Databases::new(&client);\n\n let result = databases.upsert_document(\n \"<DATABASE_ID>\",\n \"users\",\n \"<DOCUMENT_ID_OR_NEW_ID>\",\n Some(json!({\n \"$updatedAt\": \"<EXTERNAL_LAST_MODIFIED_ISO>\",\n \"profile\": \"<PROFILE_DATA>\"\n })),\n None,\n None,\n ).await?;\n\n println!(\"Upserted: {:?}\", result);\n Ok(())\n}\n```\n{% /multicode %}\n\n{% info title=\"Timestamp format and usage\" %}\n- Values must be valid ISO 8601 date-time strings (UTC recommended). Using `toISOString()` (JavaScript) or `datetime.isoformat()` (Python) is a good default.\n- You can set either or both attributes as needed. If omitted, Appwrite sets them automatically.\n{% /info %}"}, {"path": "docs/products/databases/transactions", "title": "Transactions", "description": "Stage multiple database operations and commit them atomically. Group changes across databases and tables with ordering, isolation, and conflict detection.", "content": "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.\n\n# How transactions work {% #how-transactions-work %}\n\n1. Call the [createTransaction](#create-a-transaction) method to create a transaction. This will return a transaction model, including its ID.\n2. 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.\n3. Call the [updateTransaction](#update-transaction) method to commit or roll back.\n\nOn 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.\n\n{% info title=\"Scope and limitations\" %}\nYou 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.\n{% /info %}\n\n# Limits {% #limits %}\n\nThe maximum number of operations you can stage per transaction depends on your plan:\n\n| Plan | Max operations per transaction |\n|------|-------------------------------|\n| Free | 100 |\n| Pro | 1,000 |\n| Scale | 2,500 |\n\n# Create a transaction {% #create-a-transaction %}\n\nCall the `createTransaction` method to begin. It returns a transaction model that includes `$id`. Pass this ID as `transactionId` to subsequent operations.\n\n{% multicode %}\n```client-web\nimport { Client, TablesDB } from 'appwrite';\n\nconst client = new Client()\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')\n .setProject('<PROJECT_ID>');\n\nconst tablesDB = new TablesDB(client);\n\nconst tx = await tablesDB.createTransaction();\n// tx.$id is your transactionId\n```\n```client-react-native\nimport { Client, TablesDB } from 'react-native-appwrite';\n\nconst client = new Client()\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')\n .setProject('<PROJECT_ID>');\n\nconst tablesDB = new TablesDB(client);\n\nconst tx = await tablesDB.createTransaction();\n// tx.$id is your transactionId\n```\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nfinal client = Client()\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')\n .setProject('<PROJECT_ID>');\n\nfinal tablesDB = TablesDB(client);\n\nfinal tx = await tablesDB.createTransaction();\n// tx.$id is your transactionId\n```\n```client-apple\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\")\n .setProject(\"<PROJECT_ID>\")\n\nlet tablesDB = TablesDB(client)\n\nlet tx = try await tablesDB.createTransaction()\n// tx.$id is your transactionId\n```\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.TablesDB\n\nval client = Client(applicationContext)\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\")\n .setProject(\"<PROJECT_ID>\")\n\nval tablesDB = TablesDB(client)\n\nval tx = tablesDB.createTransaction()\n// tx.$id is your transactionId\n```\n```client-android-java\nimport io.appwrite.Client;\nimport io.appwrite.services.TablesDB;\n\nClient client = new Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\")\n .setProject(\"<PROJECT_ID>\");\n\nTablesDB tablesDB = new TablesDB(client);\n\n// Create a transaction (asynchronous)\ntablesDB.createTransaction(new CoroutineCallback<>((tx, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n System.out.println(tx);\n}));\n```\n```server-nodejs\nconst sdk = require('node-appwrite');\n\nconst client = new sdk.Client()\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')\n .setProject('<PROJECT_ID>')\n .setKey('<API_KEY>');\n\nconst tablesDB = new sdk.TablesDB(client);\n\nconst tx = await tablesDB.createTransaction();\n// tx.$id is your transactionId\n```\n```server-deno\nimport * as sdk from 'npm:node-appwrite';\n\nconst client = new sdk.Client()\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')\n .setProject('<PROJECT_ID>')\n .setKey('<API_KEY>');\n\nconst tablesDB = new sdk.TablesDB(client);\n\nconst tx = await tablesDB.createTransaction();\n// tx.$id is your transactionId\n```\n```server-python\nfrom appwrite.client import Client\nfrom appwrite.services.tables_db import TablesDB\n\nclient = Client()\nclient.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')\nclient.set_project('<PROJECT_ID>')\nclient.set_key('<API_KEY>')\n\ntablesDB = TablesDB(client)\n\ntx = tablesDB.create_transaction()\n# tx.$id is your transactionId\n```\n```server-php\n<?php\n\nuse Appwrite\\Client;\nuse Appwrite\\Services\\TablesDB;\n\n$client = new Client();\n\n$client\n ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')\n ->setProject('<PROJECT_ID>')\n ->setKey('<API_KEY>')\n;\n\n$tablesDB = new TablesDB($client);\n\n$tx = $tablesDB->createTransaction();\n// $tx->\\$id is your transactionId\n```\n```server-ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')\n .set_project('<PROJECT_ID>')\n .set_key('<API_KEY>')\n\ntablesDB = TablesDB.new(client)\n\ntx = tablesDB.create_transaction\n# tx['$id'] is your transactionId\n```\n```server-dotnet\nusing Appwrite;\nusing Appwrite.Services;\n\nvar client = new Client()\n .SetEndPoint(\"https://<REGION>.cloud.appwrite.io/v1\")\n .SetProject(\"<PROJECT_ID>\")\n .SetKey(\"<API_KEY>\");\n\nvar tablesDB = new TablesDB(client);\n\nvar tx = await tablesDB.CreateTransaction();\n// tx.$id is your transactionId\n```\n```server-dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\n\nvoid main() async {\n Client client = Client();\n TablesDB tablesDB = TablesDB(client);\n\n client\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')\n .setProject('<PROJECT_ID>')\n .setKey('<API_KEY>');\n\n final tx = await tablesDB.createTransaction();\n // tx contains the transaction ID\n}\n```\n```server-go\npackage main\n\nimport (\n \"log\"\n \"github.com/appwrite/sdk-for-go/appwrite\"\n)\n\nfunc main() {\n client := appwrite.NewClient(\n appwrite.WithEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\"),\n appwrite.WithProject(\"<PROJECT_ID>\"),\n appwrite.WithKey(\"<API_KEY>\"),\n )\n\n tablesDB := appwrite.NewTablesDB(client)\n\n tx, err := tablesDB.CreateTransaction()\n if err != nil { log.Fatal(err) }\n _ = tx\n}\n```\n```server-swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\")\n .setProject(\"<PROJECT_ID>\")\n .setKey(\"<API_KEY>\")\n\nlet tablesDB = TablesDB(client)\n\nlet tx = try await tablesDB.createTransaction()\n// tx.$id is your transactionId\n```\n```server-kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.TablesDB\n\nval client = Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\")\n .setProject(\"<PROJECT_ID>\")\n .setKey(\"<API_KEY>\")\n\nval tablesDB = TablesDB(client)\n\nval tx = tablesDB.createTransaction()\n// tx.$id is your transactionId\n```\n```server-java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.TablesDB;\n\nClient client = new Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\")\n .setProject(\"<PROJECT_ID>\")\n .setKey(\"<API_KEY>\");\n\nTablesDB tablesDB = new TablesDB(client);\n\ntablesDB.createTransaction(new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n System.out.println(result);\n}));\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::tables_db::TablesDB;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n let client = Client::new()\n .set_endpoint(\"https://<REGION>.cloud.appwrite.io/v1\")\n .set_project(\"<PROJECT_ID>\")\n .set_key(\"<API_KEY>\");\n\n let tables_db = TablesDB::new(&client);\n\n let tx = tables_db.create_transaction(None).await?;\n // tx.id is your transaction_id\n\n Ok(())\n}\n```\n{% /multicode %}\n\n# Stage operations {% #stage-operations %}\n\nAdd the `transactionId` parameter to supported methods to stage them instead of immediately persisting.\n\nWhen you pass `transactionId`, Appwrite writes the operation to an internal staging area. The target table is not modified until you commit the transaction.\n\n## Stage single operations {% #stage-single-operations %}\n\nCreate, update, upsert, delete, and atomic numeric operations accept `transactionId`, as well as their bulk versions (createRows, updateRows, upsertRows, deleteRows).\n\n{% multicode %}\n```client-web\n// Create inside a transaction\nawait tablesDB.createRow({\n databaseId: '<DATABASE_ID>',\n tableId: '<TABLE_ID>',\n rowId: '<ROW_ID>',\n data: { name: 'Walter' },\n transactionId: tx.$id\n});\n\n// Increment inside a transaction\nawait tablesDB.incrementRowColumn({\n databaseId: '<DATABASE_ID>',\n tableId: '<TABLE_ID>',\n rowId: '<ROW_ID>',\n column: 'credits',\n value: 1,\n transactionId: tx.$id\n});\n```\n```client-flutter\n// Create inside a transaction\nawait tablesDB.createRow(\n databaseId: '<DATABASE_ID>',\n tableId: '<TABLE_ID>',\n rowId: '<ROW_ID>',\n data: { 'name': 'Walter' },\n transactionId: tx.$id\n);\n\n// Increment inside a transaction\nawait tablesDB.incrementRowColumn(\n databaseId: '<DATABASE_ID>',\n tableId: '<TABLE_ID>',\n rowId: '<ROW_ID>',\n column: 'credits',\n value: 1,\n transactionId: tx.$id\n);\n```\n```client-apple\n// Create inside a transaction\ntry await tablesDB.createRow(\n databaseId: \"<DATABASE_ID>\",\n tableId: \"<TABLE_ID>\",\n rowId: \"<ROW_ID>\",\n data: [\"name\": \"Walter\"],\n transactionId: tx.$id\n)\n\n// Increment inside a transaction\ntry await tablesDB.incrementRowColumn(\n databaseId: \"<DATABASE_ID>\",\n tableId: \"<TABLE_ID>\",\n rowId: \"<ROW_ID>\",\n column: \"credits\",\n value: 1,\n transactionId: tx.$id\n)\n```\n```server-kotlin\n// Create inside a transaction\ntablesDB.createRow(\n databaseId = \"<DATABASE_ID>\",\n tableId = \"<TABLE_ID>\",\n rowId = \"<ROW_ID>\",\n data = mapOf(\"name\" to \"Walter\"),\n transactionId = tx.$id\n)\n\n// Increment inside a transaction\ntablesDB.incrementRowColumn(\n databaseId = \"<DATABASE_ID>\",\n tableId = \"<TABLE_ID>\",\n rowId = \"<ROW_ID>\",\n column = \"credits\",\n value = 1,\n transactionId = tx.$id\n)\n```\n```server-java\n// Create inside a transaction (asynchronous)\ntablesDB.createRow(\n \"<DATABASE_ID>\",\n \"<TABLE_ID>\",\n \"<ROW_ID>\",\n Map.of(\"name\", \"Walter\"),\n \"<TRANSACTION_ID>\",\n new CoroutineCallback<>((row, error) -> {\n if (error != null) {\n error.printStackTrace();\n return null;\n }\n System.out.println(row);\n return null;\n })\n);\n\n// Increment inside a transaction (asynchronous)\ntablesDB.incrementRowColumn(\n \"<DATABASE_ID>\",\n \"<TABLE_ID>\",\n \"<ROW_ID>\",\n \"credits\",\n 1,\n \"<TRANSACTION_ID>\",\n new CoroutineCallback<>((row, error) -> {\n if (error != null) {\n error.printStackTrace();\n return null;\n }\n System.out.println(row);\n return null;\n })\n);\n```\n```client-react-native\n// Create inside a transaction\nawait tablesDB.createRow({\n databaseId: '<DATABASE_ID>',\n tableId: '<TABLE_ID>',\n rowId: '<ROW_ID>',\n data: { name: 'Walter' },\n transactionId: tx.$id\n});\n\n// Increment inside a transaction\nawait tablesDB.incrementRowColumn({\n databaseId: '<DATABASE_ID>',\n tableId: '<TABLE_ID>',\n rowId: '<ROW_ID>',\n column: 'credits',\n value: 1,\n transactionId: tx.$id\n});\n```\n```server-nodejs\n// Update inside a transaction\nawait tablesDB.updateRow({\n databaseId: '<DATABASE_ID>',\n tableId: '<TABLE_ID>',\n rowId: '<ROW_ID>',\n data: { plan: 'pro' },\n transactionId: tx.$id\n});\n\n// Delete inside a transaction\nawait tablesDB.deleteRow({\n databaseId: '<DATABASE_ID>',\n tableId: '<TABLE_ID>',\n rowId: '<ROW_ID>',\n transactionId: tx.$id\n});\n```\n```server-python\n# Upsert inside a transaction\ntablesDB.upsert_row(\n database_id = '<DATABASE_ID>',\n table_id = '<TABLE_ID>',\n row_id = '<ROW_ID>',\n data = { 'name': 'Walter' },\n transaction_id = tx.id\n)\n\n# Decrement inside a transaction\ntablesDB.decrement_row_column(\n database_id = '<DATABASE_ID>',\n table_id = '<TABLE_ID>',\n row_id = '<ROW_ID>',\n column = 'credits',\n value = 1,\n transaction_id = tx.id\n)\n```\n```server-php\n// Create inside a transaction\n$tablesDB->createRow(\n databaseId: '<DATABASE_ID>',\n tableId: '<TABLE_ID>',\n rowId: '<ROW_ID>',\n data: ['name' => 'Walter'],\n transactionId: $tx['$id']\n);\n\n// Increment inside a transaction\n$tablesDB->incrementRowColumn(\n databaseId: '<DATABASE_ID>',\n tableId: '<TABLE_ID>',\n rowId: '<ROW_ID>',\n column: 'credits',\n value: 1,\n transactionId: $tx['$id']\n);\n```\n```server-ruby\n# Create inside a transaction\ntablesDB.create_row(\n database_id: '<DATABASE_ID>',\n table_id: '<TABLE_ID>',\n row_id: '<ROW_ID>',\n data: { 'name' => 'Walter' },\n transaction_id: tx['$id']\n)\n\n# Increment inside a transaction\ntablesDB.increment_row_column(\n database_id: '<DATABASE_ID>',\n table_id: '<TABLE_ID>',\n row_id: '<ROW_ID>',\n column: 'credits',\n value: 1,\n transaction_id: tx['$id']\n)\n```\n```server-dotnet\n// Create inside a transaction\nawait tablesDB.CreateRow(\n databaseId: \"<DATABASE_ID>\",\n tableId: \"<TABLE_ID>\",\n rowId: \"<ROW_ID>\",\n data: new Dictionary<string, object> { [\"name\"] = \"Walter\" },\n transactionId: tx.Id\n);\n\n// Increment inside a transaction\nawait tablesDB.IncrementRowColumn(\n databaseId: \"<DATABASE_ID>\",\n tableId: \"<TABLE_ID>\",\n rowId: \"<ROW_ID>\",\n column: \"credits\",\n value: 1,\n transactionId: tx.Id\n);\n```\n```server-dart\n// Create inside a transaction\nawait tablesDB.createRow(\n databaseId: '<DATABASE_ID>',\n tableId: '<TABLE_ID>',\n rowId: '<ROW_ID>',\n data: { 'name': 'Walter' },\n transactionId: tx.Id\n);\n\n// Increment inside a transaction\nawait tablesDB.incrementRowColumn(\n databaseId: '<DATABASE_ID>',\n tableId: '<TABLE_ID>',\n rowId: '<ROW_ID>',\n column: 'credits',\n value: 1,\n transactionId: tx.Id\n);\n```\n```server-deno\n// Create inside a transaction\nawait tablesDB.createRow({\n databaseId: '<DATABASE_ID>',\n tableId: '<TABLE_ID>',\n rowId: '<ROW_ID>',\n data: { name: 'Walter' },\n transactionId: tx.$id\n});\n\n// Increment inside a transaction\nawait tablesDB.incrementRowColumn({\n databaseId: '<DATABASE_ID>',\n tableId: '<TABLE_ID>',\n rowId: '<ROW_ID>',\n column: 'credits',\n value: 1,\n transactionId: tx.$id\n});\n```\n```server-swift\n// Create inside a transaction\ntry await tablesDB.createRow(\n databaseId: \"<DATABASE_ID>\",\n tableId: \"<TABLE_ID>\",\n rowId: \"<ROW_ID>\",\n data: [\"name\": \"Walter\"],\n transactionId: tx.$id\n)\n\n// Increment inside a transaction\ntry await tablesDB.incrementRowColumn(\n databaseId: \"<DATABASE_ID>\",\n tableId: \"<TABLE_ID>\",\n rowId: \"<ROW_ID>\",\n column: \"credits\",\n value: 1,\n transactionId: tx.$id\n)\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::tables_db::TablesDB;\nuse serde_json::json;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n let client = Client::new()\n .set_endpoint(\"https://<REGION>.cloud.appwrite.io/v1\")\n .set_project(\"<PROJECT_ID>\")\n .set_key(\"<API_KEY>\");\n\n let tables_db = TablesDB::new(&client);\n\n let tx = tables_db.create_transaction(None).await?;\n\n // Update inside a transaction\n tables_db.update_row(\n \"<DATABASE_ID>\",\n \"<TABLE_ID>\",\n \"<ROW_ID>\",\n Some(json!({ \"plan\": \"pro\" })),\n None,\n Some(&tx.id),\n ).await?;\n\n // Delete inside a transaction\n tables_db.delete_row(\n \"<DATABASE_ID>\",\n \"<TABLE_ID>\",\n \"<ROW_ID>\",\n Some(&tx.id),\n ).await?;\n\n Ok(())\n}\n```\n{% /multicode %}\n\n## Stage many with createOperations {% #create-operations %}\n\nUse the `createOperations` method to stage multiple operations across databases and tables in a single request. Provide an array of operation objects:\n\n```json\n[\n {\n \"action\": \"create|update|upsert|increment|decrement|delete|bulkCreate|bulkUpdate|bulkUpsert|bulkDelete\",\n \"databaseId\": \"<DATABASE_ID>\",\n \"tableId|collectionId\": \"<TABLE_ID|COLLECTION_ID>\",\n \"rowId|documentId\": \"<ROW_ID|DOCUMENT_ID>\",\n \"data\": {}\n }\n]\n```\n\n### Provide data for each action (createOperations) {% #provide-data-for-each-action %}\n\n### Create, update, and upsert {% #create-update-upsert %}\nPass a raw data object.\n```json\n{ \"name\": \"Walter\" }\n```\n\n### Increment and decrement {% #increment-decrement %}\nPass a value and optionally `min`/`max` bounds.\n```json\n{ \"value\": 1, \"min\": 0, \"max\": 1000, \"column\": \"<COLUMN_NAME>\" }\n```\n\n### Bulk create and bulk upsert {% #bulk-create-upsert %}\nPass an array of raw data objects.\n```json\n[{ \"$id\": \"123\", \"name\": \"Walter\" }]\n```\n\n### Bulk update {% #bulk-update %}\nPass queries and the data to apply.\n```json\n{ \"queries\": [{\"method\": \"equal\", \"attribute\": \"status\", \"values\": [\"draft\"]}], \"data\": { \"status\": \"published\" } }\n```\n\n### Bulk delete {% #bulk-delete %}\nPass queries to select rows to delete.\n```json\n{ \"queries\": [{\"method\": \"equal\", \"attribute\": \"archived\", \"values\": [true]}] }\n```\n\n{% multicode %}\n```server-nodejs\n// Stage multiple operations at once\nawait tablesDB.createOperations({\n transactionId: tx.$id,\n operations: [\n {\n action: 'create',\n databaseId: '<DB_A>',\n tableId: '<TABLE_1>',\n rowId: 'u1',\n data: { name: 'Walter' }\n },\n {\n action: 'increment',\n databaseId: '<DB_B>',\n tableId: '<TABLE_2>',\n rowId: 'u2',\n data: { value: 1, min: 0, column: 'credits' }\n }\n ]\n});\n```\n```server-python\ntablesDB.create_operations(\n transaction_id = tx.id,\n operations = [\n {\n 'action': 'create',\n 'databaseId': '<DB_A>',\n 'tableId': '<TABLE_1>',\n 'rowId': 'u1',\n 'data': { 'name': 'Walter' }\n },\n {\n 'action': 'increment',\n 'databaseId': '<DB_B>',\n 'tableId': '<TABLE_2>',\n 'rowId': 'u2',\n 'data': { 'value': 1, 'min': 0, 'column': 'credits' }\n }\n ]\n)\n```\n```client-web\nawait tablesDB.createOperations({\n transactionId: tx.$id,\n operations: [\n {\n action: 'create',\n databaseId: '<DB_A>',\n tableId: '<TABLE_1>',\n rowId: 'u1',\n data: { name: 'Walter' }\n },\n {\n action: 'increment',\n databaseId: '<DB_B>',\n tableId: '<TABLE_2>',\n rowId: 'u2',\n data: { value: 1, min: 0, column: 'credits' }\n }\n ]\n});\n```\n```client-flutter\nawait tablesDB.createOperations(\n transactionId: tx.$id,\n operations: [\n {\n 'action': 'create',\n 'databaseId': '<DB_A>',\n 'tableId': '<TABLE_1>',\n 'rowId': 'u1',\n 'data': { 'name': 'Walter' }\n },\n {\n 'action': 'increment',\n 'databaseId': '<DB_B>',\n 'tableId': '<TABLE_2>',\n 'rowId': 'u2',\n 'data': { 'value': 1, 'min': 0, 'column': 'credits' }\n }\n ],\n);\n```\n```client-apple\ntry await tablesDB.createOperations(\n transactionId: tx.$id,\n operations: [\n [\n \"action\": \"create\",\n \"databaseId\": \"<DB_A>\",\n \"tableId\": \"<TABLE_1>\",\n \"rowId\": \"u1\",\n \"data\": [\"name\": \"Walter\"]\n ],\n [\n \"action\": \"increment\",\n \"databaseId\": \"<DB_B>\",\n \"tableId\": \"<TABLE_2>\",\n \"rowId\": \"u2\",\n \"data\": [\"value\": 1, \"min\": 0, \"column\": \"credits\"]\n ]\n ]\n)\n```\n```server-kotlin\ntablesDB.createOperations(\n transactionId = tx.$id,\n operations = listOf(\n mapOf(\n \"action\" to \"create\",\n \"databaseId\" to \"<DB_A>\",\n \"tableId\" to \"<TABLE_1>\",\n \"rowId\" to \"u1\",\n \"data\" to mapOf(\"name\" to \"Walter\")\n ),\n mapOf(\n \"action\" to \"increment\",\n \"databaseId\" to \"<DB_B>\",\n \"tableId\" to \"<TABLE_2>\",\n \"rowId\" to \"u2\",\n \"data\" to mapOf(\"value\" to 1, \"min\" to 0, \"column\" to \"credits\")\n )\n )\n)\n```\n```server-java\n// Stage multiple operations at once (asynchronous)\nList<Map<String, Object>> operations = Arrays.asList(\n Map.of(\n \"action\", \"create\",\n \"databaseId\", \"<DB_A>\",\n \"tableId\", \"<TABLE_1>\",\n \"rowId\", \"u1\",\n \"data\", Map.of(\"name\", \"Walter\")\n ),\n Map.of(\n \"action\", \"increment\",\n \"databaseId\", \"<DB_B>\",\n \"tableId\", \"<TABLE_2>\",\n \"rowId\", \"u2\",\n \"data\", Map.of(\"value\", 1, \"min\", 0, \"column\", \"credits\")\n )\n);\n\ntablesDB.createOperations(\n \"<TRANSACTION_ID>\",\n operations,\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return null;\n }\n System.out.println(result);\n return null;\n })\n);\n```\n```client-react-native\nawait tablesDB.createOperations({\n transactionId: tx.$id,\n operations: [\n {\n action: 'create',\n databaseId: '<DB_A>',\n tableId: '<TABLE_1>',\n rowId: 'u1',\n data: { name: 'Walter' }\n },\n {\n action: 'increment',\n databaseId: '<DB_B>',\n tableId: '<TABLE_2>',\n rowId: 'u2',\n data: { value: 1, min: 0, column: 'credits' }\n }\n ]\n});\n```\n```server-deno\nawait tablesDB.createOperations({\n transactionId: tx.$id,\n operations: [\n {\n action: 'create',\n databaseId: '<DB_A>',\n tableId: '<TABLE_1>',\n rowId: 'u1',\n data: { name: 'Walter' }\n },\n {\n action: 'increment',\n databaseId: '<DB_B>',\n tableId: '<TABLE_2>',\n rowId: 'u2',\n data: { value: 1, min: 0, column: 'credits' }\n }\n ]\n});\n```\n```server-php\n$tablesDB->createOperations(\n transactionId: $tx['$id'],\n operations: [\n [\n 'action' => 'create',\n 'databaseId' => '<DB_A>',\n 'tableId' => '<TABLE_1>',\n 'rowId' => 'u1',\n 'data' => [ 'name' => 'Walter' ]\n ],\n [\n 'action' => 'increment',\n 'databaseId' => '<DB_B>',\n 'tableId' => '<TABLE_2>',\n 'rowId' => 'u2',\n 'data' => [ 'value' => 1, 'min' => 0, 'column' => 'credits' ]\n ]\n ]\n);\n```\n```server-ruby\ntablesDB.create_operations(\n transaction_id: tx['$id'],\n operations: [\n {\n 'action' => 'create',\n 'databaseId' => '<DB_A>',\n 'tableId' => '<TABLE_1>',\n 'rowId' => 'u1',\n 'data' => { 'name' => 'Walter' }\n },\n {\n 'action' => 'increment',\n 'databaseId' => '<DB_B>',\n 'tableId' => '<TABLE_2>',\n 'rowId' => 'u2',\n 'data' => { 'value' => 1, 'min' => 0, 'column' => 'credits' }\n }\n ]\n)\n```\n```server-dotnet\nawait tablesDB.CreateOperations(\n transactionId: tx.Id,\n operations: new List<Dictionary<string, object>>\n {\n new Dictionary<string, object>\n {\n [\"action\"] = \"create\",\n [\"databaseId\"] = \"<DB_A>\",\n [\"tableId\"] = \"<TABLE_1>\",\n [\"rowId\"] = \"u1\",\n [\"data\"] = new Dictionary<string, object> { [\"name\"] = \"Walter\" }\n },\n new Dictionary<string, object>\n {\n [\"action\"] = \"increment\",\n [\"databaseId\"] = \"<DB_B>\",\n [\"tableId\"] = \"<TABLE_2>\",\n [\"rowId\"] = \"u2\",\n [\"data\"] = new Dictionary<string, object> { [\"value\"] = 1, [\"min\"] = 0, [\"column\"] = \"credits\" }\n }\n }\n);\n```\n```server-dart\nawait tablesDB.createOperations(\n transactionId: tx.Id,\n operations: [\n {\n 'action': 'create',\n 'databaseId': '<DB_A>',\n 'tableId': '<TABLE_1>',\n 'rowId': 'u1',\n 'data': { 'name': 'Walter' }\n },\n {\n 'action': 'increment',\n 'databaseId': '<DB_B>',\n 'tableId': '<TABLE_2>',\n 'rowId': 'u2',\n 'data': { 'value': 1, 'min': 0, 'column': 'credits' }\n }\n ]\n);\n```\n```server-swift\ntry await tablesDB.createOperations(\n transactionId: tx.$id,\n operations: [\n [\n \"action\": \"create\",\n \"databaseId\": \"<DB_A>\",\n \"tableId\": \"<TABLE_1>\",\n \"rowId\": \"u1\",\n \"data\": [\"name\": \"Walter\"]\n ],\n [\n \"action\": \"increment\",\n \"databaseId\": \"<DB_B>\",\n \"tableId\": \"<TABLE_2>\",\n \"rowId\": \"u2\",\n \"data\": [\"value\": 1, \"min\": 0, \"column\": \"credits\"]\n ]\n ]\n)\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::tables_db::TablesDB;\nuse serde_json::json;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n let client = Client::new()\n .set_endpoint(\"https://<REGION>.cloud.appwrite.io/v1\")\n .set_project(\"<PROJECT_ID>\")\n .set_key(\"<API_KEY>\");\n\n let tables_db = TablesDB::new(&client);\n\n let tx = tables_db.create_transaction(None).await?;\n\n // Stage multiple operations at once\n tables_db.create_operations(\n &tx.id,\n Some(vec![\n json!({\n \"action\": \"create\",\n \"databaseId\": \"<DB_A>\",\n \"tableId\": \"<TABLE_1>\",\n \"rowId\": \"u1\",\n \"data\": { \"name\": \"Walter\" }\n }),\n json!({\n \"action\": \"increment\",\n \"databaseId\": \"<DB_B>\",\n \"tableId\": \"<TABLE_2>\",\n \"rowId\": \"u2\",\n \"data\": { \"value\": 1, \"min\": 0, \"column\": \"credits\" }\n }),\n ]),\n ).await?;\n\n Ok(())\n}\n```\n{% /multicode %}\n\n# Commit or roll back {% #commit-or-rollback %}\n\nWhen you are done staging operations, call the `updateTransaction` method to finalize the transaction.\n\n{% multicode %}\n```client-web\n// Commit\nawait tablesDB.updateTransaction({\n transactionId: tx.$id,\n commit: true\n});\n\n// Or roll back\nawait tablesDB.updateTransaction({\n transactionId: tx.$id,\n rollback: true\n});\n```\n```client-flutter\n// Commit\nawait tablesDB.updateTransaction(\n transactionId: tx.$id,\n commit: true\n);\n\n// Roll back\nawait tablesDB.updateTransaction(\n transactionId: tx.$id,\n rollback: true\n);\n```\n```client-apple\n// Commit\ntry await tablesDB.updateTransaction(\n transactionId: tx.$id,\n commit: true\n)\n\n// Roll back\ntry await tablesDB.updateTransaction(\n transactionId: tx.$id,\n rollback: true\n)\n```\n```server-kotlin\n// Commit\ntablesDB.updateTransaction(\n transactionId = tx.$id,\n commit = true\n)\n\n// Roll back\ntablesDB.updateTransaction(\n transactionId = tx.$id,\n rollback = true\n)\n```\n```server-java\n// Commit (asynchronous)\ntablesDB.updateTransaction(\n \"<TRANSACTION_ID>\",\n true,\n false,\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return null;\n }\n System.out.println(result);\n return null;\n })\n);\n\n// Roll back (asynchronous)\ntablesDB.updateTransaction(\n \"<TRANSACTION_ID>\",\n false,\n true,\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return null;\n }\n System.out.println(result);\n return null;\n })\n);\n```\n```client-react-native\n// Commit\nawait tablesDB.updateTransaction({\n transactionId: tx.$id,\n commit: true\n});\n\n// Roll back\nawait tablesDB.updateTransaction({\n transactionId: tx.$id,\n rollback: true\n});\n```\n```server-nodejs\n// Commit\nawait tablesDB.updateTransaction({\n transactionId: tx.$id,\n commit: true\n});\n\n// Roll back\nawait tablesDB.updateTransaction({\n transactionId: tx.$id,\n rollback: true\n});\n```\n```server-python\n# Commit\ntablesDB.update_transaction(\n transaction_id = tx.id,\n commit = True\n)\n\n# Roll back\ntablesDB.update_transaction(\n transaction_id = tx.id,\n rollback = True\n)\n```\n```server-php\n// Commit\n$tablesDB->updateTransaction(\n transactionId: $tx['$id'],\n commit: true\n);\n\n// Roll back\n$tablesDB->updateTransaction(\n transactionId: $tx['$id'],\n rollback: true\n);\n```\n```server-ruby\n# Commit\ntablesDB.update_transaction(\n transaction_id: tx['$id'],\n commit: true\n)\n\n# Roll back\ntablesDB.update_transaction(\n transaction_id: tx['$id'],\n rollback: true\n)\n```\n```server-dotnet\n// Commit\nawait tablesDB.UpdateTransaction(\n transactionId: tx.Id,\n commit: true\n);\n\n// Roll back\nawait tablesDB.UpdateTransaction(\n transactionId: tx.Id,\n rollback: true\n);\n```\n```server-dart\n// Commit\nawait tablesDB.updateTransaction(\n transactionId: tx.Id,\n commit: true\n);\n\n// Roll back\nawait tablesDB.updateTransaction(\n transactionId: tx.Id,\n rollback: true\n);\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::tables_db::TablesDB;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n let client = Client::new()\n .set_endpoint(\"https://<REGION>.cloud.appwrite.io/v1\")\n .set_project(\"<PROJECT_ID>\")\n .set_key(\"<API_KEY>\");\n\n let tables_db = TablesDB::new(&client);\n\n // Commit\n tables_db.update_transaction(\n \"<TRANSACTION_ID>\",\n Some(true),\n None,\n ).await?;\n\n // Roll back\n tables_db.update_transaction(\n \"<TRANSACTION_ID>\",\n None,\n Some(true),\n ).await?;\n\n Ok(())\n}\n```\n{% /multicode %}\n\n# Handle conflicts {% #handle-conflicts %}\n\nOn 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.\n\n{% info title=\"Best practices\" %}\nKeep 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.\n{% /info %}\n\n{% arrow_link href=\"/docs/references\" %}\nExplore the API references\n{% /arrow_link %}"}, {"path": "docs/products/databases/type-generation", "title": "Type generation", "description": "Generate types from your Appwrite database schema. Learn how to use the Appwrite CLI to create and manage your types effectively.", "content": "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.\n\nTo generate types, the CLI reads the database schema from your project's `appwrite.json` file and generates type definitions for each table.\n\n## Generating types\n\nFirst, 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:\n\n```bash\nappwrite pull tables\n```\n\nTo generate types, you can use the Appwrite CLI command:\n\n```bash\nappwrite types [options] <output-directory>\n```\n\nThe following options are currently available:\n\n| Option | Description |\n|--------|-------------|\n| `--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. |\n| `--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. |\n| `--help` or `-h` | Displays help information for the command. |\n\n## Example usage\n\nSuppose you want to generate types for a table with data on books with the following schema from your `appwrite.json` file:\n\n```json\n{\n \"projectId\": \"682ca9a50004cf4b330f\",\n \"endpoint\": \"https://<REGION>.cloud.appwrite.io/v1\",\n \"projectName\": \"Appwrite project\",\n \"databases\": [\n {\n \"$id\": \"684c678b00211ddac082\",\n \"name\": \"Library\",\n \"enabled\": true\n }\n ],\n \"tables\": [\n {\n \"$id\": \"684c6790002d457ee89d\",\n \"$permissions\": [],\n \"databaseId\": \"684c678b00211ddac082\",\n \"name\": \"Books\",\n \"enabled\": true,\n \"rowSecurity\": false,\n \"columns\": [\n {\n \"key\": \"name\",\n \"type\": \"varchar\",\n \"required\": true,\n \"array\": false,\n \"size\": 255,\n \"default\": null\n },\n {\n \"key\": \"author\",\n \"type\": \"varchar\",\n \"required\": true,\n \"array\": false,\n \"size\": 255,\n \"default\": null\n },\n {\n \"key\": \"release_year\",\n \"type\": \"datetime\",\n \"required\": false,\n \"array\": false,\n \"format\": \"\",\n \"default\": null\n },\n {\n \"key\": \"category\",\n \"type\": \"varchar\",\n \"required\": false,\n \"array\": false,\n \"elements\": [\n \"fiction\",\n \"nonfiction\"\n ],\n \"format\": \"enum\",\n \"default\": null\n },\n {\n \"key\": \"genre\",\n \"type\": \"varchar\",\n \"required\": false,\n \"array\": true,\n \"size\": 100,\n \"default\": null\n },\n {\n \"key\": \"is_checked_out\",\n \"type\": \"boolean\",\n \"required\": true,\n \"array\": false,\n \"default\": null\n }\n ],\n \"indexes\": []\n }\n ]\n}\n```\n\nHere's how you can generate types for this table across all supported languages:\n\n{% tabs %}\n{% tabsitem #ts title=\"TypeScript\" %}\nRun the following command in your terminal:\n\n```bash\nappwrite types --language ts ./types\n```\n\nThis will generate the following types in the `./types` sub-directory of your project:\n\n```ts\nimport { type Models } from 'appwrite';\n\nexport enum Category {\n FICTION = \"fiction\",\n NONFICTION = \"nonfiction\",\n}\n\nexport type Books = Models.Row & {\n name: string;\n author: string;\n releaseYear: string | null;\n category: Category | null;\n genre: string[] | null;\n isCheckedOut: boolean;\n}\n```\n{% /tabsitem %}\n\n{% tabsitem #js title=\"JavaScript\" %}\nRun the following command in your terminal:\n\n```bash\nappwrite types --language js ./types\n```\n\nThis will generate the following types in the `./types` sub-directory of your project:\n\n```js\n/**\n * @typedef {import('appwrite').Models.Row} Row\n */\n\n\n/**\n * @typedef {Object} Books\n * @property {string} name\n * @property {string} author\n * @property {string|null|undefined} releaseYear\n * @property {\"fiction\"|\"nonfiction\"|null|undefined} category\n * @property {string[]|null|undefined} genre\n * @property {boolean} isCheckedOut\n */\n```\n{% /tabsitem %}\n\n{% tabsitem #java title=\"Java\" %}\nRun the following command in your terminal:\n\n```bash\nappwrite types --language java ./types\n```\n\nThis will generate the following types in the `./types` sub-directory of your project:\n\n```java\npackage io.appwrite.models;\n\nimport java.util.*;\npublic class Books {\n\n public enum Category {\n fiction,\n nonfiction;\n }\n\n private String name;\n private String author;\n private String releaseYear;\n private Category category;\n private List<String> genre;\n private boolean isCheckedOut;\n\n public Books() {\n }\n\n public Books(\n String name,\n String author,\n String releaseYear,\n Category category,\n List<String> genre,\n boolean isCheckedOut\n ) {\n this.name = name;\n this.author = author;\n this.releaseYear = releaseYear;\n this.category = category;\n this.genre = genre;\n this.isCheckedOut = isCheckedOut;\n }\n\n public String getName() {\n return name;\n }\n\n public void setName(String name) {\n this.name = name;\n }\n\n public String getAuthor() {\n return author;\n }\n\n public void setAuthor(String author) {\n this.author = author;\n }\n\n public String getReleaseYear() {\n return releaseYear;\n }\n\n public void setReleaseYear(String releaseYear) {\n this.releaseYear = releaseYear;\n }\n\n public Category getCategory() {\n return category;\n }\n\n public void setCategory(Category category) {\n this.category = category;\n }\n\n public List<String> getGenre() {\n return genre;\n }\n\n public void setGenre(List<String> genre) {\n this.genre = genre;\n }\n\n public boolean getIsCheckedOut() {\n return isCheckedOut;\n }\n\n public void setIsCheckedOut(boolean isCheckedOut) {\n this.isCheckedOut = isCheckedOut;\n }\n\n @Override\n public boolean equals(Object obj) {\n if (this == obj) return true;\n if (obj == null || getClass() != obj.getClass()) return false;\n Books that = (Books) obj;\n return Objects.equals(name, that.name) &&\n Objects.equals(author, that.author) &&\n Objects.equals(releaseYear, that.releaseYear) &&\n Objects.equals(category, that.category) &&\n Objects.equals(genre, that.genre) &&\n Objects.equals(isCheckedOut, that.isCheckedOut);\n }\n\n @Override\n public int hashCode() {\n return Objects.hash(name, author, releaseYear, category, genre, isCheckedOut);\n }\n\n @Override\n public String toString() {\n return \"Books{\" +\n \"name=\" + name +\n \"author=\" + author +\n \"releaseYear=\" + releaseYear +\n \"category=\" + category +\n \"genre=\" + genre +\n \"isCheckedOut=\" + isCheckedOut +\n '}';\n }\n}\n```\n{% /tabsitem %}\n\n{% tabsitem #php title=\"PHP\" %}\nRun the following command in your terminal:\n\n```bash\nappwrite types --language php ./types\n```\n\nThis will generate the following types in the `./types` sub-directory of your project:\n\n```php\n<?php\nnamespace Appwrite\\Models;\n\nenum Category: string {\n case FICTION = 'fiction';\n case NONFICTION = 'nonfiction';\n}\n\nclass Books {\n private string $name;\n private string $author;\n private string|null $releaseYear;\n private Category|null $category;\n private array $genre;\n private bool $isCheckedOut;\n\n public function __construct(\n string $name,\n string $author,\n ?string $releaseYear = null,\n ?Category $category = null,\n ?array $genre = null,\n bool $isCheckedOut\n ) {\n $this->name = $name;\n $this->author = $author;\n $this->releaseYear = $releaseYear;\n $this->category = $category;\n $this->genre = $genre;\n $this->isCheckedOut = $isCheckedOut;\n }\n\n public function getName(): string {\n return $this->name;\n }\n\n public function setName(string $name): void {\n $this->name = $name;\n }\n public function getAuthor(): string {\n return $this->author;\n }\n\n public function setAuthor(string $author): void {\n $this->author = $author;\n }\n public function getReleaseYear(): string|null {\n return $this->releaseYear;\n }\n\n public function setReleaseYear(string|null $releaseYear): void {\n $this->releaseYear = $releaseYear;\n }\n public function getCategory(): Category|null {\n return $this->category;\n }\n\n public function setCategory(Category|null $category): void {\n $this->category = $category;\n }\n public function getGenre(): array {\n return $this->genre;\n }\n\n public function setGenre(array $genre): void {\n $this->genre = $genre;\n }\n public function getIsCheckedOut(): bool {\n return $this->isCheckedOut;\n }\n\n public function setIsCheckedOut(bool $isCheckedOut): void {\n $this->isCheckedOut = $isCheckedOut;\n }\n}\n```\n{% /tabsitem %}\n\n{% tabsitem #dart title=\"Dart\" %}\nRun the following command in your terminal:\n\n```bash\nappwrite types --language dart ./types\n```\n\nThis will generate the following types in the `./types` sub-directory of your project:\n\n```dart\nenum Category {\n fiction,\n nonfiction,\n}\n\nclass Books {\n String name;\n String author;\n String? releaseYear;\n Category? category;\n List<String>? genre;\n bool isCheckedOut;\n\n Books({\n required this.name,\n required this.author,\n this.releaseYear,\n this.category,\n this.genre,\n required this.isCheckedOut,\n });\n\n factory Books.fromMap(Map<String, dynamic> map) {\n return Books(\n name: map['name'].toString(),\n author: map['author'].toString(),\n releaseYear: map['release_year']?.toString() ?? null,\n category: map['category'] != null ? Category.values.where((e) => e.name == map['category']).firstOrNull : null,\n genre: List<String>.from(map['genre'] ?? []) ?? [],\n isCheckedOut: map['is_checked_out'],\n );\n }\n\n Map<String, dynamic> toMap() {\n return {\n \"name\": name,\n \"author\": author,\n \"release_year\": releaseYear,\n \"category\": category?.name ?? null,\n \"genre\": genre,\n \"is_checked_out\": isCheckedOut,\n };\n }\n}\n```\n{% /tabsitem %}\n\n{% tabsitem #kotlin title=\"Kotlin\" %}\nRun the following command in your terminal:\n\n```bash\nappwrite types --language kotlin ./types\n```\n\nThis will generate the following types in the `./types` sub-directory of your project:\n\n```kotlin\npackage io.appwrite.models\n\nenum class Category {\n fiction,\n nonfiction\n}\n\ndata class Books(\n val name: String,\n val author: String,\n val releaseYear: String?,\n val category: Category?,\n val genre: List<String>?,\n val isCheckedOut: Boolean,\n)\n```\n{% /tabsitem %}\n\n{% tabsitem #swift title=\"Swift\" %}\nRun the following command in your terminal:\n\n```bash\nappwrite types --language swift ./types\n```\n\nThis will generate the following types in the `./types` sub-directory of your project:\n\n```swift\nimport Foundation\n\npublic enum Category: String, Codable, CaseIterable {\n case fiction = \"fiction\"\n case nonfiction = \"nonfiction\"\n}\n\npublic class Books: Codable {\n public let name: String\n public let author: String\n public let releaseYear: String?\n public let category: Category?\n public let genre: [String]?\n public let isCheckedOut: Bool\n\n enum CodingKeys: String, CodingKey {\n case name = \"name\"\n case author = \"author\"\n case releaseYear = \"release_year\"\n case category = \"category\"\n case genre = \"genre\"\n case isCheckedOut = \"is_checked_out\"\n }\n\n init(\n name: String,\n author: String,\n releaseYear: String?,\n category: Category?,\n genre: [String]?,\n isCheckedOut: Bool\n ) {\n self.name = name\n self.author = author\n self.releaseYear = releaseYear\n self.category = category\n self.genre = genre\n self.isCheckedOut = isCheckedOut\n }\n\n public required init(from decoder: Decoder) throws {\n let container = try decoder.container(keyedBy: CodingKeys.self)\n\n self.name = try container.decode(String.self, forKey: .name)\n self.author = try container.decode(String.self, forKey: .author)\n self.releaseYear = try container.decodeIfPresent(String.self, forKey: .releaseYear)\n self.category = try container.decodeIfPresent(Category.self, forKey: .category)\n self.genre = try container.decodeIfPresent([String].self, forKey: .genre)\n self.isCheckedOut = try container.decode(Bool.self, forKey: .isCheckedOut)\n }\n\n public func encode(to encoder: Encoder) throws {\n var container = encoder.container(keyedBy: CodingKeys.self)\n\n try container.encode(name, forKey: .name)\n try container.encode(author, forKey: .author)\n try container.encodeIfPresent(releaseYear, forKey: .releaseYear)\n try container.encodeIfPresent(category, forKey: .category)\n try container.encodeIfPresent(genre, forKey: .genre)\n try container.encode(isCheckedOut, forKey: .isCheckedOut)\n }\n\n public func toMap() -> [String: Any] {\n return [\n \"name\": name as Any,\n \"author\": author as Any,\n \"release_year\": releaseYear as Any,\n \"category\": category as Any,\n \"genre\": genre as Any,\n \"is_checked_out\": isCheckedOut as Any\n ]\n }\n\n public static func from(map: [String: Any]) -> Books {\n return Books(\n name: map[\"name\"] as! String,\n author: map[\"author\"] as! String,\n releaseYear: map[\"release_year\"] as? String,\n category: map[\"category\"] as? String,\n genre: map[\"genre\"] as? [String],\n isCheckedOut: map[\"is_checked_out\"] as! Bool\n )\n }\n}\n```\n{% /tabsitem %}\n{% /tabs %}"}, {"path": "docs/products/functions", "title": "Functions", "description": "Appwrite Functions is your gateway to scalable applications. Explore our complete guide to building and deploying serverless functions effortlessly.", "content": "Appwrite Functions unlock limitless potential for developers to extend Appwrite with code snippets. \nAppwrite Functions are user-defined functions that can start small and scale big, deploying automatically from source control. \nThese Functions can be triggered by HTTP requests, SDK methods, server events, webhooks, and scheduled executions. \nEach function will have its own URL, execute in its own isolated container, and have its own configurable environment variables and permissions.\n\n# Getting started {% #getting-started %}\n\nAppwrite Functions let you build anything you can imagine, but this flexibility makes it difficult to know where to start. \nStart exploring by cloning one of the quick start templates or using a template with pre-built integration to quickly implement features.\n\n{% only_dark %}\n![Create project screen](/images/docs/functions/dark/template.avif)\n{% /only_dark %}\n{% only_light %}\n![Create project screen](/images/docs/functions/template.avif)\n{% /only_light %}\n\n{% arrow_link href=\"/docs/products/functions/quick-start\" %}\nQuick start \n{% /arrow_link %}"}, {"path": "docs/products/functions/deploy-from-git", "title": "Deploy from Git", "description": "Learn to version and update your Appwrite Functions' code with deployments.", "content": "Appwrite Functions are mini-applications in Appwrite with their own endpoints. \nEach function can have many deployments, which can be thought of as versions of the mini-application.\n\nAppwrite Functions can be automatically deployed from Git repositories, \nso you can track changes to your function's code naturally as a part of you development workflow.\n\n# Create deployment {% #create-deployment %} \n\nThe recommended way to manage your Appwrite Function deployments is to use a version control system, like Git. \nThis offers simple versioning and collaboration that will easily fit into the rest of your development workflow.\n\nYou can only use Git deployment for Appwrite Functions connected to Git. \n[Create a new function with Git](/docs/products/functions/functions#create-function) or \nconnect your existing function to a Git repository in your function's **Settings** > **Configuration** > **Git settings** > **Connect Git**. \n\n1. Using Git, checkout the branch you configured as the production branch when creating the Appwrite Function.\n2. Create a new commit.\n3. Push your new commit.\n4. A new deployment will be automatically created, built and activated.\n\n## Commits to the production branch {% #commits-to-production-branch %}\n\nWhen you push a commit to the production branch, usually `main`, a new deployment will be created, built, and activated.\nThis means, the new deployments will **immediately replace the current active deployment** and handle all incoming requests.\n\n## Commits to other branches {% #commits-to-other-branches %}\n\nWhen you push a commit to a branch other than the production branch, a new deployment will be created, but it will not be activated.\nThis means, the new deployment will not handle any incoming requests until it is activated.\n\n# Git configuration\nIf you need to update your Git configuration, navigate to **Functions** > your function > \n**Settings** > **Configuration** > **Git settings**. \n\n## Build triggers {% #build-triggers %}\n\nBuild triggers control which Git changes create automatic deployments. You can configure branch filters and path filters with glob patterns.\n\n**Branch filters** match branch names. Add patterns to limit automatic deployments to specific branches.\n\n```txt\nmain\nstaging\npreview/**\n```\n\n**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.\n\n```txt\nfunctions/api/**\npackages/shared/**\n!docs/**\n```\n\nUse these formats to write glob patterns.\n\n{% partial file=\"git-glob-patterns.md\" /%}\n\n## Entry point {% #entry-point %}\nThe entry point is the code file contains the exported function that will be executed when the function is called.\nThis entry point has a specific format that must be followed. You can find examples using a [starter template](/docs/products/functions/templates)\nor following the [developing functions docs](/docs/products/functions/develop).\n\n## Root directory {% #root-directory %}\n\nThe root directory is the root of the code that will be copied to the executor. \nIf you have a monorepo, you can specify the subdirectory that contains the function's code using the root directory setting.\n\n## Share code between multiple functions {% #share-code-between-multiple-functions %}\n\nIf you're sharing code between multiple Appwrite Functions in a monorepo, referencing files outside of the entry point file will not work.\nTo share code between multiple functions, set the root directory to be the common root of the mono repo, and use `cd <working directory>`\nin your **Build settings** to navigate to the function's directory before building.\n\nAnother option is to use submodules in your Git repository to include shared code in each function's repository.\n\n# Debugging {% #debugging %}\n- If you updated your function's configuration but the deployment is not working as expected,\nyou may need to first redeploy your function before the changes take effect.\n\n- If you notice your function is missing dependencies during build or at runtimes, update it's build settings.\nNavigate to **Functions** > your function > **Settings** > **Configuration** > **Build settings**. \nThese commands will be ran before the function is built and can be used to install dependencies.\n\n- 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.\n\n- If you're self-hosting Appwrite, you will need to configure some [environment variables](/docs/advanced/self-hosting/functions) to enable Git deployments."}, {"path": "docs/products/functions/deploy-manually", "title": "Deploy manually", "description": "Learn to deploy Appwrite functions manually from the Appwrite CLI or the Appwrite Console.", "content": "Appwrite Functions are mini-applications in Appwrite with their own endpoints. \nEach function can have many deployments, which can be thought of as versions of the mini-application.\n\nWhile we recommend you create deployments through [automatic Git deployments](/docs/products/functions/deploy-from-git),\nyou can also create deployments manually or through the Appwrite CLI.\n\n# CLI {% #cli %} \n\n{% partial file=\"cli-function.md\" /%}\n\n## Configure CLI deployments {% #configure-cli-deployments %}\nIf 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, \nyou can do so by editing the `appwrite.config.json` file.\n\n{% arrow_link href=\"/docs/tooling/command-line/functions#appwritejson\" %}\nLearn more about appwrite.config.json\n{% /arrow_link %}\n\nFor 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).\n\n# Manual Deployment {% #manual %} \n\nYou can upload your functions to be deployed using the Appwrite Console. The example below shows a simple Node.js function.\n\n```text\n.\n├── package.json\n└── index.js\n```\n\nFirst, navigate inside the folder that contains your dependency file. Package your code files into the `.tar.gz` format:\n\n{% multicode %}\n ```bash\n tar --exclude code.tar.gz -czf code.tar.gz .\n ```\n ```cmd\n tar --exclude code.tar.gz -czf code.tar.gz .\n ```\n ```powershell\n tar --exclude code.tar.gz -czf code.tar.gz .\n ```\n{% /multicode %}\n\n\nNext, navigate to your Appwrite Console and upload the function.\n\n1. Navigate to the function you want to deploy.\n2. Click **Create deployment**.\n3. Select the **Manual** tab.\n4. Input the entry point of your function under **Entrypoint**. For the example above, it would be `index.js`.\n5. Upload `code.tar.gz`.\n6. Select **Activate deployment after build**.\n7. Click **Create**.\n\n\n# Debugging {% #debugging %}\n- If you updated your function's configuration but the deployment is not working as expected,\nyou may need to first redeploy your function before the changes take effect.\n\n- If you notice your function is missing dependencies during build or at runtimes, update it's build settings.\nNavigate to **Functions** > your function > **Settings** > **Configuration** > **Build settings**. \nThese commands will be ran before the function is built and can be used to install dependencies.\n\n- If you're missing some code files at build time, make sure they are included in the **Root directory**. \nOnly files in the root directory folder will be copied to the executor."}, {"path": "docs/products/functions/deployments", "title": "Deployments", "description": "Efficiently deploy your serverless functions with Appwrite. Explore deployment options, strategies, and best practices for seamless function execution.", "content": "Each function can have many deployments, which can be thought of as versions of the mini-application.\nFunctions can be created and deployed in different ways to meet your unique development habits. \n\n# Deployment status {% #deployment-status %}\nThroughout the life cycle of a deployment, it could have the following status.\n\n{% table %}\n* Status \n* description\n---\n* `active`\n* 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.\n---\n* `ready`\n* A deployment is built, but is not activated. `ready` deployments can be activated to replace the current active deployment.\n---\n* `building`\n* A deployment is being built. Check the [build log](#build-logs) for more detailed logs.\n---\n* `processing`\n* The function deployment has begun and has not finished.\n---\n* `waiting`\n* The deployment is queued but has not been picked up for processing.\n---\n* `failed`\n* A deployment was not successful. Check the [build log](#build-logs) for detailed logs for debugging.\n{% /table %}\n\n# Update deployment {% #update-deployment %}\nSome Function settings require redeploying your function to be reflected in your active deployment.\nWhen you update a function by changing it's **Git settings**, **Build settings**, and **Environment variables**, \nyou need to redeploy your function before they take effect.\n\n# Build logs {% #build-logs %}\nWhen you build a deployment, the logs generated will be saved for debugging purposes.\nYou can find build logs by navigating to the **deployments** tab of your function, clicking the three-dots menu beside, and click **Logs**.\n\n# Redeploy {% #redeploy %} \n\nAfter updating the configuration, redeploy your function for changes to take effect. You can also redeploy to retry failed builds.\n\n1. Navigate to **Functions**.\n2. Open the function you wish to inspect.\n3. Under the **Deployments** tab, find the status of the current active deployment.\n4. Redeploy by clicking the triple-dots beside an execution, and hitting the **Redeploy** button.\n\nRedeployment behavior varies depending on how the initial deployment was created.\n\n{% info title=\"Benefits for Pro+ users\" %}\nUsers subscribed to the Appwrite Pro plan or above receive certain special benefits:\n\n- [Express builds](/changelog/entry/2024-08-10) for quicker deployments, resulting in reduced wait times and smoother workflows\n- Longer [build timeouts](/docs/advanced/billing/compute#build-timeouts) (45 minutes vs 15 minutes on Free; Enterprise is custom)\n- Customizable [build and runtime specifications](/docs/advanced/billing/compute) for CPU and memory on each function\n{% /info %}\n\n# Deployment retention {% #deployment-retention %}\nDeployment 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.\n\nTo configure deployment retention from the Appwrite Console:\n\n1. Navigate to **Functions**.\n2. Open the function you want to configure.\n3. Go to **Settings** > **Deployment retention**.\n4. Turn on **Keep deployments forever**, or turn it off and choose how long to keep non-active deployments.\n5. Click **Update**.\n\n{% only_dark %}\n![Function deployment retention settings](/images/docs/functions/dark/deployment-retention.avif)\n{% /only_dark %}\n{% only_light %}\n![Function deployment retention settings](/images/docs/functions/deployment-retention.avif)\n{% /only_light %}\n\nThe 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.\n\nWhen updating a function with a Server SDK, pass the existing settings you do not intend to change and update only `deploymentRetention`.\n\n{% multicode %}\n\n```server-nodejs\nconst func = await functions.get({\n functionId: '<FUNCTION_ID>'\n});\n\nawait functions.update({\n functionId: func.$id,\n name: func.name,\n runtime: func.runtime,\n execute: func.execute ?? undefined,\n events: func.events ?? undefined,\n schedule: func.schedule ?? undefined,\n timeout: func.timeout ?? undefined,\n enabled: func.enabled ?? undefined,\n logging: func.logging ?? undefined,\n entrypoint: func.entrypoint ?? undefined,\n commands: func.commands ?? undefined,\n scopes: func.scopes ?? undefined,\n installationId: func.installationId ?? undefined,\n providerRepositoryId: func.providerRepositoryId ?? undefined,\n providerBranch: func.providerBranch ?? undefined,\n providerSilentMode: func.providerSilentMode ?? undefined,\n providerRootDirectory: func.providerRootDirectory ?? undefined,\n buildSpecification: func.buildSpecification ?? undefined,\n runtimeSpecification: func.runtimeSpecification ?? undefined,\n deploymentRetention: 90\n});\n```\n\n{% /multicode %}"}, {"path": "docs/products/functions/develop", "title": "Develop Appwrite Functions", "description": "Master serverless function development with Appwrite. Learn how to write and test functions locally, debug code, and optimize for efficient execution.", "content": "Appwrite Functions offer a familiar interface if you've developed REST endpoints.\nEach function is handled following a request and response pattern.\n\n# Lifecycle {% #life-cycle %}\nThere is a clear lifecycle for all Appwrite Functions, from beginning to end.\nHere's everything that happens during a function execution.\n\n1. The function is invoked.\n1. The active [deployment](/docs/products/functions/deployments)'s executor will handle the request.\n1. The Executor passes in request information like headers, body or path through the `context.req` object of your exported function.\n1. The runtime executes the code you defined, you can log through the `context.log()` or `context.error()` methods.\n1. Function terminates when you return results using `return context.res.text()`, `return context.res.json()` or similar.\n\n[Locally developed functions](/docs/products/functions/develop-locally) follow the same lifecycle on your local machine.\n\n## Entrypoint {% #entrypoint %}\nYou'll find all of these steps in a simple function like this.\nNotice the exported entry point that the executor will call.\n\n{% multicode %}\n```server-nodejs\nimport { Client } from 'node-appwrite';\n\n// This is your Appwrite function\n// It's executed each time we get a request\nexport default async ({ req, res, log, error }) => {\n // Why not try the Appwrite SDK?\n //\n // Set project and set API key\n // const client = new Client()\n // .setProject(process.env.APPWRITE_FUNCTION_PROJECT_ID)\n // .setKey(req.headers['x-appwrite-key']);\n\n // You can log messages to the console\n log('Hello, Logs!');\n\n // If something goes wrong, log an error\n error('Hello, Errors!');\n\n // The `req` object contains the request data\n if (req.method === 'GET') {\n // Send a response with the res object helpers\n // `res.text()` dispatches a string back to the client\n return res.text('Hello, World!');\n }\n\n // `res.json()` is a handy helper for sending JSON\n return res.json({\n motto: 'Build like a team of hundreds_',\n learn: 'https://appwrite.io/docs',\n connect: 'https://appwrite.io/discord',\n getInspired: 'https://builtwith.appwrite.io',\n });\n};\n```\n\n```php\nrequire(__DIR__ . '/../vendor/autoload.php');\n\nuse Appwrite\\Client;\nuse Appwrite\\Exception;\n\n// This is your Appwrite function\n// It's executed each time we get a request\nreturn function ($context) {\n // Why not try the Appwrite SDK?\n //\n // Set project and set API key\n // $client = (new Client())\n // ->setProject(getenv(APPWRITE_FUNCTION_PROJECT_ID))\n // ->setKey($context->req->headers['x-appwrite-key']);\n \n // You can log messages to the console\n $context->log('Hello, Logs!');\n\n // If something goes wrong, log an error\n $context->error('Hello, Errors!');\n\n // The `req` object contains the request data\n if ($context->req->method === 'GET') {\n // Send a response with the res object helpers\n // `res.text()` dispatches a string back to the client\n return $context->res->text('Hello, World!');\n }\n\n // `res.json()` is a handy helper for sending JSON\n return $context->res->json([\n 'motto' => 'Build like a team of hundreds_',\n 'learn' => 'https://appwrite.io/docs',\n 'connect' => 'https://appwrite.io/discord',\n 'getInspired' => 'https://builtwith.appwrite.io',\n ]);\n};\n```\n\n```python\nfrom appwrite.client import Client\nimport os\n\n\n# This is your Appwrite function\n# It's executed each time we get a request\ndef main(context):\n # Why not try the Appwrite SDK?\n #\n # Set project and set API key\n # client = (\n # Client()\n # .set_project(os.environ[\"APPWRITE_FUNCTION_PROJECT_ID\"])\n # .set_key(context.req.headers[\"x-appwrite-key\"])\n # )\n\n # You can log messages to the console\n context.log(\"Hello, Logs!\")\n\n # If something goes wrong, log an error\n context.error(\"Hello, Errors!\")\n\n # The `context.req` object contains the request data\n if context.req.method == \"GET\":\n # Send a response with the res object helpers\n # `context.res.text()` dispatches a string back to the client\n return context.res.text(\"Hello, World!\")\n\n # `context.res.json()` is a handy helper for sending JSON\n return context.res.json({\n \"motto\": \"Build like a team of hundreds_\",\n \"learn\": \"https://appwrite.io/docs\",\n \"connect\": \"https://appwrite.io/discord\",\n \"getInspired\": \"https://builtwith.appwrite.io\",\n })\n```\n```ruby\nrequire \"appwrite\"\n\n# This is your Appwrite function\n# It's executed each time we get a request\ndef main(context)\n # Why not try the Appwrite SDK?\n #\n # Set project and set API key\n # client = Client.new\n # .set_project(ENV['APPWRITE_FUNCTION_PROJECT_ID'])\n # .set_key(context.req.headers['x-appwrite-key'])\n\n # You can log messages to the console\n context.log(\"Hello, Logs!\")\n\n # If something goes wrong, log an error\n context.error(\"Hello, Errors!\")\n\n # The `context.req` object contains the request data\n if (context.req.method == \"GET\")\n # Send a response with the res object helpers\n # `context.res.text()` dispatches a string back to the client\n return context.res.text(\"Hello, World!\")\n end\n\n # `context.res.json()` is a handy helper for sending JSON\n return context.res.json({\n \"motto\": \"Build like a team of hundreds_\",\n \"learn\": \"https://appwrite.io/docs\",\n \"connect\": \"https://appwrite.io/discord\",\n \"getInspired\": \"https://builtwith.appwrite.io\",\n })\nend\n```\n```deno\nimport { Client } from \"npm:node-appwrite\";\n\n// This is your Appwrite function\n// It's executed each time we get a request\nexport default ({ req, res, log, error }: any) => {\n // Why not try the Appwrite SDK?\n //\n // Set project and set API key\n // const client = new Client()\n // .setProject(Deno.env.get(\"APPWRITE_FUNCTION_PROJECT_ID\") || \"\")\n // .setKey(req.headers[\"x-appwrite-key\"] || \"\");\n\n // You can log messages to the console\n log(\"Hello, Logs!\");\n\n // If something goes wrong, log an error\n error(\"Hello, Errors!\");\n\n // The `req` object contains the request data\n if (req.method === \"GET\") {\n // Send a response with the res object helpers\n // `res.text()` dispatches a string back to the client\n return res.text(\"Hello, World!\");\n }\n\n // `res.json()` is a handy helper for sending JSON\n return res.json({\n motto: \"Build like a team of hundreds_\",\n learn: \"https://appwrite.io/docs\",\n connect: \"https://appwrite.io/discord\",\n getInspired: \"https://builtwith.appwrite.io\",\n });\n};\n```\n```go\npackage handler\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/appwrite/sdk-for-go/appwrite\"\n\t\"github.com/open-runtimes/types-for-go/v4/openruntimes\"\n)\n\ntype Response struct {\n\tMotto string `json:\"motto\"`\n\tLearn string `json:\"learn\"`\n\tConnect string `json:\"connect\"`\n\tGetInspired string `json:\"getInspired\"`\n}\n\nfunc Main(Context openruntimes.Context) openruntimes.Response {\n\t// This is your Appwrite function\n\t// It's executed each time we get a request service\n\tvar _ = appwrite.NewClient(\n\t\tappwrite.WithProject(os.Getenv(\"APPWRITE_FUNCTION_PROJECT_ID\")),\n\t\tappwrite.WithKey(Context.Req.Headers[\"x-appwrite-key\"]),\n\t)\n\n\t// You can log messages to the console\n\tfmt.Println(\"Hello, Logs!\")\n\n\tfmt.Fprintln(os.Stderr, \"Error:\", \"Hello, Errors!\")\n\n\t// The `Context.Req` object contains the request data\n\tif Context.Req.Method == \"GET\" {\n\t\t// Send a response with the Context.Res object helpers\n\t\t// `Context.Res.Text()` dispatches a string back to the client\n\t\treturn Context.Res.Text(\"Hello, World!\")\n\t}\n\n\t// `res.json()` is a handy helper for sending JSON\n\treturn Context.Res.Json(\n\t\tResponse{\n\t\t\tMotto: \"Build like a team of hundreds_\",\n\t\t\tLearn: \"https://appwrite.io/docs\",\n\t\t\tConnect: \"https://appwrite.io/discord\",\n\t\t\tGetInspired: \"https://builtwith.appwrite.io\",\n\t\t})\n}\n```\n```dart\nimport 'dart:async';\nimport 'package:dart_appwrite/dart_appwrite.dart';\n\n// This is your Appwrite function\n// It's executed each time we get a request\nFuture main(final context) async {\n // Why not try the Appwrite SDK?\n //\n // Set project and set API key\n // final client = Client()\n // .setProject(Platform.environment['APPWRITE_FUNCTION_PROJECT_ID'])\n // .setKey(context.req.headers['x-appwrite-key']);\n\n\n // You can log messages to the console\n context.log('Hello, Logs!');\n\n // If something goes wrong, log an error\n context.error('Hello, Errors!');\n\n // The `req` object contains the request data\n if (context.req.method == 'GET') {\n // Send a response with the res object helpers\n // `res.text()` dispatches a string back to the client\n return context.res.text('Hello, World!');\n }\n\n // `res.json()` is a handy helper for sending JSON\n return context.res.json({\n 'motto': 'Build like a team of hundreds_',\n 'learn': 'https://appwrite.io/docs',\n 'connect': 'https://appwrite.io/discord',\n 'getInspired': 'https://builtwith.appwrite.io',\n });\n}\n```\n```kotlin\npackage io.openruntimes.kotlin.src\n\nimport io.openruntimes.kotlin.RuntimeContext\nimport io.openruntimes.kotlin.RuntimeOutput\nimport io.appwrite.Client\nimport java.util.HashMap\n\nclass Main {\n // This is your Appwrite function\n // It's executed each time we get a request\n fun main(context: RuntimeContext): RuntimeOutput {\n // Why not try the Appwrite SDK?\n //\n // Set project and set API key\n // val client = Client()\n // .setProject(System.getenv(\"APPWRITE_FUNCTION_PROJECT_ID\"))\n // .setKey(context.req.headers[\"x-appwrite-key\"])\n\n // You can log messages to the console\n context.log(\"Hello, Logs!\")\n\n // If something goes wrong, log an error\n context.error(\"Hello, Errors!\")\n\n // The `context.req` object contains the request data\n if (context.req.method == \"GET\") {\n // Send a response with the res object helpers\n // `context.res.text()` dispatches a string back to the client\n return context.res.text(\"Hello, World!\")\n }\n\n // `context.res.json()` is a handy helper for sending JSON\n return context.res.json(mutableMapOf(\n \"motto\" to \"Build like a team of hundreds_\",\n \"learn\" to \"https://appwrite.io/docs\",\n \"connect\" to \"https://appwrite.io/discord\",\n \"getInspired\" to \"https://builtwith.appwrite.io\"\n ))\n }\n}\n```\n```java\npackage io.openruntimes.java.src;\n\nimport io.openruntimes.java.RuntimeContext;\nimport io.openruntimes.java.RuntimeOutput;\nimport java.util.HashMap;\nimport io.appwrite.Client;\n\npublic class Main {\n\n // This is your Appwrite function\n // It's executed each time we get a request\n public RuntimeOutput main(RuntimeContext context) throws Exception {\n // Why not try the Appwrite SDK?\n //\n // Set project and set API key\n // Client client = new Client();\n // .setProject(System.getenv(\"APPWRITE_FUNCTION_PROJECT_ID\"))\n // .setKey(context.getReq().getHeaders().get(\"x-appwrite-key\"));\n\n // You can log messages to the console\n context.log(\"Hello, Logs!\");\n\n // If something goes wrong, log an error\n context.error(\"Hello, Errors!\");\n\n // The `context.getReq()` object contains the request data\n if (context.getReq().getMethod().equals(\"GET\")) {\n // Send a response with the res object helpers\n // `context.getRes().text()` dispatches a string back to the client\n return context.getRes().text(\"Hello, World!\");\n }\n\n Map json = new HashMap<>();\n json.put(\"motto\", \"Build like a team of hundreds_\");\n json.put(\"learn\", \"https://appwrite.io/docs\");\n json.put(\"connect\", \"https://appwrite.io/discord\");\n json.put(\"getInspired\", \"https://builtwith.appwrite.io\");\n\n // `context.getRes().json()` is a handy helper for sending JSON\n return context.getRes().json(json);\n }\n}\n```\n```swift\nimport Appwrite\nimport AppwriteModels\nimport Foundation\n\n// This is your Appwrite function\n// It's executed each time we get a request\nfunc main(context: RuntimeContext) async throws -> RuntimeOutput {\n // Why not try the Appwrite SDK?\n //\n // Set project and set API key\n // let client = Client()\n // .setProject(ProcessInfo.processInfo.environment[\"APPWRITE_FUNCTION_PROJECT_ID\"])\n // .setKey(context.req.headers[\"x-appwrite-key\"] ?? \"\")\n\n // You can log messages to the console\n context.log(\"Hello, Logs!\")\n\n // If something goes wrong, log an error\n context.error(\"Hello, Errors!\")\n\n // The `context.req` object contains the request data\n if context.req.method == \"GET\" {\n // Send a response with the res object helpers\n // `res.text()` dispatches a string back to the client\n return context.res.text(\"Hello, World!\")\n }\n\n // `context.res.json()` is a handy helper for sending JSON\n return try context.res.json([\n \"motto\": \"Build like a team of hundreds_\",\n \"learn\": \"https://appwrite.io/docs\",\n \"connect\": \"https://appwrite.io/discord\",\n \"getInspired\": \"https://builtwith.appwrite.io\",\n ])\n}\n```\n```csharp\nnamespace DotNetRuntime;\n\nusing Appwrite;\nusing Appwrite.Services;\nusing Appwrite.Models;\n\npublic class Handler {\n\n // This is your Appwrite function\n // It\"s executed each time we get a request\n public async Task Main(RuntimeContext Context)\n {\n // Why not try the Appwrite SDK?\n //\n // Set project and set API key\n // var client = new Client()\n // .SetProject(Environment.GetEnvironmentVariable(\"APPWRITE_FUNCTION_PROJECT_ID\"))\n // .SetKey(Context.Req.Headers[\"x-appwrite-key\"]);\n\n // You can log messages to the console\n Context.Log(\"Hello, Logs!\");\n\n // If something goes wrong, log an error\n Context.Error(\"Hello, Errors!\");\n\n // The `Context.Req` object contains the request data\n if (Context.Req.Method == \"GET\") {\n // Send a response with the res object helpers\n // `Context.Res.Text()` dispatches a string back to the client\n return Context.Res.Text(\"Hello, World!\");\n }\n\n // `Context.Res.Json()` is a handy helper for sending JSON\n return Context.Res.Json(new Dictionary()\n {\n { \"motto\", \"Build like a team of hundreds_\" },\n { \"learn\", \"https://appwrite.io/docs\" },\n { \"connect\", \"https://appwrite.io/discord\" },\n { \"getInspired\", \"https://builtwith.appwrite.io\" },\n });\n }\n}\n```\n```rust\nuse openruntimes::{Context, Response};\nuse serde_json::json;\n\n// This is your Appwrite function\n// It's executed each time we get a request\npub fn main(context: Context) -> Response {\n // Why not try the Appwrite SDK?\n //\n // Set project and set API key\n // let client = appwrite::Client::new()\n // .set_endpoint(std::env::var(\"APPWRITE_FUNCTION_API_ENDPOINT\").unwrap_or_default())\n // .set_project(std::env::var(\"APPWRITE_FUNCTION_PROJECT_ID\").unwrap_or_default())\n // .set_key(context.req.headers.get(\"x-appwrite-key\").cloned().unwrap_or_default());\n\n // You can log messages to the console\n context.log(\"Hello, Logs!\");\n\n // If something goes wrong, log an error\n context.error(\"Hello, Errors!\");\n\n // The `context.req` object contains the request data\n if context.req.method == \"GET\" {\n // Send a response with the res object helpers\n // `context.res.text()` dispatches a string back to the client\n return context.res.text(\"Hello, World!\", None, None);\n }\n\n // `context.res.json()` is a handy helper for sending JSON\n context.res.json(\n json!({\n \"motto\": \"Build like a team of hundreds_\",\n \"learn\": \"https://appwrite.io/docs\",\n \"connect\": \"https://appwrite.io/discord\",\n \"getInspired\": \"https://builtwith.appwrite.io\",\n }),\n None,\n None,\n )\n}\n```\n{% /multicode %}\n\nIf you prefer to learn through more examples like this, explore the [examples page](/docs/products/functions/examples).\n\n# Context object {% #context-object %}\nContext is an object passed into every function to handle communication to both the end users, and logging to the Appwrite Console.\nAll input, output, and logging **must be handled through the context object** passed in.\n\nYou'll find these properties in the context object.\n\n| Property | Description |\n|----------|--------------------------------------------------------------------------------------------------------------------------|\n| req | Contains request information like method, body, and headers. See full examples [in the request section](#request). |\n| res | Contains methods to build a response and return information. See full examples [in the response section](#response). |\n| 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). |\n| 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). |\n\n{% info title=\"Depreciation notice\" %}\nUse `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.\n{% /info %}\n\n### Destructuring assignment {% #destructuring %}\nSome languages, namely JavaScript, support destructuring.\nYou'll see us use destructuring in examples, which has the following syntax.\n\n[Learn more about destructuring assignment](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment).\n{% multicode %}\n```server-nodejs\n// before destructuring\nexport default async function (context) {\n context.log(\"This is a log!\");\n return context.res.text(\"This is a response!\");\n}\n\n// after destructuring\nexport default async function ({ req, res, log, error }) {\n log(\"This is a log!\");\n return res.text(\"This is a response!\");\n}\n```\n```deno\n// before destructuring\nexport default async function (context: any) {\n context.log(\"This is a log!\");\n return context.res.text(\"This is a response!\");\n}\n\n// after destructuring\nexport default async function ({ req, res, log, error }: any) {\n log(\"This is a log!\");\n return res.text(\"This is a response!\");\n}\n```\n{% /multicode %}\n\n## Request {% #request %}\n\nIf you pass data into an Appwrite Function, it'll be found in the request object.\nThis includes all invocation inputs from Appwrite SDKs, HTTP calls, Appwrite events, or browsers visiting the configured domain.\nExplore the request object with the following function, which logs all request params to the Appwrite Console.\n\n### Request types {% #request-types %}\n\n{% table %}\n* Request\n* Description\n---\n* `req.bodyText`\n* Returns text that has been converted from binary data.\n---\n* `req.bodyJson`\n* Parses the body text as JSON.\n---\n* `req.bodyBinary`\n* Returns the binary body.\n---\n{% /table %}\n\n{% multicode %}\n```server-nodejs\nexport default async ({ req, res, log }) => {\n log(req.bodyText); // Raw request body, contains request data\n log(JSON.stringify(req.bodyJson)); // Object from parsed JSON request body, otherwise string\n log(JSON.stringify(req.headers)); // String key-value pairs of all request headers, keys are lowercase\n log(req.scheme); // Value of the x-forwarded-proto header, usually http or https\n log(req.method); // Request method, such as GET, POST, PUT, DELETE, PATCH, etc.\n log(req.url); // Full URL, for example: http://awesome.appwrite.io:8000/v1/hooks?limit=12&offset=50\n log(req.host); // Hostname from the host header, such as awesome.appwrite.io\n log(req.port); // Port from the host header, for example 8000\n log(req.path); // Path part of URL, for example /v1/hooks\n log(req.queryString); // Raw query params string. For example \"limit=12&offset=50\"\n log(JSON.stringify(req.query)); // Parsed query params. For example, req.query.limit\n\n return res.text(\"All the request parameters are logged to the Appwrite Console.\");\n};\n```\n```php\n<?php\nreturn function ($context) {\n $context->log(json_encode($context->req->bodyJson));// Object from parsed JSON request body, otherwise string\n $context->log(json_encode($context->req->headers)); // String key-value pairs of all request headers, keys are lowercase\n $context->log($context->req->scheme); // Value of the x-forwarded-proto header, usually http or https\n $context->log($context->req->method); // Request method, such as GET, POST, PUT, DELETE, PATCH, etc.\n $context->log($context->req->url); // Full URL, for example: http://awesome.appwrite.io:8000/v1/hooks?limit=12&offset=50\n $context->log($context->req->host); // Hostname from the host header, such as awesome.appwrite.io\n $context->log($context->req->port); // Port from the host header, for example 8000\n $context->log($context->req->path); // Path part of URL, for example /v1/hooks\n $context->log($context->req->queryString); // Raw query params string. For example \"limit=12&offset=50\"\n $context->log(json_encode($context->req->query)); // Parsed query params. For example, req.query.limit\n\n return $context->res->text(\"All the request parameters are logged to the Appwrite Console.\");\n}\n```\n```python\nimport json\n\ndef main(context):\n context.log(context.req.body_text) # Raw request body, contains request data\n context.log(json.dumps(context.req.body_json)) # Object from parsed JSON request body, otherwise string\n context.log(json.dumps(context.req.headers)) # String key-value pairs of all request headers, keys are lowercase\n context.log(context.req.scheme) # Value of the x-forwarded-proto header, usually http or https\n context.log(context.req.method) # Request method, such as GET, POST, PUT, DELETE, PATCH, etc.\n context.log(context.req.url) # Full URL, for example: http://awesome.appwrite.io:8000/v1/hooks?limit=12&offset=50\n context.log(context.req.host) # Hostname from the host header, such as awesome.appwrite.io\n context.log(context.req.port) # Port from the host header, for example 8000\n context.log(context.req.path) # Path part of URL, for example /v1/hooks\n context.log(context.req.query_string) # Raw query params string. For example \"limit=12&offset=50\"\n context.log(json.dumps(context.req.query)) # Parsed query params. For example, req.query.limit\n\n return context.res.text(\"All the request parameters are logged to the Appwrite Console.\")\n```\n```ruby\nrequire 'json'\n\ndef main(context)\n context.log(context.req.body_text) # Raw request body, contains request data\n context.log(JSON.generate(context.req.body_json)) # Object from parsed JSON request body, otherwise string\n context.log(JSON.generate(context.req.headers)) # String key-value pairs of all request headers, keys are lowercase\n context.log(context.req.scheme) # Value of the x-forwarded-proto header, usually http or https\n context.log(context.req.method) # Request method, such as GET, POST, PUT, DELETE, PATCH, etc.\n context.log(context.req.url) # Full URL, for example: http://awesome.appwrite.io:8000/v1/hooks?limit=12&offset=50\n context.log(context.req.host) # Hostname from the host header, such as awesome.appwrite.io\n context.log(context.req.port) # Port from the host header, for example 8000\n context.log(context.req.path) # Path part of URL, for example /v1/hooks\n context.log(context.req.query_string) # Raw query params string. For example \"limit=12&offset=50\"\n context.log(JSON.generate(context.req.query)) # Parsed query params. For example, req.query.limit\n\n return context.res.text(\"All the request parameters are logged to the Appwrite Console.\")\nend\n```\n```deno\nexport default async ({ req, res, log }: any) => {\n log(req.bodyText); // Raw request body, contains request data\n log(JSON.stringify(req.bodyJson)); // Object from parsed JSON request body, otherwise string\n log(JSON.stringify(req.headers)); // String key-value pairs of all request headers, keys are lowercase\n log(req.scheme); // Value of the x-forwarded-proto header, usually http or https\n log(req.method); // Request method, such as GET, POST, PUT, DELETE, PATCH, etc.\n log(req.url); // Full URL, for example: http://awesome.appwrite.io:8000/v1/hooks?limit=12&offset=50\n log(req.host); // Hostname from the host header, such as awesome.appwrite.io\n log(req.port); // Port from the host header, for example 8000\n log(req.path); // Path part of URL, for example /v1/hooks\n log(req.queryString); // Raw query params string. For example \"limit=12&offset=50\"\n log(JSON.stringify(req.query)); // Parsed query params. For example, req.query.limit\n\n return res.text(\"All the request parameters are logged to the Appwrite Console.\");\n}\n```\n```go\npackage handler\n\nimport (\n\t\"encoding/json\"\n\n\t\"github.com/open-runtimes/types-for-go/v4/openruntimes\"\n)\n\nfunc Main(Context openruntimes.Context) openruntimes.Response {\n\tContext.Log(Context.Req.BodyText) // Raw request body, contains request data\n\tContext.Log(json.Marshal(Context.Req.BodyJson)) // Object from parsed JSON request body, otherwise string\n\tContext.Log(json.Marshal(Context.Req.Headers)) // String key-value pairs of all request headers, keys are lowercase\n\tContext.Log(Context.Req.Scheme) // Value of the x-forwarded-proto header, usually http or https\n\tContext.Log(Context.Req.Method) // Request method, such as GET, POST, PUT, DELETE, PATCH, etc.\n\tContext.Log(Context.Req.Url) // Full URL, for example: http://awesome.appwrite.io:8000/v1/hooks?limit=12&offset=50\n\tContext.Log(Context.Req.Host) // Hostname from the host header, such as awesome.appwrite.io\n\tContext.Log(Context.Req.Port) // Port from the host header, for example 8000\n\tContext.Log(Context.Req.Path) // Path part of URL, for example /v1/hooks\n\tContext.Log(Context.Req.QueryString) // Raw query params string. For example \"limit=12&offset=50\"\n\tContext.Log(json.Marshal(Context.Req.Query)) // Parsed query params. For example, req.query.limit\n\n\treturn Context.Res.Text(\"All the request parameters are logged to the Appwrite Console.\")\n}\n```\n```dart\nimport 'dart:async';\nimport 'dart:convert';\n\nFuture<dynamic> main(final context) async {\n context.log(context.req.bodyText); // Raw request body, contains request data\n context.log(json.encode(context.req.bodyJson)); // Object from parsed JSON request body, otherwise string\n context.log(json.encode(context.req.headers)); // String key-value pairs of all request headers, keys are lowercase\n context.log(context.req.scheme); // Value of the x-forwarded-proto header, usually http or https\n context.log(context.req.method); // Request method, such as GET, POST, PUT, DELETE, PATCH, etc.\n context.log(context.req.url); // Full URL, for example: http://awesome.appwrite.io:8000/v1/hooks?limit=12&offset=50\n context.log(context.req.host); // Hostname from the host header, such as awesome.appwrite.io\n context.log(context.req.port); // Port from the host header, for example 8000\n context.log(context.req.path); // Path part of URL, for example /v1/hooks\n context.log(context.req.queryString); // Raw query params string. For example \"limit=12&offset=50\"\n context.log(json.encode(context.req.query)); // Parsed query params. For example, req.query.limit\n\n return context.res.text(\"All the request parameters are logged to the Appwrite Console.\");\n}\n```\n```swift\nimport Foundation\nimport Foundation\n\nfunc main(context: RuntimeContext) async throws -> RuntimeOutput {\n context.log(context.req.bodyJson) // Raw request body, contains request data\n context.log(NSJSONSerialization.jsonObject(with: context.req.bodyJson, options: [])!) // Object from parsed JSON request body, otherwise string\n context.log(NSJSONSerialization.jsonObject(with: context.req.headers, options: [])!) // String key-value pairs of all request headers, keys are lowercase\n context.log(context.req.scheme) // Value of the x-forwarded-proto header, usually http or https\n context.log(context.req.method) // Request method, such as GET, POST, PUT, DELETE, PATCH, etc.\n context.log(context.req.url) // Full URL, for example: http://awesome.appwrite.io:8000/v1/hooks?limit=12&offset=50\n context.log(context.req.host) // Hostname from the host header, such as awesome.appwrite.io\n context.log(context.req.port) // Port from the host header, for example 8000\n context.log(context.req.path) // Path part of URL, for example /v1/hooks\n context.log(context.req.queryString) // Raw query params string. For example \"limit=12&offset=50\"\n context.log(NSJSONSerialization.jsonObject(with: context.req.query, options: [])!) // Parsed query params. For example, req.query.limit\n\n return context.res.text(\"All the request parameters are logged to the Appwrite Console.\")\n}\n```\n```csharp\nnamespace DotNetRuntime;\n\nusing System.Text.Json;\n\npublic class Handler {\n public async Task<RuntimeOutput> Main(RuntimeContext Context)\n {\n Context.Log(JsonSerializer.Serialize<object>(Context.Req.BodyJson)); // Object from parsed JSON request body, otherwise string\n Context.Log(JsonSerializer.Serialize<object>(Context.Req.Headers)); // String key-value pairs of all request headers, keys are lowercase\n Context.Log(Context.Req.Scheme); // Value of the x-forwarded-proto header, usually http or https\n Context.Log(Context.Req.Method); // Request method, such as GET, POST, PUT, DELETE, PATCH, etc.\n Context.Log(Context.Req.Url); // Full URL, for example: http://awesome.appwrite.io:8000/v1/hooks?limit=12&offset=50\n Context.Log(Context.Req.Host); // Hostname from the host header, such as awesome.appwrite.io\n Context.Log(Context.Req.Port); // Port from the host header, for example 8000\n Context.Log(Context.Req.Path); // Path part of URL, for example /v1/hooks\n Context.Log(Context.Req.QueryString); // Raw query params string. For example \"limit=12&offset=50\"\n Context.Log(JsonSerializer.Serialize<object>(Context.Req.Query)); // Parsed query params. For example, req.query.limit\n\n return Context.Res.Text(\"All the request parameters are logged to the Appwrite Console.\");\n }\n}\n```\n```kotlin\npackage io.openruntimes.kotlin.src\n\nimport io.openruntimes.kotlin.RuntimeContext\nimport io.openruntimes.kotlin.RuntimeOutput\nimport com.google.gson.Gson\n\nclass Main {\n fun main(context: RuntimeContext): RuntimeOutput {\n val gson = Gson()\n\n context.log(context.req.bodyJson) // Raw request body, contains request data\n context.log(gson.toString(context.req.bodyJson)) // Object from parsed JSON request body, otherwise string\n context.log(gson.toString(context.req.headers)) // String key-value pairs of all request headers, keys are lowercase\n context.log(context.req.scheme) // Value of the x-forwarded-proto header, usually http or https\n context.log(context.req.method) // Request method, such as GET, POST, PUT, DELETE, PATCH, etc.\n context.log(context.req.url) // Full URL, for example: http://awesome.appwrite.io:8000/v1/hooks?limit=12&offset=50\n context.log(context.req.host) // Hostname from the host header, such as awesome.appwrite.io\n context.log(context.req.port) // Port from the host header, for example 8000\n context.log(context.req.path) // Path part of URL, for example /v1/hooks\n context.log(context.req.queryString) // Raw query params string. For example \"limit=12&offset=50\"\n context.log(gson.toString(context.req.query)) // Parsed query params. For example, req.query.limit\n\n return context.res.text(\"All the request parameters are logged to the Appwrite Console.\")\n }\n}\n```\n```java\npackage io.openruntimes.java;\n\nimport com.google.gson.Gson;\nimport io.openruntimes.java.models.RuntimeContext;\nimport io.openruntimes.java.models.RuntimeOutput;\n\npublic class Main {\n public RuntimeOutput main(RuntimeContext context) {\n Gson gson = new Gson();\n\n context.log(gson.toString(context.getReq().getBody())); // Object from parsed JSON request body, otherwise string\n context.log(gson.toString(context.getReq().getHeaders())); // String key-value pairs of all request headers, keys are lowercase\n context.log(context.getReq().getScheme()); // Value of the x-forwarded-proto header, usually http or https\n context.log(context.getReq().getMethod()); // Request method, such as GET, POST, PUT, DELETE, PATCH, etc.\n context.log(context.getReq().getUrl()); // Full URL, for example: http://awesome.appwrite.io:8000/v1/hooks?limit=12&offset=50\n context.log(context.getReq().getHost()); // Hostname from the host header, such as awesome.appwrite.io\n context.log(context.getReq().getPort()); // Port from the host header, for example 8000\n context.log(context.getReq().getPath()); // Path part of URL, for example /v1/hooks\n context.log(context.getReq().getQueryString()); // Raw query params string. For example \"limit=12&offset=50\"\n context.log(gson.toString(context.getReq().getQuery())); // Parsed query params. For example, req.query.limit\n\n return context.getRes().text(\"All the request parameters are logged to the Appwrite Console.\");\n }\n}\n```\n```rust\nuse openruntimes::{Context, Response};\nuse serde_json::{json, Value};\n\npub fn main(mut context: Context) -> Response {\n let body_json: Value = context.req.body_json().unwrap_or(Value::Null);\n\n context.log(context.req.body_text()); // Raw request body, contains request data\n context.log(body_json.to_string()); // Object from parsed JSON request body, otherwise string\n context.log(json!(&context.req.headers).to_string()); // String key-value pairs of all request headers, keys are lowercase\n context.log(context.req.scheme.as_str()); // Value of the x-forwarded-proto header, usually http or https\n context.log(context.req.method.as_str()); // Request method, such as GET, POST, PUT, DELETE, PATCH, etc.\n context.log(context.req.url.as_str()); // Full URL, for example: http://awesome.appwrite.io:8000/v1/hooks?limit=12&offset=50\n context.log(context.req.host.as_str()); // Hostname from the host header, such as awesome.appwrite.io\n context.log(context.req.port as i32); // Port from the host header, for example 8000\n context.log(context.req.path.as_str()); // Path part of URL, for example /v1/hooks\n context.log(context.req.query_string.as_str()); // Raw query params string. For example \"limit=12&offset=50\"\n context.log(json!(&context.req.query).to_string()); // Parsed query params. For example, req.query.limit\n\n context.res.text(\n \"All the request parameters are logged to the Appwrite Console.\",\n None,\n None,\n )\n}\n```\n{% /multicode %}\n\n### Headers {% #headers %}\n\nAppwrite Functions will always receive a set of headers that provide meta data about the function execution.\nThese are provided alongside any custom headers sent to the function.\n\n| Variable | Description |\n|---------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------|\n| `x-appwrite-trigger` | Describes how the function execution was invoked. Possible values are `http`, `schedule` or `event`. |\n| `x-appwrite-event` | If the function execution was triggered by an event, describes the triggering event. |\n| `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). |\n| `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. |\n| `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). |\n| `x-appwrite-country-code` | Displays the country code of the configured locale. |\n| `x-appwrite-continent-code` | Displays the continent code of the configured locale. |\n| `x-appwrite-continent-eu` | Describes if the configured local is within the EU. |\n| `x-appwrite-client-ip` | Displays the IP of the client creating the execution. |\n| `x-appwrite-execution-id` | Displays the ID of the current execution. |\n\n## Response {% #response %}\nUse the response object to send a response to the function caller. This could be a user, client app, or an integration.\nThe response information **will not be logged** to the Appwrite Console.\nThere are several possible ways to send a response, explore them in the following Appwrite Function.\n\n### Response types {% #response-types %}\n\n{% table %}\n* Response\n* Description\n---\n* `empty`\n* Sends a response with a `code 204 No Content` status.\n---\n* `json`\n* Converts the data into a JSON string and sets the content-type header to `application/json`.\n---\n* `binary`\n* Packages binary bytes, the status code, and the headers into an object.\n---\n* `redirect`\n* Redirects the client to the specified URL link.\n---\n* `text`\n* Converts the body using UTF-8 encoding into a binary Buffer.\n{% /table %}\n\n{% multicode %}\n```server-nodejs\nconst fs = require('fs');\n\nexport default async ({ req, res, log }) => {\n\n switch (req.query.type) {\n case 'empty':\n return res.empty();\n case 'json':\n return res.json({\"type\": \"This is a JSON response\"});\n case 'binary':\n const bytes = await fs.readFile('file.png');\n return res.binary(bytes);\n case 'redirect':\n return res.redirect(\"https://appwrite.io\", 301);\n case 'html':\n return res.text(\n \"<h1>This is an HTML response</h1>\", 200, {\n \"content-type\": \"text/html\"\n });\n default:\n return res.text(\"This is a text response\");\n }\n}\n```\n```php\n<?php\n\nreturn function ($context) {\n switch ($context->req->query['type']) {\n case 'empty':\n return $context->res->empty();\n case 'json':\n return $context->res->json([\"type\" => \"This is a JSON response\"]);\n case 'binary':\n $fileContent = file_get_contents('file.png');\n return $context->res->binary($fileContent);\n case 'redirect':\n return $context->res->redirect(\"https://appwrite.io\", 301);\n case 'html':\n return $context->res->text(\"<h1>This is an HTML response</h1>\", 200, [\n \"content-type\" => \"text/html\"\n ]);\n default:\n return $context->res->text(\"This is a text response\");\n }\n};\n```\n```python\n\ndef main(context):\n type = context.req.query['type']\n\n if type == 'empty':\n return context.res.empty()\n elif type == 'json':\n return context.res.json({\"type\": \"This is a JSON response\"})\n elif type == 'binary':\n with open('file.png', 'rb') as file:\n file_contents = file.read()\n return context.res.binary(file_contents)\n elif type == 'redirect':\n return context.res.redirect(\"https://appwrite.io\", 301)\n elif type == 'html':\n return context.res.text(\"<h1>This is an HTML response</h1>\", 200, {\n \"content-type\": \"text/html\"\n })\n else:\n return context.res.text(\"This is a text response\")\n```\n```ruby\ndef main(context)\n case context.req.query['type']\n when 'empty'\n return context.res.empty()\n when 'json'\n return context.res.json({\"type\": \"This is a JSON response\"})\n when 'binary'\n file_contents = File.binread('file.png')\n return context.res.binary(file_contents)\n when 'redirect'\n return context.res.redirect(\"https://appwrite.io\", 301)\n when 'html'\n return context.res.text(\"<h1>This is an HTML response</h1>\", 200, {\n \"content-type\": \"text/html\"\n })\n else\n return context.res.text(\"This is a text response\")\n end\nend\n```\n```deno\nexport default async ({ req, res, log }) => {\n\n switch (req.query.type) {\n case 'empty':\n return res.empty();\n case 'json':\n return res.json({type: \"This is a JSON response\"});\n case 'binary':\n const fileContents = await Deno.readFile('file.png');\n return res.binary(fileContents);\n case 'redirect':\n return res.redirect(\"https://appwrite.io\", 301);\n case 'html':\n return res.text(\n \"<h1>This is an HTML response</h1>\", 200, {\n \"content-type\": \"text/html\"\n });\n default:\n return res.text(\"This is a text response\");\n }\n}\n```\n```go\npackage handler\n\nimport (\n\t\"io\"\n\t\"os\"\n\t\"embed\"\n\t\"github.com/open-runtimes/types-for-go/v4/openruntimes\"\n)\n\n//go:embed images/*.png\nvar images embed.FS\n\nfunc Main(Context openruntimes.Context) openruntimes.Response {\n\tswitch Context.Req.Query[\"type\"] {\n\tcase \"empty\":\n\t\treturn Context.Res.Empty()\n\tcase \"json\":\n\t\treturn Context.Res.Json(map[string]string{\"type\": \"This is a JSON response\"})\n\tcase \"binary\":\n\t\timageData, _ := images.ReadFile(\"file.png\")\n\t\treturn Context.Res.Binary(imageData)\n\tcase \"redirect\":\n\t\treturn Context.Res.Redirect(\"https://appwrite.io\")\n\tcase \"html\":\n\t\treturn Context.Res.Text(\"<h1>This is an HTML response</h1>\")\n\tdefault:\n\t\treturn Context.Res.Text(\"This is a text response\")\n\t}\n}\n```\n```dart\nimport 'dart:io';\nimport 'dart:async';\n\nFuture<dynamic> main(final context) async {\n switch (context.req.query['type']) {\n case 'empty':\n return context.res.empty();\n case 'json':\n return context.res.json({'type': 'This is a JSON response'});\n case 'binary':\n final file = File('file.png');\n final fileContents = await file.readAsBytes();\n return context.res.binary(fileContents);\n case 'redirect':\n return context.res.redirect('https://appwrite.io', 301);\n case 'html':\n return context.res.text('<h1>This is an HTML response</h1>',\n 200, {'content-type': 'text/html'});\n default:\n return context.res.text('This is a text response');\n }\n}\n```\n```swift\nimport Foundation\n\nfunc main(context: RuntimeContext) async throws -> RuntimeOutput {\n switch context.req.query[\"type\"] {\n case \"empty\":\n return context.res.empty()\n case \"json\":\n return context.res.text([\"type\": \"This is a JSON response\"])\n case \"binary\":\n let fileContents = FileManager.default.contents(atPath: \"file.png\")\n return context.res.binary(fileContents)\n case \"redirect\":\n return context.res.redirect(\"https://appwrite.io\", 301)\n case \"html\":\n return context.res.text(\"<h1>This is an HTML response</h1>\", 200, [\n \"content-type\": \"text/html\"\n ])\n default:\n return context.res.text(\"This is a text response\")\n }\n}\n```\n```csharp\npublic class Handler {\n public async Task<RuntimeOutput> Main(RuntimeContext Context)\n {\n switch (Context.Request.Query[\"type\"])\n {\n case \"empty\":\n return Context.Res.Empty();\n case \"json\":\n return Context.Res.Text(new Dictionary<string, object>() { { \"type\", \"This is a JSON response\" } });\n case \"binary\":\n return Context.Res.Binary(File.ReadAllBytes(\"file.png\"));\n case \"redirect\":\n return Context.Res.Redirect(\"https://appwrite.io\", 301);\n case \"html\":\n return Context.Res.Text(\"<h1>This is an HTML response</h1>\", 200, new Dictionary<string, string>() {\n { \"content-type\", \"text/html\" }\n });\n default:\n return Context.Res.Text(\"This is a text response\");\n }\n }\n}\n```\n```kotlin\npackage io.openruntimes.kotlin.src\n\nimport io.openruntimes.kotlin.RuntimeContext\nimport io.openruntimes.kotlin.RuntimeOutput\n\nclass Main {\n fun main(context: RuntimeContext): RuntimeOutput {\n when (context.req.query[\"type\"]) {\n \"empty\" -> return context.res.empty()\n \"json\" -> return context.res.text(mapOf(\"type\" to \"This is a JSON response\"))\n \"binary\" -> return context.res.binary(File(\"file.png\").readBytes())\n \"redirect\" -> return context.res.redirect(\"https://appwrite.io\", 301)\n \"html\" -> return context.res.text(\"<h1>This is an HTML response</h1>\", 200, mapOf(\"content-type\" to \"text/html\"))\n else -> return context.res.text(\"This is a text response\")\n }\n }\n}\n```\n```java\npackage io.openruntimes.java.src;\n\nimport io.openruntimes.java.RuntimeContext;\nimport io.openruntimes.java.RuntimeOutput;\nimport java.util.Map;\nimport java.util.HashMap;\n\npublic class Main {\n public RuntimeOutput main(RuntimeContext context) throws Exception {\n switch (context.getReq().getQuery()[\"type\"]) {\n case \"text\":\n return context.getRes().empty();\n case \"json\":\n HashMap<String, Object> data = new HashMap<>();\n data.put(\"type\", \"This is a JSON response\");\n return context.getRes().text(data);\n case \"binary\"\n return context.getRes().binary(Files.readAllBytes(Paths.get(\"file.png\")));\n case \"redirect\":\n return context.getRes().redirect(\"https://appwrite.io\", 301);\n case \"html\":\n return context.getRes().text(\"<h1>This is an HTML response</h1>\", 200, Map.of(\"content-type\", \"text/html\"));\n default:\n return context.getRes().text(\"This is a text response\");\n }\n }\n}\n```\n```cpp\n#include \"../RuntimeResponse.h\"\n#include \"../RuntimeRequest.h\"\n#include \"../RuntimeOutput.h\"\n#include \"../RuntimeContext.h\"\n\nnamespace runtime {\n class Handler {\n public:\n static RuntimeOutput main(RuntimeContext &context) {\n std::string type = context.req.query[\"type\"];\n\n if (type == \"empty\") {\n return context.res.empty();\n } else if (type == \"json\") {\n Json::Value data;\n data[\"type\"] = \"This is a JSON response\";\n return context.res.text(data);\n } else if (type == \"binary\") {\n std::vector<char> buffer(std::istreambuf_iterator<char>(std::ifstream(\"file.png\", std::ios::binary)), {});\n return context.res.binary(buffer)\n } else if (type == \"redirect\") {\n return context.res.redirect(\"https://appwrite.io\", 301);\n } else if (type == \"html\") {\n Json::Value headers;\n headers[\"content-type\"] = \"text/html\";\n return context.res.text(\"<h1>This is an HTML response</h1>\", 200, headers);\n } else {\n return context.res.text(\"This is a text response\");\n }\n }\n };\n}\n```\n```rust\nuse openruntimes::{Context, Response};\nuse serde_json::json;\nuse std::collections::HashMap;\n\npub fn main(context: Context) -> Response {\n match context.req.query.get(\"type\").map(|s| s.as_str()) {\n Some(\"empty\") => context.res.empty(),\n Some(\"json\") => context.res.json(\n json!({ \"type\": \"This is a JSON response\" }),\n None,\n None,\n ),\n Some(\"redirect\") => {\n let mut headers = HashMap::new();\n headers.insert(\"location\".to_string(), \"https://appwrite.io\".to_string());\n context.res.text(\"\", Some(301), Some(headers))\n }\n Some(\"html\") => {\n let mut headers = HashMap::new();\n headers.insert(\"content-type\".to_string(), \"text/html\".to_string());\n context\n .res\n .text(\"<h1>This is an HTML response</h1>\", Some(200), Some(headers))\n }\n _ => context.res.text(\"This is a text response\", None, None),\n }\n}\n```\n{% /multicode %}\n\nTo get the different response types, set one of the following query parameters in the [generated domain](/docs/products/functions/domains) of your function.\n\n| Type | Query Param | Example |\n|----------|-----------------|-------------------------------------------------------------|\n| `text` | `/?type=text` | `https://64d4d22db370ae41a32e.fra.appwrite.run/?type=text` |\n| `json` | `/?type=json` | `https://64d4d22db370ae41a32e.fra.appwrite.run/?type=json` |\n| `redirect` | `/?type=redirect` | `https://64d4d22db370ae41a32e.fra.appwrite.run/?type=redirect` |\n| `html` | `/?type=html` | `https://64d4d22db370ae41a32e.fra.appwrite.run/?type=html` |\n| `empty` | `/` | `https://64d4d22db370ae41a32e.fra.appwrite.run/` |\n\n## Logging {% #logging %}\nTo protect user privacy, the request and response objects are not logged to the Appwrite Console by default.\n\nWe support the spread operator across most of the languages, meaning you can write code that is more concise and flexible.\n\nThis means, to see logs or debug function executions you need to use the `log()` and `error()` methods.\nThese logs are only visible to developers with access to the Appwrite Console.\n\nHere's an example of using logs and errors.\n\n{% multicode %}\n```server-nodejs\nexport default async ({ req, res, log, error }) => {\n const message = \"This is a log, use for logging information to console\";\n log(\"Message: \", message);\n log(`This function was called with ${req.method} method`);\n const errorMessage = \"This is an error, use for logging errors to console\"\n error(\"Error: \", errorMessage);\n\n return res.text(\"Check the Appwrite Console to see logs and errors!\");\n};\n```\n```php\n<?php\n\nreturn function ($context) {\n $message = \"This is a log, use for logging information to console\";\n $context->log(\"Message: \", message);\n $context->log(\"This function was called with \" . $context->req->method . \" method\");\n $errorMessage = \"Check the Appwrite Console to see logs and errors!\"\n $context->error(\"Error: \", errorMessage);\n\n return $context->text(\"Check the Appwrite Console to see logs and errors!\");\n};\n```\n```python\ndef main(context):\n message = \"This is a log, use for logging information to console\"\n context.log(\"Message: \", message)\n context.log(f\"This function was called with {context.req.method} method\")\n errorMessage = \"This is an error, use for logging errors to console\"\n context.error(\"Error: \", errorMessage)\n\n return context.res.text(\"Check the Appwrite Console to see logs and errors!\")\n```\n```ruby\ndef main(context)\n message = \"This is a log, use for logging information to console\"\n context.log(\"Message: \", message)\n context.log(\"This function was called with #{context.req.method} method\")\n errorMessage = \"This is an error, use for logging errors to console\"\n context.error(\"Error: \", errorMessage)\n\n return context.res.text(\"Check the Appwrite Console to see logs and errors!\")\nend\n```\n```deno\nexport default async ({ res, log, error }: any) => {\n let message = \"This is a log, use for logging information to console\";\n log(\"Message: \", message);\n log(`This function was called with ${context.req.method} method`);\n let errorMessage = \"This is an error, use for logging errors to console\";\n error(\"Error: \", errorMessage);\n\n return res.text(\"Check the Appwrite Console to see logs and errors!\");\n};\n```\n```go\npackage handler\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/open-runtimes/types-for-go/v4/openruntimes\"\n)\n\nfunc Main(Context openruntimes.Context) openruntimes.Response {\n\tmessage := \"This is a log, use for logging information to console\"\n\tContext.Log(\"Message: \", message)\n\tContext.Log(fmt.Sprintf(\"This function was called with %s method\", Context.Req.Method))\n\terrorMessage := \"This is an error, use for logging errors to console\"\n\tContext.Error(\"Error: \", errorMessage)\n\n\treturn Context.Res.Text(\"Check the Appwrite Console to see logs and errors!\")\n}\n```\n```dart\nimport 'dart:async';\n\nFuture<dynamic> main(final context) async {\n var message = \"This is a log, use for logging information to console\";\n context.log(\"message: \", var);\n context.log(\"This function was called with ${context.req.method} method\");\n var errorMessage = \"This is an error, use for logging errors to console\";\n context.error(\"Error: \", errorMessage);\n\n return context.res.text(\"Check the Appwrite Console to see logs and errors!\");\n}\n```\n```swift\nimport Foundation\n\nfunc main(context: RuntimeContext) async throws -> RuntimeOutput {\n var message: String = \"This is a log, use for logging information to console\"\n context.log(\"Message: \", message)\n context.log(\"This function was called with \\(context.req.method) method\")\n var message: String = \"This is an error, use for logging errors to console\"\n context.error(\"Error: \", message)\n\n return context.res.text(\"Check the Appwrite Console to see logs and errors!\")\n}\n```\n```csharp\nnamespace DotNetRuntime;\n\npublic class Handler {\n public async Task<RuntimeOutput> Main(RuntimeContext Context)\n {\n string message = \"This is a log, use for logging information to console\";\n Context.Log(\"Message: \", message);\n Context.Log($\"This function was called with {Context.Req.Method} method\");\n string errorMessage = \"This is an error, use for logging errors to console\";\n Context.Error(\"Error: \", errorMessage);\n\n return Context.Res.Text(\"Check the Appwrite Console to see logs and errors!\");\n }\n}\n```\n```kotlin\npackage io.openruntimes.kotlin.src\n\nimport io.openruntimes.kotlin.RuntimeContext\nimport io.openruntimes.kotlin.RuntimeOutput\n\nclass Main {\n fun main(context: RuntimeContext): RuntimeOutput {\n var message: String = \"This is a log, use for logging information to console\"\n context.log(\"Message: \", message)\n context.log(\"This function was called with ${context.req.method} method\")\n var errorMessage: String = \"This is an error, use for logging errors to console\"\n context.error(\"Error: \", errorMessage)\n\n return context.res.text(\"Check the Appwrite Console to see logs and errors!\")\n }\n}\n```\n```java\npackage io.openruntimes.java.src;\n\nimport io.openruntimes.java.RuntimeContext;\nimport io.openruntimes.java.RuntimeOutput;\n\npublic class Main {\n public RuntimeOutput main(RuntimeContext context) throws Exception {\n String message = \"This is a log, use for logging information to console\";\n context.log(\"Message: \", message);\n context.log(\"This function was called with \" + context.req.method + \" method\");\n string errorMessage = \"This is an error, use for logging errors to console\";\n context.error(\"Error: \", errorMessage);\n\n return context.getRes().text(\"Check the Appwrite Console to see logs and errors!\");\n }\n}\n```\n```cpp\n#include \"../RuntimeResponse.h\"\n#include \"../RuntimeRequest.h\"\n#include \"../RuntimeOutput.h\"\n#include \"../RuntimeContext.h\"\n\nnamespace runtime {\n class Handler {\n public:\n static RuntimeOutput main(RuntimeContext &context) {\n const std::string message = \"This is a log, use for logging information to console\";\n context.log(\"Message: \", message);\n context.log(\"This function was called with \" + context.req.method + \" method\");\n const std::string errorMessage = \"This is an error, use for logging errors to console\";\n context.error(\"Error: \", errorMessage);\n\n return context.res.text(\"Check the Appwrite Console to see logs and errors!\");\n }\n };\n}\n```\n```rust\nuse openruntimes::{Context, Response};\n\npub fn main(context: Context) -> Response {\n let message = \"This is a log, use for logging information to console\";\n context.log(format!(\"Message: {}\", message));\n context.log(format!(\n \"This function was called with {} method\",\n context.req.method\n ));\n\n let error_message = \"This is an error, use for logging errors to console\";\n context.error(format!(\"Error: {}\", error_message));\n\n context\n .res\n .text(\"Check the Appwrite Console to see logs and errors!\", None, None)\n}\n```\n{% /multicode %}\n\nYou can access these logs through the following steps.\n\n1. In Appwrite Console, navigate to Functions.\n2. Click to open a function you wish to inspect.\n3. Under the Executions tab, click on an execution.\n4. In the Response section, you'll be able to view logs under the Logs and Errors tabs.\n\n# Environment variables {% #environment-variables %}\n\nPass constants and secrets into your function with environment variables, and read them inside your function using your runtime's standard environment lookup.\n\n{% arrow_link href=\"/docs/products/functions/environment-variables\" %}\nManage and read environment variables\n{% /arrow_link %}\n\n# Dependencies {% #dependencies %}\nTo install your dependencies before your function is built,\nyou should add the relevant install command to the top your function's **Build setting** > **Commands**.\nYou can find this setting under **Functions** > your function > **Settings** > **Configuration** > **Build settings**.\n\nMake 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).\nDo not include the dependency folders like `node_modules`, `vendor`, etc. in your function's root directory.\nThe dependencies installed for your local OS may not work in the executor environments\n\nYour function's dependencies should be managed by the package manager of each language.\nBy default, we include the following package managers in each runtime.\n\n{% table %}\n*   {% width=80 %}\n* Language\n* Package Manager\n* Commands\n---\n* {% only_dark %}{% icon_image src=\"/images/platforms/dark/node.svg\" alt=\"Node.js logo\" size=\"m\" /%}{% /only_dark %}\n{% only_light %}{% icon_image src=\"/images/platforms/light/node.svg\" alt=\"Node.js logo\" size=\"m\" /%}{% /only_light %}\n* Node.js\n* NPM\n* `npm install`\n---\n* {% only_dark %}{% icon_image src=\"/images/platforms/dark/php.svg\" alt=\"PHP logo\" size=\"m\" /%}{% /only_dark %}\n{% only_light %}{% icon_image src=\"/images/platforms/light/php.svg\" alt=\"PHP logo\" size=\"m\" /%}{% /only_light %}\n* PHP\n* Composer\n* `composer install`\n---\n* {% only_dark %}{% icon_image src=\"/images/platforms/dark/python.svg\" alt=\"Python logo\" size=\"m\" /%}{% /only_dark %}\n{% only_light %}{% icon_image src=\"/images/platforms/light/python.svg\" alt=\"Python logo\" size=\"m\" /%}{% /only_light %}\n* Python\n* pip\n* `pip install -r requirements.txt`\n---\n* {% only_dark %}{% icon_image src=\"/images/platforms/dark/ruby.svg\" alt=\"Ruby logo\" size=\"m\" /%}{% /only_dark %}\n{% only_light %}{% icon_image src=\"/images/platforms/light/ruby.svg\" alt=\"Ruby logo\" size=\"m\" /%}{% /only_light %}\n* Ruby\n* Bundler\n* `bundle install`\n---\n* {% only_dark %}{% icon_image src=\"/images/platforms/dark/deno.svg\" alt=\"Deno logo\" size=\"m\" /%}{% /only_dark %}\n{% only_light %}{% icon_image src=\"/images/platforms/light/deno.svg\" alt=\"Deno logo\" size=\"m\" /%}{% /only_light %}\n* Deno\n* deno\n* `deno cache <ENTRYPOINT_FILE>`\n---\n* {% only_dark %}{% icon_image src=\"/images/platforms/dark/go.svg\" alt=\"Go logo\" size=\"m\" /%}{% /only_dark %}\n{% only_light %}{% icon_image src=\"/images/platforms/light/go.svg\" alt=\"Go logo\" size=\"m\" /%}{% /only_light %}\n* Go\n* Go Modules\n* N/A\n---\n* {% only_dark %}{% icon_image src=\"/images/platforms/dark/dart.svg\" alt=\"Dart logo\" size=\"m\" /%}{% /only_dark %}\n{% only_light %}{% icon_image src=\"/images/platforms/light/dart.svg\" alt=\"Dart logo\" size=\"m\" /%}{% /only_light %}\n* Dart\n* pub\n* `pub get`\n---\n* {% only_dark %}{% icon_image src=\"/images/platforms/dark/swift.svg\" alt=\"Swift logo\" size=\"m\" /%}{% /only_dark %}\n{% only_light %}{% icon_image src=\"/images/platforms/light/swift.svg\" alt=\"Swift logo\" size=\"m\" /%}{% /only_light %}\n* Swift\n* Swift Package Manager\n* `swift package resolve`\n---\n* {% only_dark %}{% icon_image src=\"/images/platforms/dark/dotnet.svg\" alt=\".NET logo\" size=\"m\" /%}{% /only_dark %}\n{% only_light %}{% icon_image src=\"/images/platforms/light/dotnet.svg\" alt=\".NET logo\" size=\"m\" /%}{% /only_light %}\n* .NET\n* NuGet\n* `dotnet restore`\n---\n* {% only_dark %}{% icon_image src=\"/images/platforms/dark/bun.svg\" alt=\"Bun logo\" size=\"m\" /%}{% /only_dark %}\n{% only_light %}{% icon_image src=\"/images/platforms/light/bun.svg\" alt=\"Bun logo\" size=\"m\" /%}{% /only_light %}\n* Bun\n* bun\n* `bun install`\n---\n* {% only_dark %}{% icon_image src=\"/images/platforms/dark/kotlin.svg\" alt=\"Kotlin logo\" size=\"m\" /%}{% /only_dark %}\n{% only_light %}{% icon_image src=\"/images/platforms/light/kotlin.svg\" alt=\"Kotlin logo\" size=\"m\" /%}{% /only_light %}\n* Kotlin\n* Gradle\n* N/A\n---\n* {% only_dark %}{% icon_image src=\"/images/platforms/dark/java.svg\" alt=\"Java logo\" size=\"m\" /%}{% /only_dark %}\n{% only_light %}{% icon_image src=\"/images/platforms/light/java.svg\" alt=\"Java logo\" size=\"m\" /%}{% /only_light %}\n* Java\n* Gradle\n* N/A\n---\n* {% only_dark %}{% icon_image src=\"/images/platforms/dark/c.svg\" alt=\"C++ logo\" size=\"m\" /%}{% /only_dark %}\n{% only_light %}{% icon_image src=\"/images/platforms/light/c.svg\" alt=\"C++ logo\" size=\"m\" /%}{% /only_light %}\n* C++\n* None\n* N/A\n---\n* {% only_dark %}{% icon_image src=\"/images/platforms/dark/rust.svg\" alt=\"Rust logo\" size=\"m\" /%}{% /only_dark %}\n{% only_light %}{% icon_image src=\"/images/platforms/light/rust.svg\" alt=\"Rust logo\" size=\"m\" /%}{% /only_light %}\n* Rust\n* Cargo\n* `cargo build --release`\n{% /table %}\n\n# Using Appwrite in a function {% #using-appwrite %}\nAppwrite can be used in your functions by adding the relevant SDK to your function's dependencies.\nAuthenticating with Appwrite is done via a dynamic API key or a JWT token.\n\n## Dynamic API key {% #dynamic-api-key %}\nDynamic API keys are the same as [API keys](/docs/advanced/security/api-keys) but are automatically generated.\nThey are generated in your functions per execution.\nHowever, you can only use dynamic API keys inside Appwrite functions.\n\nDuring 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.\n\nDuring execution, dynamic API keys are automatically provided in the `x-appwrite-key` [header](#headers).\n\nDynamic API keys grant access and operate without sessions.\nThey allow your function to act as an admin-type role instead of acting on behalf of a user.\nUpdate the function settings to configure the scopes of the function.\n\n1. In Appwrite Console, navigate to **Functions**.\n2. Click to open a function you wish to configure.\n3. Under the **Settings** tab, navigate to **Scopes**.\n4. Select the scopes you want to grant the dynamic key.\n5. It is best practice to allow only necessary permissions.\n\n{% multicode %}\n```server-nodejs\nimport { Client, TablesDB, ID } from 'node-appwrite';\n\nexport default async ({ req, res, log, error }) => {\n // Set project and set API key\n const client = new Client()\n .setProject(process.env.APPWRITE_FUNCTION_PROJECT_ID)\n .setKey(req.headers['x-appwrite-key']);\n\n const tablesDB = new TablesDB(client);\n\n try {\n await tablesDB.createRow({\n databaseId: '<DATABASE_ID>',\n tableId: '<TABLE_ID>',\n rowId: ID.unique(),\n data: {}\n })\n } catch (e) {\n error(\"Failed to create row: \" + e.message)\n return res.text(\"Failed to create row\")\n }\n\n return res.text(\"Row created\")\n}\n```\n```php\n<?php\n\nrequire(__DIR__ . '/../vendor/autoload.php');\n\nuse Appwrite\\Client;\nuse Appwrite\\Exception;\nuse Appwrite\\Services\\TablesDB;\nuse Appwrite\\ID;\n\nreturn function ($context) {\n // Set project and set API key\n $client = (new Client())\n ->setProject(getenv('APPWRITE_FUNCTION_PROJECT_ID'))\n ->setKey($context->req->headers['x-appwrite-key']);\n\n $tablesDB = new TablesDB($client);\n\n try {\n $tablesDB->createRow(\n databaseId: '<DATABASE_ID>',\n tableId: '<TABLE_ID>',\n rowId: ID::unique(),\n data: []\n );\n } catch (Exception $e) {\n $context->error(\"Failed to create row: \" . $e->getMessage());\n return $context->res->text(\"Failed to create row\");\n }\n\n return $context->res->text(\"Row created\");\n};\n```\n```python\nfrom appwrite.client import Client\nfrom appwrite.services.tables_db import TablesDB\nfrom appwrite.id import ID\n\nimport os\n\ndef main(context):\n # Set project and set API key\n client = (\n Client()\n .set_project(os.environ[\"APPWRITE_FUNCTION_PROJECT_ID\"])\n .set_key(context.req.headers[\"x-appwrite-key\"])\n )\n\n tablesDB = TablesDB(client)\n\n try:\n tablesDB.create_row(\n database_id=\"<DATABASE_ID>\",\n table_id=\"<TABLE_ID>\",\n row_id=ID.unique(),\n data={}\n )\n except Exception as e:\n context.error(\"Failed to create row: \" + e.message)\n return context.response.text(\"Failed to create row\")\n\n return context.response.text(\"Row created\")\n```\n```ruby\nrequire \"appwrite\"\n\ninclude Appwrite\n\ndef main(context)\n # Set project and set API key\n client = Appwrite::Client.new\n .set_project(ENV['APPWRITE_FUNCTION_PROJECT_ID'])\n .set_key(context.req.headers['x-appwrite-key'])\n\n tablesDB = Appwrite::TablesDB.new(client)\n\n begin\n tablesDB.create_row(\n databaseId: '<DATABASE_ID>',\n tableId: '<TABLE_ID>',\n rowId: ID.unique(),\n data: {}\n )\n rescue Exception => e\n context.error(\"Failed to create row: \" + e.message)\n return context.response.text(\"Failed to create row\")\n end\n\n return context.response.text(\"Row created\")\nend\n```\n```deno\nimport { Client, TablesDB, ID } from \"npm:node-appwrite\";\n\nexport default function ({req, res, error}: any){\n // Set project and set API key\n const client = new Client()\n .setProject(Deno.env.get(\"APPWRITE_FUNCTION_PROJECT_ID\"))\n .setKey(req.headers[\"x-appwrite-key\"] || \"\");\n\n const tablesDB = new TablesDB(client);\n\n try {\n tablesDB.createRow(\n \"<DATABASE_ID>\",\n \"<TABLE_ID>\",\n ID.unique(),\n {}\n );\n } catch (e) {\n error(\"Failed to create row: \" + e.message);\n return res.text(\"Failed to create row\");\n }\n\n return res.text(\"Row created\");\n}\n```\n```go\npackage handler\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/appwrite/sdk-for-go/appwrite\"\n\t\"github.com/appwrite/sdk-for-go/id\"\n\t\"github.com/open-runtimes/types-for-go/v4/openruntimes\"\n)\n\nfunc Main(Context openruntimes.Context) openruntimes.Response {\n\t// Set project and set API key\n\tclient := appwrite.NewClient(\n\t\tappwrite.WithProject(os.Getenv(\"APPWRITE_FUNCTION_PROJECT_ID\")),\n\t\tappwrite.WithKey(Context.Req.Headers[\"x-appwrite-key\"]),\n\t)\n\n\tdatabases := appwrite.NewTablesDB(client)\n\n\t_, err := databases.createRow(\n\t\t\"<DATABASE_ID>\",\n\t\t\"<TABLE_ID>\",\n\t\tid.Unique(),\n\t\tmap[string]interface{}{},\n\t)\n\n\tif err != nil {\n\t\tContext.Log(fmt.Sprintf(\"Failed to create row: %v\", err))\n\t\treturn Context.Res.Text(\"Failed to create row\")\n\t}\n\n\treturn Context.Res.Text(\"Row created\")\n}\n```\n```dart\nimport 'dart:io';\nimport 'dart:async';\nimport 'package:dart_appwrite/dart_appwrite.dart';\n\nFuture<dynamic> main(final context) async {\n // Set project and set API key\n final client = Client()\n .setProject(Platform.environment['APPWRITE_FUNCTION_PROJECT_ID'])\n .setKey(context.req.headers['x-appwrite-key']);\n\n final tablesDB = TablesDB(client);\n\n try {\n await tablesDB.createRow(\n databaseId: '<DATABASE_ID>',\n tableId: '<TABLE_ID>',\n rowId: ID.unique(),\n data: {}\n );\n } catch (e) {\n context.error(\"Failed to create row: \" + e.message);\n return context.res.text(\"Failed to create row\");\n }\n\n return context.res.text(\"Row created\");\n}\n```\n```swift\nimport Appwrite\nimport AppwriteModels\nimport Foundation\n\nfunc main(context: RuntimeContext) async throws -> RuntimeOutput {\n // Set project and set API key\n let client = Client()\n .setProject(ProcessInfo.processInfo.environment[\"APPWRITE_FUNCTION_PROJECT_ID\"])\n .setKey(context.req.headers[\"x-appwrite-key\"] ?? \"\")\n\n let tablesDB = TablesDB(client: client)\n\n do {\n try await tablesDB.createRow(\n databaseId: \"<DATABASE_ID>\",\n tableId: \"<TABLE_ID>\",\n rowId: ID.unique(),\n data: [:]\n )\n } catch {\n context.error(\"Failed to create row: \\(error.localizedDescription)\")\n return context.res.text(\"Failed to create row\")\n }\n\n return context.res.text(\"Row created\")\n}\n```\n```csharp\nusing Appwrite;\nusing Appwrite.Services;\nusing Appwrite.Models;\n\nnamespace DotNetRuntime\n{\n public class Handler\n {\n public async Task Main(RuntimeContext Context)\n {\n // Set API\n var client = new Client()\n .SetProject(Environment.GetEnvironmentVariable(\"APPWRITE_FUNCTION_PROJECT_ID\"))\n .SetKey(Context.Req.Headers[\"x-appwrite-key\"]);\n\n var tablesDB = new TablesDB(client);\n\n try {\n await databases.createRow(\n databaseId: \"<DATABASE_ID>\",\n tableId: \"<TABLE_ID>\",\n rowId: ID.Unique(),\n data: new Dictionary<string, object>());\n } catch (Exception e) {\n Context.Error(\"Failed to create row: \" + e.Message);\n return Context.Response.Text(\"Failed to create row\");\n }\n\n return Context.Response.Text(\"Row created\");\n }\n }\n}\n```\n```kotlin\npackage io.openruntimes.kotlin.src\n\nimport io.openruntimes.kotlin.RuntimeContext\nimport io.openruntimes.kotlin.RuntimeOutput\nimport io.appwrite.Client\nimport io.appwrite.services.TablesDB\nimport io.appwrite.ID\nimport java.util.HashMap\n\nclass Main {\n fun main(context: RuntimeContext): RuntimeOutput {\n // Set project and set API key\n val client = Client()\n .setProject(System.getenv(\"APPWRITE_FUNCTION_PROJECT_ID\"))\n .setKey(context.req.headers[\"x-appwrite-key\"])\n\n val tablesDB = TablesDB(client)\n\n try {\n tablesDB.createRow(\n databaseId = \"<DATABASE_ID>\",\n tableId = \"<TABLE_ID>\",\n rowId = ID.unique()\n data = mapOf()\n )\n } catch (e: Exception) {\n context.error(\"Failed to create row: \" + e.message)\n return context.res.text(\"Failed to create row\")\n }\n\n return context.res.text(\"Row created\")\n }\n}\n```\n```java\npackage io.openruntimes.java.src;\n\nimport io.openruntimes.java.RuntimeContext;\nimport io.openruntimes.java.RuntimeOutput;\nimport java.util.HashMap;\nimport io.appwrite.Client;\n\npublic class Main {\n public RuntimeOutput main(RuntimeContext context) throws Exception {\n // Set project and set API key\n Client client = new Client();\n .setProject(System.getenv(\"APPWRITE_FUNCTION_PROJECT_ID\"))\n .setKey(context.getReq().getHeaders().get(\"x-appwrite-key\"));\n\n Databases tablesDB = new TablesDB(client);\n\n try {\n tablesDB.createRow(\n \"<DATABASE_ID>\",\n \"<TABLE_ID>\",\n ID.unique(),\n new HashMap<>()\n );\n } catch (Exception e) {\n context.error(\"Failed to create row: \" + e.getMessage());\n return context.res.text(\"Failed to create row\");\n }\n\n return context.res.text(\"Row created\");\n }\n}\n```\n```rust\nuse appwrite::id::ID;\nuse appwrite::services::tables_db::TablesDB;\nuse appwrite::Client;\nuse openruntimes::{Context, Response};\nuse serde_json::json;\nuse std::env;\n\npub fn main(context: Context) -> Response {\n // Set project and set API key\n let client = Client::new()\n .set_endpoint(env::var(\"APPWRITE_FUNCTION_API_ENDPOINT\").unwrap_or_default())\n .set_project(env::var(\"APPWRITE_FUNCTION_PROJECT_ID\").unwrap_or_default())\n .set_key(\n context\n .req\n .headers\n .get(\"x-appwrite-key\")\n .cloned()\n .unwrap_or_default(),\n );\n\n let tables_db = TablesDB::new(&client);\n\n let runtime = tokio::runtime::Builder::new_current_thread()\n .enable_all()\n .build()\n .unwrap();\n\n let result = runtime.block_on(async move {\n tables_db\n .create_row(\n \"<DATABASE_ID>\",\n \"<TABLE_ID>\",\n ID::unique(),\n json!({}),\n None,\n None,\n )\n .await\n });\n\n match result {\n Ok(_) => context.res.text(\"Row created\", None, None),\n Err(e) => {\n context.error(format!(\"Failed to create row: {}\", e));\n context.res.text(\"Failed to create row\", None, None)\n }\n }\n}\n```\n{% /multicode %}\n\n## Using with JWT {% #using-jwt %}\nJWTs allow you to act on behalf of an user in your Appwrite Function.\nWhen 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.\nThis preserves the permissions you configured on each resource.\n\nIf the Appwrite Function is invoked by an authenticated user, the `x-appwrite-user-jwt` header is automatically passed in.\n\n{% multicode %}\n\n```server-nodejs\nimport { Client, TablesDB, ID } from 'node-appwrite';\n\nexport default async ({ req, res, log }) => {\n const client = new Client()\n .setProject(process.env.APPWRITE_FUNCTION_PROJECT_ID)\n\n if (req.headers['x-appwrite-user-jwt']) {\n client.setJWT(req.headers['x-appwrite-user-jwt'])\n } else {\n return res.text(\"Access denied: This function requires authentication. Please sign in to continue.\");\n }\n\n const tablesDB = new TablesDB(client);\n\n try {\n await tablesDB.createRow({\n databaseId: '<DATABASE_ID>',\n tableId: '<TABLE_ID>',\n rowId: ID.unique(),\n data: {}\n })\n } catch (e) {\n log(\"Failed to create row: \" + e.message)\n return res.text(\"Failed to create row\")\n }\n\n return res.text(\"Row created\")\n}\n```\n```php\n<?php\n\nrequire(__DIR__ . '/../vendor/autoload.php');\n\nuse Appwrite\\Client;\nuse Appwrite\\Exception;\nuse Appwrite\\Services\\TablesDB;\nuse Appwrite\\ID;\n\nreturn function ($context) {\n $client = new (Client())\n ->setProject(getenv('APPWRITE_FUNCTION_PROJECT_ID'))\n\n if (isset($context->req->headers['x-appwrite-user-jwt'])) {\n $client->setJWT($context->req->headers['x-appwrite-user-jwt']);\n } else {\n return $context->res->text(\"Access denied: This function requires authentication. Please sign in to continue.\");\n }\n\n $tablesDB = new TablesDB($client);\n\n try {\n $tablesDB->createRow(\n databaseId: '<DATABASE_ID>',\n tableId: '<TABLE_ID>',\n rowId: ID::unique(),\n data: []\n );\n } catch (Exception $e) {\n $context->error(\"Failed to create row: \" . $e->getMessage());\n return $context->res->text(\"Failed to create row\");\n }\n\n return $context->res->text(\"Row created\");\n};\n```\n```python\nfrom appwrite.client import Client\nfrom appwrite.services.tables_db import TablesDB\nfrom appwrite.id import ID\n\nimport os\n\ndef main(context):\n client = (\n Client()\n .set_project(os.environ[\"APPWRITE_FUNCTION_PROJECT_ID\"])\n )\n\n if \"x-appwrite-user-jwt\" in context.req.headers:\n client.set_jwt(context.req.headers[\"x-appwrite-user-jwt\"])\n else:\n return context.res.text(\"Access denied: This function requires authentication. Please sign in to continue.\")\n\n tablesDB = TablesDB(client)\n\n try:\n tablesDB.create_row(\n database_id=\"<DATABASE_ID>\",\n table_id=\"<TABLE_ID>\",\n row_id=ID.unique(),\n data={}\n )\n except Exception as e:\n context.error(\"Failed to create row: \" + e.message)\n return context.response.text(\"Failed to create row\")\n\n return context.response.text(\"Row created\")\n```\n```ruby\nrequire \"appwrite\"\n\ninclude Appwrite\n\ndef main(context)\n client = Client.new\n .set_project(ENV['APPWRITE_FUNCTION_PROJECT_ID'])\n\n if context.request.headers['x-appwrite-user-jwt']\n client.set_jwt(context.request.headers['x-appwrite-user-jwt'])\n else\n return context.response.text(\"Access denied: This function requires authentication. Please sign in to continue.\")\n end\n\n tablesDB = Appwrite::TablesDB.new(client)\n\n begin\n tablesDB.create_row('<DATABASE_ID>', '<TABLE_ID>', Appwrite::ID.unique(), {})\n rescue Appwrite::Exception => e\n context.error(\"Failed to create row: \" + e.message)\n return context.response.text(\"Failed to create row\")\n end\n\n return context.response.text(\"Row created\")\nend\n```\n```deno\nimport { Client, TablesDB, ID } from \"npm:node-appwrite\";\n\nexport default function ({req, res, error}: any){\n const client = new Client()\n .setProject(Deno.env.get(\"APPWRITE_FUNCTION_PROJECT_ID\") || \"\")\n\n if (req.headers[\"x-appwrite-user-jwt\"]) {\n client.setJWT(req.headers[\"x-appwrite-user-jwt\"]);\n } else {\n return res.text(\"Access denied: This function requires authentication. Please sign in to continue.\");\n }\n\n const tablesDB = new TablesDB(client);\n\n try {\n tablesDB.createRow(\n \"<DATABASE_ID>\",\n \"<TABLE_ID>\",\n ID.unique(),\n {}\n );\n } catch (e) {\n error(\"Failed to create row: \" + e.message)\n return res.text(\"Failed to create row\");\n }\n\n return res.text(\"Row created\");\n}\n```\n```go\npackage handler\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\n\t\"github.com/appwrite/sdk-for-go/appwrite\"\n\t\"github.com/appwrite/sdk-for-go/id\"\n\t\"github.com/open-runtimes/types-for-go/v4/openruntimes\"\n)\n\nfunc Main(Context openruntimes.Context) openruntimes.Response {\n\tclient := appwrite.NewClient(\n\t\tappwrite.WithProject(\"APPWRITE_FUNCTION_PROJECT_ID\"),\n\t)\n\n\tjwt, exists := Context.Req.Headers[\"x-appwrite-user-jwt\"]\n\tif !exists || len(jwt) == 0 {\n\t\tappwrite.WithJWT(Context.Req.Headers[\"x-appwrite-user-jwt\"])\n\t} else {\n\t\treturn Context.Res.Text(\"Access denied: This function requires authentication. Please sign in to continue.\")\n\t}\n\n\tdatabases := appwrite.NewTablesDB(client)\n\n\t_, err := databases.createRow(\n\t\t\"<DATABASE_ID>\",\n\t\t\"<TABLE_ID>\",\n\t\tid.Unique(),\n\t\tmap[string]interface{}{},\n\t)\n\n\tif err != nil {\n Context.Log(fmt.Sprintf(\"Failed to create row: %v\", err))\n\t\treturn Context.Res.Text(str)\n\t}\n\n\treturn Context.Res.Text(\"Row created\")\n}\n```\n```dart\nimport 'dart:io';\nimport 'dart:async';\nimport 'package:dart_appwrite/dart_appwrite.dart';\n\nFuture<dynamic> main(final context) async {\n final client = Client()\n .setProject(Platform.environment['APPWRITE_FUNCTION_PROJECT_ID'])\n\n if (context.req.headers['x-appwrite-user-jwt'] != null) {\n client.setJWT(context.req.headers['x-appwrite-user-jwt']);\n } else {\n return context.res.text(\"Access denied: This function requires authentication. Please sign in to continue.\");\n }\n\n final tablesDB = TablesDB(client);\n\n try {\n await tablesDB.createRow(\n databaseId: '<DATABASE_ID>',\n tableId: '<TABLE_ID>',\n rowId: ID.unique(),\n data: {}\n );\n } catch (e) {\n context.error(\"Failed to create row: \" + e.message);\n return context.res.text(\"Failed to create row\");\n }\n\n return context.res.text(\"Row created\");\n}\n```\n```swift\nimport Appwrite\nimport AppwriteModels\nimport Foundation\n\nfunc main(context: RuntimeContext) async throws -> RuntimeOutput {\n let client = Client()\n .setProject(ProcessInfo.processInfo.environment[\"APPWRITE_FUNCTION_PROJECT_ID\"])\n\n if let jwt = context.req.headers[\"x-appwrite-user-jwt\"] {\n client.setJWT(jwt)\n } else {\n return context.res.text(\"Access denied: This function requires authentication. Please sign in to continue.\")\n }\n\n let tablesDB = TablesDB(client: client)\n\n do {\n try await tablesDB.createRow(\n databaseId: \"<DATABASE_ID>\",\n tableId: \"<TABLE_ID>\",\n rowId: ID.unique()\n data: [:]\n )\n } catch {\n context.error(\"Failed to create row: \\(error.localizedDescription)\")\n return context.res.text(\"Failed to create row\")\n }\n\n return context.res.text(\"Row created\")\n}\n```\n```csharp\nusing Appwrite;\nusing Appwrite.Services;\nusing Appwrite.Models;\n\nnamespace DotNetRuntime\n{\n public class Handler\n {\n public async Task Main(RuntimeContext Context)\n {\n var client = new Client()\n .SetProject(Environment.GetEnvironmentVariable(\"APPWRITE_FUNCTION_PROJECT_ID\"))\n\n if (Context.Req.Headers.ContainsKey(\"x-appwrite-user-jwt\")) {\n client.SetJWT(Context.Req.Headers[\"x-appwrite-user-jwt\"]);\n } else {\n return Context.Res.Text(\"Access denied: This function requires authentication. Please sign in to continue\");\n }\n\n var tablesDB = new TablesDB(client);\n\n try {\n await databases.createRow(\n databaseId: \"<DATABASE_ID>\",\n tableId: \"<TABLE_ID>\",\n rowId: ID.Unique(),\n data: new Dictionary<string, object>());\n } catch (Exception e) {\n Context.Error(\"Failed to create row: \" + e.Message);\n return Context.Res.Text(\"Failed to create row\");\n }\n\n return Context.Res.Text(\"Row created\");\n }\n }\n}\n```\n```kotlin\npackage io.openruntimes.kotlin.src\n\nimport io.openruntimes.kotlin.RuntimeContext\nimport io.openruntimes.kotlin.RuntimeOutput\nimport io.appwrite.Client\nimport io.appwrite.services.TablesDB\nimport io.appwrite.ID\nimport java.util.HashMap\n\nclass Main {\n fun main(context: RuntimeContext): RuntimeOutput {\n val client = Client()\n .setProject(System.getenv(\"APPWRITE_FUNCTION_PROJECT_ID\"))\n\n if (context.req.headers[\"x-appwrite-user-jwt\"] != null) {\n client.setJWT(context.req.headers[\"x-appwrite-user-jwt\"])\n } else {\n return context.res.text(\"Access denied: This function requires authentication. Please sign in to continue.\")\n }\n\n val tablesDB = TablesDB(client)\n\n try {\n tablesDB.createRow(\n databaseId = \"<DATABASE_ID>\",\n tableId = \"<TABLE_ID>\",\n rowId = ID.unique(),\n data = mapOf()\n )\n } catch (e: Exception) {\n context.error(\"Failed to create row: \" + e.message)\n return context.res.text(\"Failed to create row\")\n }\n\n return context.res.text(\"Row created\")\n }\n}\n```\n```java\npackage io.openruntimes.java.src;\n\nimport io.openruntimes.java.RuntimeContext;\nimport io.openruntimes.java.RuntimeOutput;\nimport java.util.HashMap;\nimport io.appwrite.Client;\n\npublic class Main {\n public RuntimeOutput main(RuntimeContext context) throws Exception {\n Client client = new Client()\n .setProject(System.getenv(\"APPWRITE_FUNCTION_PROJECT_ID\"))\n\n if (context.req.headers.containsKey(\"x-appwrite-user-jwt\")) {\n client.setJWT(context.req.headers.get(\"x-appwrite-user-jwt\"));\n } else {\n return context.res.text(\"Access denied: This function requires authentication. Please sign in to continue.\");\n }\n\n Databases tablesDB = new TablesDB(client);\n\n try {\n tablesDB.createRow(\n \"<DATABASE_ID>\",\n \"<TABLE_ID>\",\n ID.unique(),\n new HashMap<>()\n );\n } catch (Exception e) {\n context.error(\"Failed to create row: \" + e.getMessage());\n return context.res.text(\"Failed to create row\");\n }\n\n return context.res.text(\"Row created\");\n\n }\n}\n```\n```rust\nuse appwrite::id::ID;\nuse appwrite::services::tables_db::TablesDB;\nuse appwrite::Client;\nuse openruntimes::{Context, Response};\nuse serde_json::json;\nuse std::env;\n\npub fn main(context: Context) -> Response {\n let client = Client::new()\n .set_endpoint(env::var(\"APPWRITE_FUNCTION_API_ENDPOINT\").unwrap_or_default())\n .set_project(env::var(\"APPWRITE_FUNCTION_PROJECT_ID\").unwrap_or_default());\n\n let client = match context.req.headers.get(\"x-appwrite-user-jwt\") {\n Some(jwt) => client.set_jwt(jwt.clone()),\n None => {\n return context.res.text(\n \"Access denied: This function requires authentication. Please sign in to continue.\",\n None,\n None,\n );\n }\n };\n\n let tables_db = TablesDB::new(&client);\n\n let runtime = tokio::runtime::Builder::new_current_thread()\n .enable_all()\n .build()\n .unwrap();\n\n let result = runtime.block_on(async move {\n tables_db\n .create_row(\n \"<DATABASE_ID>\",\n \"<TABLE_ID>\",\n ID::unique(),\n json!({}),\n None,\n None,\n )\n .await\n });\n\n match result {\n Ok(_) => context.res.text(\"Row created\", None, None),\n Err(e) => {\n context.error(format!(\"Failed to create row: {}\", e));\n context.res.text(\"Failed to create row\", None, None)\n }\n }\n}\n```\n{% /multicode %}\n\n# Code structure {% #code-structure %}\n\nAs 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.\n\n{% tabs %}\n{% tabsitem #nodejs title=\"Node.js\" %}\n```server-nodejs\n// src/utils.js\nexport function add(a, b) {\n return a + b;\n}\n```\n```server-nodejs\n// src/main.js\nimport { add } from './utils.js';\n\nexport default function ({ res }) {\n return res.text(add(1, 2));\n}\n```\n{% /tabsitem %}\n\n{% tabsitem #php title=\"PHP\" %}\n```php\n<?php\n// src/utils.php\nfunction add($a, $b) {\n return $a + $b;\n}\n```\n```php\n<?php\n// src/main.php\nrequire_once(__DIR__ . '/utils.php');\n\nreturn function ($context) {\n return $context->res->text(add(1, 2));\n};\n```\n{% /tabsitem %}\n\n{% tabsitem #python title=\"Python\" %}\n```python\n# src/utils.py\ndef add(a, b):\n return a + b\n```\n```python\n# src/main.py\nfrom .utils import add\n\ndef main(context):\n return context.res.text(add(1, 2))\n```\n{% /tabsitem %}\n\n{% tabsitem #ruby title=\"Ruby\" %}\n```ruby\n# lib/utils.rb\ndef add(a, b)\n return a + b\nend\n```\n```ruby\n# lib/main.rb\nrequire_relative 'utils'\n\ndef main(context)\n return context.res.text(add(1, 2))\nend\n```\n{% /tabsitem %}\n\n{% tabsitem #deno title=\"Deno\" %}\n```deno\n// src/utils.ts\nexport function add(a: number, b: number): number {\n return a + b;\n}\n```\n```deno\n// src/main.ts\nimport { add } from './utils.ts';\n\nexport default function ({res}: {res: any}) {\n return res.text(add(1, 2));\n}\n```\n{% /tabsitem %}\n\n{% tabsitem #go title=\"Go\" %}\n```go\n// src/utils/go.mod\nmodule example.com/utils\n\ngo 1.23.0\n```\n```go\n// src/utils/utils.go\npackage utils\n\nfunc Add(a int, b int) int {\n\treturn a + b\n}\n```\n```go\n// src/main/go.mod\nmodule example.com/main\n\ngo 1.23.0\n\nreplace example.com/utils => ../utils // Run go mod edit -replace example.com/go=../go\n\nrequire example.com/utils v0.0.0-00010101000000-000000000000 // Run go mod tidy\n```\n```go\n// src/main/main.go\npackage main\n\nimport \"example.com/utils\"\n\nfunc main() {\n\t// Get a greeting message and print it.\n\tmessage := utils.Add(5, 4)\n\tprint(message)\n}\n```\n{% /tabsitem %}\n\n{% tabsitem #dart title=\"Dart\" %}\n```dart\n// lib/utils.dart\nint add(int a, int b) {\n return a + b;\n}\n```\n```dart\n// lib/main.dart\nimport 'dart:async';\n\nimport 'package:package_name/utils.dart';\n\nFuture<dynamic> main(final context) async {\n return context.res.text(add(1, 2));\n}\n```\n{% /tabsitem %}\n\n{% tabsitem #swift title=\"Swift\" %}\n```swift\n// Sources/utils.swift\nfunc add(_ a: Int, _ b: Int) -> Int {\n return a + b\n}\n```\n```swift\n// Sources/index.swift\nimport Foundation\n\nfunc main(context: RuntimeContext) async throws -> RuntimeOutput {\n return context.res.text(add(1, 2))\n}\n```\n{% /tabsitem %}\n\n{% tabsitem #dotnet title=\".NET\" %}\n```csharp\n// src/Utils.cs\nnamespace DotNetRuntime\n{\n public static class Utils\n {\n public static int Add(int a, int b)\n {\n return a + b;\n }\n }\n}\n```\n```csharp\n// src/Index.cs\nnamespace DotNetRuntime\n{\n public class Handler {\n public async Task<RuntimeOutput> Main(RuntimeContext Context)\n {\n return Context.Res.Text(Utils.Add(1, 2));\n }\n }\n}\n```\n{% /tabsitem %}\n\n{% tabsitem #kotlin title=\"Kotlin\" %}\n```kotlin\n// src/Utils.kt\npackage io.openruntimes.kotlin.src\n\nobject Utils {\n fun add(a: Int, b: Int): Int {\n return a + b\n }\n}\n```\n```kotlin\n// src/Main.kt\npackage io.openruntimes.kotlin.src\n\nimport io.openruntimes.kotlin.RuntimeContext\nimport io.openruntimes.kotlin.RuntimeOutput\nimport io.openruntimes.kotlin.Utils\n\nclass Main {\n fun main(context: RuntimeContext): RuntimeOutput {\n return context.res.text(Utils.add(1, 2))\n }\n}\n```\n{% /tabsitem %}\n\n{% tabsitem #java title=\"Java\" %}\n```java\n// src/Utils.java\npackage io.openruntimes.java.src;\n\nclass Utils {\n public static int add(int a, int b) {\n return a + b;\n }\n}\n```\n```java\npackage io.openruntimes.java.src;\n\nimport io.openruntimes.java.RuntimeContext;\nimport io.openruntimes.java.RuntimeOutput;\nimport io.openruntimes.java.Utils;\n\npublic class Main {\n public RuntimeOutput main(RuntimeContext context) throws Exception {\n return context.res.text(Utils.add(1, 2));\n }\n}\n```\n{% /tabsitem %}\n\n{% tabsitem #rust title=\"Rust\" %}\n```rust\n// utils/mod.rs\npub fn add(a: i32, b: i32) -> i32 {\n a + b\n}\n```\n```rust\n// lib.rs\nuse openruntimes::{Context, Response};\n\nmod utils;\n\npub fn main(context: Context) -> Response {\n context\n .res\n .text(utils::add(1, 2).to_string(), None, None)\n}\n```\n{% /tabsitem %}\n\n{% /tabs %}"}, {"path": "docs/products/functions/develop-locally", "title": "Develop locally", "description": "Learn to develop Appwrite functions locally.", "content": "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.\n\n# Setup {% #setup %}\n\nWe 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).\n\n1. Install the [Docker CLI](https://www.docker.com/products/docker-desktop/)\n2. Ensure Docker is running in the background\n3. Install the [Appwrite CLI](/docs/tooling/command-line/installation#getting-started)\n4. [Log in](/docs/tooling/command-line/installation#login) to your Appwrite account using `appwrite login`\n5. [Initialize your project](/docs/tooling/command-line/installation#initialization)\n6. [Initialize an Appwrite function](/docs/tooling/command-line/functions) and copy and paste your code\n\n# Develop {% #develop %}\n\nUse the `appwrite run functions` command to develop your function locally.\n\n| Parameter | Description |\n|---------------------|----------------------------------------------------------------------------------------------------------------------------------------------|\n|`--port` | Set your function port; it defaults to `3000`, or the closest available, i.e. `3001`, `3002`, etc. |\n|`--function-id` | Select a function so you don't have to click through the list each time. |\n|`--user-id <user-id>`| Impersonates a user. Automatically sets `x-appwrite-user-id` and `x-appwrite-user-jwt` headers if the user exists. |\n|`--with-variables` | Set production environment variables for your function. Do this only if your functions don't have production secrets to avoid security risks.|\n|`--no-reload` | Set your functions to not hot reload. Any changes to your code won't cause your function to restart. |\n\n```sh\nappwrite run functions --port 3000 --function-id \"<FUNCTION_ID>\"\n\n runtime | entrypoint | path | commands \n-----------|-------------|--------------------------------|--------------\n node-16.0 | src/main.js | functions/<FUNCTION_ID> | npm install \n\nℹ Info: If you wish to change your local settings, update the appwrite.config.json file and rerun the 'appwrite run' command.\n♥ Hint: Permissions, events, CRON and timeouts dont apply when running locally.\nℹ Info: Pulling Docker image ...\n♥ Hint: This may take a few minutes, but we only need to do this once.\nℹ Info: Building function using Docker ...\nPreparing for build ...\n\nBuilding ...\n\n\nadded 4 packages, and audited 5 packages in 2s\n\n\n1 package is looking for funding\n run `npm fund` for details\n\n\nfound 0 vulnerabilities\n\nPacking build ...\n\nBuild finished.\n\nℹ Info: Starting function using Docker ...\n♥ Hint: Function automatically restarts when you edit your code.\n✓ Success: Visit http://localhost:3000/ to execute your function.\n```\n\nThis 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.\n\n{% arrow_link href=\"/docs/products/functions/develop\" %}\nLearn more about developing a function\n{% /arrow_link %}\n\n# Dynamic API keys {% #dynamic-api-keys %}\n\nYou 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.\n \n{% arrow_link href=\"/docs/products/functions/develop#dynamic-api-key\" %}\nLearn more about dynamic API keys\n{% /arrow_link %}\n\n# Hot reload {% #hot-reload %}\n\nBy 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.\n\nBecause runtimes with compiled languages must translate the source code into machine code, the function must rebuild on change.\n\nWhen 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.\n\n{% table %}\n*   {% width=80 %}\n* Language\n* Dependency File\n---\n* {% only_dark %}{% icon_image src=\"/images/platforms/dark/node.svg\" alt=\"Node.js logo\" size=\"m\" /%}{% /only_dark %}\n{% only_light %}{% icon_image src=\"/images/platforms/light/node.svg\" alt=\"Node.js logo\" size=\"m\" /%}{% /only_light %}\n* Node.js\n* package.json, package-lock.json\n---\n* {% only_dark %}{% icon_image src=\"/images/platforms/dark/php.svg\" alt=\"PHP logo\" size=\"m\" /%}{% /only_dark %}\n{% only_light %}{% icon_image src=\"/images/platforms/light/php.svg\" alt=\"PHP logo\" size=\"m\" /%}{% /only_light %}\n* PHP\n* composer.json, composer.lock\n---\n* {% only_dark %}{% icon_image src=\"/images/platforms/dark/python.svg\" alt=\"Python logo\" size=\"m\" /%}{% /only_dark %}\n{% only_light %}{% icon_image src=\"/images/platforms/light/python.svg\" alt=\"Python logo\" size=\"m\" /%}{% /only_light %}\n* Python\n* requirements.txt, requirements.lock\n---\n* {% only_dark %}{% icon_image src=\"/images/platforms/dark/ruby.svg\" alt=\"Ruby logo\" size=\"m\" /%}{% /only_dark %}\n{% only_light %}{% icon_image src=\"/images/platforms/light/ruby.svg\" alt=\"Ruby logo\" size=\"m\" /%}{% /only_light %}\n* Ruby\n* Gemfile, Gemfile.lock\n---\n* {% only_dark %}{% icon_image src=\"/images/platforms/dark/deno.svg\" alt=\"Deno logo\" size=\"m\" /%}{% /only_dark %}\n{% only_light %}{% icon_image src=\"/images/platforms/light/deno.svg\" alt=\"Deno logo\" size=\"m\" /%}{% /only_light %}\n* Deno\n* Import URLs\n---\n* {% only_dark %}{% icon_image src=\"/images/platforms/dark/go.svg\" alt=\"Go logo\" size=\"m\" /%}{% /only_dark %}\n{% only_light %}{% icon_image src=\"/images/platforms/light/go.svg\" alt=\"Go logo\" size=\"m\" /%}{% /only_light %}\n* Go\n* go.mod\n---\n* {% only_dark %}{% icon_image src=\"/images/platforms/dark/dart.svg\" alt=\"Dart logo\" size=\"m\" /%}{% /only_dark %}\n{% only_light %}{% icon_image src=\"/images/platforms/light/dart.svg\" alt=\"Dart logo\" size=\"m\" /%}{% /only_light %}\n* Dart\n* Pubspec.yaml\n---\n* {% only_dark %}{% icon_image src=\"/images/platforms/dark/swift.svg\" alt=\"Swift logo\" size=\"m\" /%}{% /only_dark %}\n{% only_light %}{% icon_image src=\"/images/platforms/light/swift.svg\" alt=\"Swift logo\" size=\"m\" /%}{% /only_light %}\n* Swift\n* Package.swift (Swift Package Manager Files)\n---\n* {% only_dark %}{% icon_image src=\"/images/platforms/dark/dotnet.svg\" alt=\".NET logo\" size=\"m\" /%}{% /only_dark %}\n{% only_light %}{% icon_image src=\"/images/platforms/light/dotnet.svg\" alt=\".NET logo\" size=\"m\" /%}{% /only_light %}\n* .NET\n* .nupkg (NuGet Packages)\n---\n* {% only_dark %}{% icon_image src=\"/images/platforms/dark/bun.svg\" alt=\"Bun logo\" size=\"m\" /%}{% /only_dark %}\n{% only_light %}{% icon_image src=\"/images/platforms/light/bun.svg\" alt=\"Bun logo\" size=\"m\" /%}{% /only_light %}\n* Bun\n* package.json, package-lock.json, bun.lockb\n---\n* {% only_dark %}{% icon_image src=\"/images/platforms/dark/kotlin.svg\" alt=\"Kotlin logo\" size=\"m\" /%}{% /only_dark %}\n{% only_light %}{% icon_image src=\"/images/platforms/light/kotlin.svg\" alt=\"Kotlin logo\" size=\"m\" /%}{% /only_light %}\n* Kotlin\n* JAR Files (Java ARchive)\n---\n* {% only_dark %}{% icon_image src=\"/images/platforms/dark/java.svg\" alt=\"Java logo\" size=\"m\" /%}{% /only_dark %}\n{% only_light %}{% icon_image src=\"/images/platforms/light/java.svg\" alt=\"Java logo\" size=\"m\" /%}{% /only_light %}\n* Java\n* JAR Files (Java ARchive)\n---\n* {% only_dark %}{% icon_image src=\"/images/platforms/dark/c.svg\" alt=\"C++ logo\" size=\"m\" /%}{% /only_dark %}\n{% only_light %}{% icon_image src=\"/images/platforms/light/c.svg\" alt=\"C++ logo\" size=\"m\" /%}{% /only_light %}\n* C++\n* .h (Header Files)\n---\n* {% only_dark %}{% icon_image src=\"/images/platforms/dark/rust.svg\" alt=\"Rust logo\" size=\"m\" /%}{% /only_dark %}\n{% only_light %}{% icon_image src=\"/images/platforms/light/rust.svg\" alt=\"Rust logo\" size=\"m\" /%}{% /only_light %}\n* Rust\n* Cargo.toml, Cargo.lock\n{% /table %}\n\n# Impersonate user {% #impersonate-user %}\n\nYou 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.\n\nWhen 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.\n\n```sh\nappwrite run functions --user-id \"<USER_ID>\"\n```\n\n# Push function {% #push-function %}\n\nOnce you've developed your function, push it by running the following CLI command\n\n```sh\nappwrite push functions\n```"}, {"path": "docs/products/functions/domains", "title": "Domains", "description": "Execute Appwrite Functions through domains using standard HTTP GET, POST, or other request methods to serve static, JSON, HTML, or other content.", "content": "Each deployed function can have its own domain, generated or developer defined.\nYou can use this domain to execute Appwrite Functions through HTTP methods.\nYou can use common practices like using paths, query parameters, headers, HTTP methods, formdata,\nand all the typical HTTP concepts to implement Appwrite Functions.\n\nAppwrite generates TLS certificates to enforce HTTPS on all Appwrite Functions domains, generated or custom.\nThese domains are safe to use and access in production.\n\n{% arrow_link href=\"/docs/products/functions/develop\" %}\nLearn about Function development\n{% /arrow_link %}\n\n# Generated domains {% #generated-domains %}\n\nEach function automatically receives a region-specific domain that's ready to use immediately after deployment.\n\n1. In the Appwrite Console's sidebar, click **Functions**.\n1. Under the **Domains** tab, you'll find the generated domain from Appwrite.\n\nThe generated domain ends with `.appwrite.run`, which executes your function directly in the region where it is deployed. For example:\n\n```text\nhttps://64d4d22db370ae41a32e.fra.appwrite.run\n```\n\n# Edge network domain {% #edge-network-domain %}\n\nYou 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.\n\nTo add an edge network domain:\n\n1. Navigate to the Appwrite Console's **Functions** page.\n2. Navigate to the **Domains** tab.\n3. Click on **Create domain** and add the `appwrite.network` domain to your function.\n\nThe edge network domain ends with `.appwrite.network`. For example:\n\n```text\nhttps://64d4d22db370ae41a32e.appwrite.network\n```\n\n{% arrow_link href=\"/docs/products/network/edges\" %}\nLearn more about edge network\n{% /arrow_link %}\n\n\n# Add a custom domain {% #add-a-custom-domain %}\n\n1. Navigate to the Appwrite Console's **Functions** page.\n2. Navigate to the **Domains** tab.\n3. Click on **Create domain**.\n4. Input your domain and click **Next**.\n5. Copy the **CNAME** record and add it to your domain registrar.\n6. Click **Go to console** and wait for verification and certificate generation.\n\nDNS records can take up to 48 hours to propagate.\n\nWhen both **VERIFICATION STATUS** and **CERTIFICATE STATUS** are green, the new domain is ready to use."}, {"path": "docs/products/functions/environment-variables", "title": "Environment variables", "description": "Set environment variables for your Appwrite Functions to pass constants and secrets at build and runtime.", "content": "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.\n\nA function reads from three sources, in this order of precedence:\n\n1. **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/advanced/security/environment-variables) for the full reference.\n2. **Function variables** are scoped to a single function. Override a project variable for one function by setting the same key on the function itself.\n3. **Appwrite-injected variables** are set by Appwrite at execution time (for example, `APPWRITE_FUNCTION_PROJECT_ID`). These take final precedence and cannot be overridden.\n\n{% info title=\"Redeployment required\" %}\nVariable changes only take effect on the next deployment. Redeploy your function after creating, updating, or deleting variables.\n{% /info %}\n\n# Manage in the Console {% #console %}\n\n1. Navigate to your function in the Appwrite Console.\n2. Open the **Settings** tab > **Environment variables** section.\n3. Click **Create variable** and enter a key and value.\n4. Optionally select the **Secret** checkbox to prevent any team member from reading the value after creation.\n5. Click **Create**, then redeploy the function for the change to take effect.\n\n{% only_dark %}\n![Function environment variables](/images/docs/functions/dark/env-variables.avif)\n{% /only_dark %}\n{% only_light %}\n![Function environment variables](/images/docs/functions/env-variables.avif)\n{% /only_light %}\n\nYou can also configure global variables that apply to all your functions from your project's **Settings** page. See [project variables](/docs/advanced/security/environment-variables) for details.\n\n# Manage with a Server SDK {% #server-sdks %}\n\nYou can also manage function variables programmatically using a [Server SDK](/docs/sdks#server). Each call requires an [API key](/docs/advanced/security/api-keys) with the `functions.write` scope to create, update, or delete variables, or the `functions.read` scope to list and read them.\n\n## Create a variable {% #create-variable %}\n\n{% multicode %}\n```server-nodejs\nimport { Client, Functions } from 'node-appwrite';\n\nconst client = new Client()\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<YOUR_PROJECT_ID>') // Your project ID\n .setKey('<YOUR_API_KEY>'); // Your API key\n\nconst functions = new Functions(client);\n\nconst result = await functions.createVariable({\n functionId: '<FUNCTION_ID>',\n key: '<KEY>',\n value: '<VALUE>',\n secret: false // optional\n});\n```\n```server-deno\nimport { Client, Functions } from \"npm:node-appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<YOUR_PROJECT_ID>') // Your project ID\n .setKey('<YOUR_API_KEY>'); // Your API key\n\nconst functions = new Functions(client);\n\nconst result = await functions.createVariable({\n functionId: '<FUNCTION_ID>',\n key: '<KEY>',\n value: '<VALUE>',\n secret: false // optional\n});\n```\n```server-php\n<?php\n\nuse Appwrite\\Client;\nuse Appwrite\\Services\\Functions;\n\n$client = (new Client())\n ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n ->setProject('<YOUR_PROJECT_ID>') // Your project ID\n ->setKey('<YOUR_API_KEY>'); // Your API key\n\n$functions = new Functions($client);\n\n$result = $functions->createVariable(\n functionId: '<FUNCTION_ID>',\n key: '<KEY>',\n value: '<VALUE>',\n secret: false // optional\n);\n```\n```server-python\nfrom appwrite.client import Client\nfrom appwrite.services.functions import Functions\nfrom appwrite.models import Variable\n\nclient = Client()\nclient.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\nclient.set_project('<YOUR_PROJECT_ID>') # Your project ID\nclient.set_key('<YOUR_API_KEY>') # Your API key\n\nfunctions = Functions(client)\n\nresult: Variable = functions.create_variable(\n function_id = '<FUNCTION_ID>',\n key = '<KEY>',\n value = '<VALUE>',\n secret = False # optional\n)\n\nprint(result.model_dump())\n```\n```server-ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('<YOUR_PROJECT_ID>') # Your project ID\n .set_key('<YOUR_API_KEY>') # Your API key\n\nfunctions = Functions.new(client)\n\nresult = functions.create_variable(\n function_id: '<FUNCTION_ID>',\n key: '<KEY>',\n value: '<VALUE>',\n secret: false # optional\n)\n```\n```server-dotnet\nusing Appwrite;\nusing Appwrite.Models;\nusing Appwrite.Services;\n\nClient client = new Client()\n .SetEndPoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .SetProject(\"<YOUR_PROJECT_ID>\") // Your project ID\n .SetKey(\"<YOUR_API_KEY>\"); // Your API key\n\nFunctions functions = new Functions(client);\n\nVariable result = await functions.CreateVariable(\n functionId: \"<FUNCTION_ID>\",\n key: \"<KEY>\",\n value: \"<VALUE>\",\n secret: false // optional\n);\n```\n```server-dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\n\nClient client = Client()\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<YOUR_PROJECT_ID>') // Your project ID\n .setKey('<YOUR_API_KEY>'); // Your API key\n\nFunctions functions = Functions(client);\n\nVariable result = await functions.createVariable(\n functionId: '<FUNCTION_ID>',\n key: '<KEY>',\n value: '<VALUE>',\n secret: false, // (optional)\n);\n```\n```server-kotlin\nimport io.appwrite.Client\nimport io.appwrite.coroutines.CoroutineCallback\nimport io.appwrite.services.Functions\n\nval client = Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<YOUR_PROJECT_ID>\") // Your project ID\n .setKey(\"<YOUR_API_KEY>\") // Your API key\n\nval functions = Functions(client)\n\nval response = functions.createVariable(\n functionId = \"<FUNCTION_ID>\",\n key = \"<KEY>\",\n value = \"<VALUE>\",\n secret = false // optional\n)\n```\n```server-java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Functions;\n\nClient client = new Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<YOUR_PROJECT_ID>\") // Your project ID\n .setKey(\"<YOUR_API_KEY>\"); // Your API key\n\nFunctions functions = new Functions(client);\n\nfunctions.createVariable(\n \"<FUNCTION_ID>\", // functionId\n \"<KEY>\", // key\n \"<VALUE>\", // value\n false, // secret (optional)\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(result);\n })\n);\n```\n```server-swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<YOUR_PROJECT_ID>\") // Your project ID\n .setKey(\"<YOUR_API_KEY>\") // Your API key\n\nlet functions = Functions(client)\n\nlet variable = try await functions.createVariable(\n functionId: \"<FUNCTION_ID>\",\n key: \"<KEY>\",\n value: \"<VALUE>\",\n secret: false // optional\n)\n```\n```server-go\npackage main\n\nimport (\n \"fmt\"\n \"github.com/appwrite/sdk-for-go/appwrite\"\n)\n\nclient := appwrite.NewClient(\n appwrite.WithEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\"),\n appwrite.WithProject(\"<YOUR_PROJECT_ID>\"),\n appwrite.WithKey(\"<YOUR_API_KEY>\"),\n)\n\nfunctions := appwrite.NewFunctions(client)\n\nresponse, error := functions.CreateVariable(\n \"<FUNCTION_ID>\",\n \"<KEY>\",\n \"<VALUE>\",\n appwrite.WithCreateVariableSecret(false),\n)\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::Functions;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n let client = Client::new();\n client.set_endpoint(\"https://<REGION>.cloud.appwrite.io/v1\"); // Your API Endpoint\n client.set_project(\"<YOUR_PROJECT_ID>\"); // Your project ID\n client.set_key(\"<YOUR_API_KEY>\"); // Your API key\n\n let functions = Functions::new(&client);\n\n let result = functions.create_variable(\n \"<FUNCTION_ID>\",\n \"<KEY>\",\n \"<VALUE>\",\n Some(false) // optional\n ).await?;\n\n let _ = result;\n\n Ok(())\n}\n```\n{% /multicode %}\n\n## List variables {% #list-variables %}\n\n{% multicode %}\n```server-nodejs\nimport { Client, Functions } from 'node-appwrite';\n\nconst client = new Client()\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<YOUR_PROJECT_ID>') // Your project ID\n .setKey('<YOUR_API_KEY>'); // Your API key\n\nconst functions = new Functions(client);\n\nconst result = await functions.listVariables({\n functionId: '<FUNCTION_ID>'\n});\n```\n```server-deno\nimport { Client, Functions } from \"npm:node-appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<YOUR_PROJECT_ID>') // Your project ID\n .setKey('<YOUR_API_KEY>'); // Your API key\n\nconst functions = new Functions(client);\n\nconst result = await functions.listVariables({\n functionId: '<FUNCTION_ID>'\n});\n```\n```server-php\n<?php\n\nuse Appwrite\\Client;\nuse Appwrite\\Services\\Functions;\n\n$client = (new Client())\n ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n ->setProject('<YOUR_PROJECT_ID>') // Your project ID\n ->setKey('<YOUR_API_KEY>'); // Your API key\n\n$functions = new Functions($client);\n\n$result = $functions->listVariables(\n functionId: '<FUNCTION_ID>'\n);\n```\n```server-python\nfrom appwrite.client import Client\nfrom appwrite.services.functions import Functions\nfrom appwrite.models import VariableList\n\nclient = Client()\nclient.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\nclient.set_project('<YOUR_PROJECT_ID>') # Your project ID\nclient.set_key('<YOUR_API_KEY>') # Your API key\n\nfunctions = Functions(client)\n\nresult: VariableList = functions.list_variables(\n function_id = '<FUNCTION_ID>'\n)\n\nprint(result.model_dump())\n```\n```server-ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('<YOUR_PROJECT_ID>') # Your project ID\n .set_key('<YOUR_API_KEY>') # Your API key\n\nfunctions = Functions.new(client)\n\nresult = functions.list_variables(\n function_id: '<FUNCTION_ID>'\n)\n```\n```server-dotnet\nusing Appwrite;\nusing Appwrite.Models;\nusing Appwrite.Services;\n\nClient client = new Client()\n .SetEndPoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .SetProject(\"<YOUR_PROJECT_ID>\") // Your project ID\n .SetKey(\"<YOUR_API_KEY>\"); // Your API key\n\nFunctions functions = new Functions(client);\n\nVariableList result = await functions.ListVariables(\n functionId: \"<FUNCTION_ID>\"\n);\n```\n```server-dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\n\nClient client = Client()\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<YOUR_PROJECT_ID>') // Your project ID\n .setKey('<YOUR_API_KEY>'); // Your API key\n\nFunctions functions = Functions(client);\n\nVariableList result = await functions.listVariables(\n functionId: '<FUNCTION_ID>',\n);\n```\n```server-kotlin\nimport io.appwrite.Client\nimport io.appwrite.coroutines.CoroutineCallback\nimport io.appwrite.services.Functions\n\nval client = Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<YOUR_PROJECT_ID>\") // Your project ID\n .setKey(\"<YOUR_API_KEY>\") // Your API key\n\nval functions = Functions(client)\n\nval response = functions.listVariables(\n functionId = \"<FUNCTION_ID>\"\n)\n```\n```server-java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Functions;\n\nClient client = new Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<YOUR_PROJECT_ID>\") // Your project ID\n .setKey(\"<YOUR_API_KEY>\"); // Your API key\n\nFunctions functions = new Functions(client);\n\nfunctions.listVariables(\n \"<FUNCTION_ID>\", // functionId\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(result);\n })\n);\n```\n```server-swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<YOUR_PROJECT_ID>\") // Your project ID\n .setKey(\"<YOUR_API_KEY>\") // Your API key\n\nlet functions = Functions(client)\n\nlet variableList = try await functions.listVariables(\n functionId: \"<FUNCTION_ID>\"\n)\n```\n```server-go\npackage main\n\nimport (\n \"fmt\"\n \"github.com/appwrite/sdk-for-go/appwrite\"\n)\n\nclient := appwrite.NewClient(\n appwrite.WithEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\"),\n appwrite.WithProject(\"<YOUR_PROJECT_ID>\"),\n appwrite.WithKey(\"<YOUR_API_KEY>\"),\n)\n\nfunctions := appwrite.NewFunctions(client)\n\nresponse, error := functions.ListVariables(\n \"<FUNCTION_ID>\",\n)\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::Functions;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n let client = Client::new();\n client.set_endpoint(\"https://<REGION>.cloud.appwrite.io/v1\"); // Your API Endpoint\n client.set_project(\"<YOUR_PROJECT_ID>\"); // Your project ID\n client.set_key(\"<YOUR_API_KEY>\"); // Your API key\n\n let functions = Functions::new(&client);\n\n let result = functions.list_variables(\n \"<FUNCTION_ID>\"\n ).await?;\n\n let _ = result;\n\n Ok(())\n}\n```\n{% /multicode %}\n\n## Get a variable {% #get-variable %}\n\n{% multicode %}\n```server-nodejs\nimport { Client, Functions } from 'node-appwrite';\n\nconst client = new Client()\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<YOUR_PROJECT_ID>') // Your project ID\n .setKey('<YOUR_API_KEY>'); // Your API key\n\nconst functions = new Functions(client);\n\nconst result = await functions.getVariable({\n functionId: '<FUNCTION_ID>',\n variableId: '<VARIABLE_ID>'\n});\n```\n```server-deno\nimport { Client, Functions } from \"npm:node-appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<YOUR_PROJECT_ID>') // Your project ID\n .setKey('<YOUR_API_KEY>'); // Your API key\n\nconst functions = new Functions(client);\n\nconst result = await functions.getVariable({\n functionId: '<FUNCTION_ID>',\n variableId: '<VARIABLE_ID>'\n});\n```\n```server-php\n<?php\n\nuse Appwrite\\Client;\nuse Appwrite\\Services\\Functions;\n\n$client = (new Client())\n ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n ->setProject('<YOUR_PROJECT_ID>') // Your project ID\n ->setKey('<YOUR_API_KEY>'); // Your API key\n\n$functions = new Functions($client);\n\n$result = $functions->getVariable(\n functionId: '<FUNCTION_ID>',\n variableId: '<VARIABLE_ID>'\n);\n```\n```server-python\nfrom appwrite.client import Client\nfrom appwrite.services.functions import Functions\nfrom appwrite.models import Variable\n\nclient = Client()\nclient.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\nclient.set_project('<YOUR_PROJECT_ID>') # Your project ID\nclient.set_key('<YOUR_API_KEY>') # Your API key\n\nfunctions = Functions(client)\n\nresult: Variable = functions.get_variable(\n function_id = '<FUNCTION_ID>',\n variable_id = '<VARIABLE_ID>'\n)\n\nprint(result.model_dump())\n```\n```server-ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('<YOUR_PROJECT_ID>') # Your project ID\n .set_key('<YOUR_API_KEY>') # Your API key\n\nfunctions = Functions.new(client)\n\nresult = functions.get_variable(\n function_id: '<FUNCTION_ID>',\n variable_id: '<VARIABLE_ID>'\n)\n```\n```server-dotnet\nusing Appwrite;\nusing Appwrite.Models;\nusing Appwrite.Services;\n\nClient client = new Client()\n .SetEndPoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .SetProject(\"<YOUR_PROJECT_ID>\") // Your project ID\n .SetKey(\"<YOUR_API_KEY>\"); // Your API key\n\nFunctions functions = new Functions(client);\n\nVariable result = await functions.GetVariable(\n functionId: \"<FUNCTION_ID>\",\n variableId: \"<VARIABLE_ID>\"\n);\n```\n```server-dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\n\nClient client = Client()\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<YOUR_PROJECT_ID>') // Your project ID\n .setKey('<YOUR_API_KEY>'); // Your API key\n\nFunctions functions = Functions(client);\n\nVariable result = await functions.getVariable(\n functionId: '<FUNCTION_ID>',\n variableId: '<VARIABLE_ID>',\n);\n```\n```server-kotlin\nimport io.appwrite.Client\nimport io.appwrite.coroutines.CoroutineCallback\nimport io.appwrite.services.Functions\n\nval client = Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<YOUR_PROJECT_ID>\") // Your project ID\n .setKey(\"<YOUR_API_KEY>\") // Your API key\n\nval functions = Functions(client)\n\nval response = functions.getVariable(\n functionId = \"<FUNCTION_ID>\",\n variableId = \"<VARIABLE_ID>\"\n)\n```\n```server-java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Functions;\n\nClient client = new Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<YOUR_PROJECT_ID>\") // Your project ID\n .setKey(\"<YOUR_API_KEY>\"); // Your API key\n\nFunctions functions = new Functions(client);\n\nfunctions.getVariable(\n \"<FUNCTION_ID>\", // functionId\n \"<VARIABLE_ID>\", // variableId\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(result);\n })\n);\n```\n```server-swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<YOUR_PROJECT_ID>\") // Your project ID\n .setKey(\"<YOUR_API_KEY>\") // Your API key\n\nlet functions = Functions(client)\n\nlet variable = try await functions.getVariable(\n functionId: \"<FUNCTION_ID>\",\n variableId: \"<VARIABLE_ID>\"\n)\n```\n```server-go\npackage main\n\nimport (\n \"fmt\"\n \"github.com/appwrite/sdk-for-go/appwrite\"\n)\n\nclient := appwrite.NewClient(\n appwrite.WithEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\"),\n appwrite.WithProject(\"<YOUR_PROJECT_ID>\"),\n appwrite.WithKey(\"<YOUR_API_KEY>\"),\n)\n\nfunctions := appwrite.NewFunctions(client)\n\nresponse, error := functions.GetVariable(\n \"<FUNCTION_ID>\",\n \"<VARIABLE_ID>\",\n)\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::Functions;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n let client = Client::new();\n client.set_endpoint(\"https://<REGION>.cloud.appwrite.io/v1\"); // Your API Endpoint\n client.set_project(\"<YOUR_PROJECT_ID>\"); // Your project ID\n client.set_key(\"<YOUR_API_KEY>\"); // Your API key\n\n let functions = Functions::new(&client);\n\n let result = functions.get_variable(\n \"<FUNCTION_ID>\",\n \"<VARIABLE_ID>\"\n ).await?;\n\n let _ = result;\n\n Ok(())\n}\n```\n{% /multicode %}\n\n## Update a variable {% #update-variable %}\n\nYou 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.\n\n{% multicode %}\n```server-nodejs\nimport { Client, Functions } from 'node-appwrite';\n\nconst client = new Client()\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<YOUR_PROJECT_ID>') // Your project ID\n .setKey('<YOUR_API_KEY>'); // Your API key\n\nconst functions = new Functions(client);\n\nconst result = await functions.updateVariable({\n functionId: '<FUNCTION_ID>',\n variableId: '<VARIABLE_ID>',\n key: '<KEY>',\n value: '<VALUE>', // optional\n secret: false // optional\n});\n```\n```server-deno\nimport { Client, Functions } from \"npm:node-appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<YOUR_PROJECT_ID>') // Your project ID\n .setKey('<YOUR_API_KEY>'); // Your API key\n\nconst functions = new Functions(client);\n\nconst result = await functions.updateVariable({\n functionId: '<FUNCTION_ID>',\n variableId: '<VARIABLE_ID>',\n key: '<KEY>',\n value: '<VALUE>', // optional\n secret: false // optional\n});\n```\n```server-php\n<?php\n\nuse Appwrite\\Client;\nuse Appwrite\\Services\\Functions;\n\n$client = (new Client())\n ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n ->setProject('<YOUR_PROJECT_ID>') // Your project ID\n ->setKey('<YOUR_API_KEY>'); // Your API key\n\n$functions = new Functions($client);\n\n$result = $functions->updateVariable(\n functionId: '<FUNCTION_ID>',\n variableId: '<VARIABLE_ID>',\n key: '<KEY>',\n value: '<VALUE>', // optional\n secret: false // optional\n);\n```\n```server-python\nfrom appwrite.client import Client\nfrom appwrite.services.functions import Functions\nfrom appwrite.models import Variable\n\nclient = Client()\nclient.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\nclient.set_project('<YOUR_PROJECT_ID>') # Your project ID\nclient.set_key('<YOUR_API_KEY>') # Your API key\n\nfunctions = Functions(client)\n\nresult: Variable = functions.update_variable(\n function_id = '<FUNCTION_ID>',\n variable_id = '<VARIABLE_ID>',\n key = '<KEY>',\n value = '<VALUE>', # optional\n secret = False # optional\n)\n\nprint(result.model_dump())\n```\n```server-ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('<YOUR_PROJECT_ID>') # Your project ID\n .set_key('<YOUR_API_KEY>') # Your API key\n\nfunctions = Functions.new(client)\n\nresult = functions.update_variable(\n function_id: '<FUNCTION_ID>',\n variable_id: '<VARIABLE_ID>',\n key: '<KEY>',\n value: '<VALUE>', # optional\n secret: false # optional\n)\n```\n```server-dotnet\nusing Appwrite;\nusing Appwrite.Models;\nusing Appwrite.Services;\n\nClient client = new Client()\n .SetEndPoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .SetProject(\"<YOUR_PROJECT_ID>\") // Your project ID\n .SetKey(\"<YOUR_API_KEY>\"); // Your API key\n\nFunctions functions = new Functions(client);\n\nVariable result = await functions.UpdateVariable(\n functionId: \"<FUNCTION_ID>\",\n variableId: \"<VARIABLE_ID>\",\n key: \"<KEY>\",\n value: \"<VALUE>\", // optional\n secret: false // optional\n);\n```\n```server-dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\n\nClient client = Client()\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<YOUR_PROJECT_ID>') // Your project ID\n .setKey('<YOUR_API_KEY>'); // Your API key\n\nFunctions functions = Functions(client);\n\nVariable result = await functions.updateVariable(\n functionId: '<FUNCTION_ID>',\n variableId: '<VARIABLE_ID>',\n key: '<KEY>',\n value: '<VALUE>', // (optional)\n secret: false, // (optional)\n);\n```\n```server-kotlin\nimport io.appwrite.Client\nimport io.appwrite.coroutines.CoroutineCallback\nimport io.appwrite.services.Functions\n\nval client = Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<YOUR_PROJECT_ID>\") // Your project ID\n .setKey(\"<YOUR_API_KEY>\") // Your API key\n\nval functions = Functions(client)\n\nval response = functions.updateVariable(\n functionId = \"<FUNCTION_ID>\",\n variableId = \"<VARIABLE_ID>\",\n key = \"<KEY>\",\n value = \"<VALUE>\", // optional\n secret = false // optional\n)\n```\n```server-java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Functions;\n\nClient client = new Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<YOUR_PROJECT_ID>\") // Your project ID\n .setKey(\"<YOUR_API_KEY>\"); // Your API key\n\nFunctions functions = new Functions(client);\n\nfunctions.updateVariable(\n \"<FUNCTION_ID>\", // functionId\n \"<VARIABLE_ID>\", // variableId\n \"<KEY>\", // key\n \"<VALUE>\", // value (optional)\n false, // secret (optional)\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(result);\n })\n);\n```\n```server-swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<YOUR_PROJECT_ID>\") // Your project ID\n .setKey(\"<YOUR_API_KEY>\") // Your API key\n\nlet functions = Functions(client)\n\nlet variable = try await functions.updateVariable(\n functionId: \"<FUNCTION_ID>\",\n variableId: \"<VARIABLE_ID>\",\n key: \"<KEY>\",\n value: \"<VALUE>\", // optional\n secret: false // optional\n)\n```\n```server-go\npackage main\n\nimport (\n \"fmt\"\n \"github.com/appwrite/sdk-for-go/appwrite\"\n)\n\nclient := appwrite.NewClient(\n appwrite.WithEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\"),\n appwrite.WithProject(\"<YOUR_PROJECT_ID>\"),\n appwrite.WithKey(\"<YOUR_API_KEY>\"),\n)\n\nfunctions := appwrite.NewFunctions(client)\n\nresponse, error := functions.UpdateVariable(\n \"<FUNCTION_ID>\",\n \"<VARIABLE_ID>\",\n \"<KEY>\",\n appwrite.WithUpdateVariableValue(\"<VALUE>\"),\n appwrite.WithUpdateVariableSecret(false),\n)\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::Functions;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n let client = Client::new();\n client.set_endpoint(\"https://<REGION>.cloud.appwrite.io/v1\"); // Your API Endpoint\n client.set_project(\"<YOUR_PROJECT_ID>\"); // Your project ID\n client.set_key(\"<YOUR_API_KEY>\"); // Your API key\n\n let functions = Functions::new(&client);\n\n let result = functions.update_variable(\n \"<FUNCTION_ID>\",\n \"<VARIABLE_ID>\",\n \"<KEY>\",\n Some(\"<VALUE>\"), // optional\n Some(false) // optional\n ).await?;\n\n let _ = result;\n\n Ok(())\n}\n```\n{% /multicode %}\n\n## Delete a variable {% #delete-variable %}\n\n{% multicode %}\n```server-nodejs\nimport { Client, Functions } from 'node-appwrite';\n\nconst client = new Client()\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<YOUR_PROJECT_ID>') // Your project ID\n .setKey('<YOUR_API_KEY>'); // Your API key\n\nconst functions = new Functions(client);\n\nconst result = await functions.deleteVariable({\n functionId: '<FUNCTION_ID>',\n variableId: '<VARIABLE_ID>'\n});\n```\n```server-deno\nimport { Client, Functions } from \"npm:node-appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<YOUR_PROJECT_ID>') // Your project ID\n .setKey('<YOUR_API_KEY>'); // Your API key\n\nconst functions = new Functions(client);\n\nconst result = await functions.deleteVariable({\n functionId: '<FUNCTION_ID>',\n variableId: '<VARIABLE_ID>'\n});\n```\n```server-php\n<?php\n\nuse Appwrite\\Client;\nuse Appwrite\\Services\\Functions;\n\n$client = (new Client())\n ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n ->setProject('<YOUR_PROJECT_ID>') // Your project ID\n ->setKey('<YOUR_API_KEY>'); // Your API key\n\n$functions = new Functions($client);\n\n$result = $functions->deleteVariable(\n functionId: '<FUNCTION_ID>',\n variableId: '<VARIABLE_ID>'\n);\n```\n```server-python\nfrom appwrite.client import Client\nfrom appwrite.services.functions import Functions\n\nclient = Client()\nclient.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\nclient.set_project('<YOUR_PROJECT_ID>') # Your project ID\nclient.set_key('<YOUR_API_KEY>') # Your API key\n\nfunctions = Functions(client)\n\nresult = functions.delete_variable(\n function_id = '<FUNCTION_ID>',\n variable_id = '<VARIABLE_ID>'\n)\n```\n```server-ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('<YOUR_PROJECT_ID>') # Your project ID\n .set_key('<YOUR_API_KEY>') # Your API key\n\nfunctions = Functions.new(client)\n\nresult = functions.delete_variable(\n function_id: '<FUNCTION_ID>',\n variable_id: '<VARIABLE_ID>'\n)\n```\n```server-dotnet\nusing Appwrite;\nusing Appwrite.Models;\nusing Appwrite.Services;\n\nClient client = new Client()\n .SetEndPoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .SetProject(\"<YOUR_PROJECT_ID>\") // Your project ID\n .SetKey(\"<YOUR_API_KEY>\"); // Your API key\n\nFunctions functions = new Functions(client);\n\nawait functions.DeleteVariable(\n functionId: \"<FUNCTION_ID>\",\n variableId: \"<VARIABLE_ID>\"\n);\n```\n```server-dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\n\nClient client = Client()\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<YOUR_PROJECT_ID>') // Your project ID\n .setKey('<YOUR_API_KEY>'); // Your API key\n\nFunctions functions = Functions(client);\n\nawait functions.deleteVariable(\n functionId: '<FUNCTION_ID>',\n variableId: '<VARIABLE_ID>',\n);\n```\n```server-kotlin\nimport io.appwrite.Client\nimport io.appwrite.coroutines.CoroutineCallback\nimport io.appwrite.services.Functions\n\nval client = Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<YOUR_PROJECT_ID>\") // Your project ID\n .setKey(\"<YOUR_API_KEY>\") // Your API key\n\nval functions = Functions(client)\n\nval response = functions.deleteVariable(\n functionId = \"<FUNCTION_ID>\",\n variableId = \"<VARIABLE_ID>\"\n)\n```\n```server-java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Functions;\n\nClient client = new Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<YOUR_PROJECT_ID>\") // Your project ID\n .setKey(\"<YOUR_API_KEY>\"); // Your API key\n\nFunctions functions = new Functions(client);\n\nfunctions.deleteVariable(\n \"<FUNCTION_ID>\", // functionId\n \"<VARIABLE_ID>\", // variableId\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(result);\n })\n);\n```\n```server-swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<YOUR_PROJECT_ID>\") // Your project ID\n .setKey(\"<YOUR_API_KEY>\") // Your API key\n\nlet functions = Functions(client)\n\nlet result = try await functions.deleteVariable(\n functionId: \"<FUNCTION_ID>\",\n variableId: \"<VARIABLE_ID>\"\n)\n```\n```server-go\npackage main\n\nimport (\n \"fmt\"\n \"github.com/appwrite/sdk-for-go/appwrite\"\n)\n\nclient := appwrite.NewClient(\n appwrite.WithEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\"),\n appwrite.WithProject(\"<YOUR_PROJECT_ID>\"),\n appwrite.WithKey(\"<YOUR_API_KEY>\"),\n)\n\nfunctions := appwrite.NewFunctions(client)\n\nresponse, error := functions.DeleteVariable(\n \"<FUNCTION_ID>\",\n \"<VARIABLE_ID>\",\n)\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::Functions;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n let client = Client::new();\n client.set_endpoint(\"https://<REGION>.cloud.appwrite.io/v1\"); // Your API Endpoint\n client.set_project(\"<YOUR_PROJECT_ID>\"); // Your project ID\n client.set_key(\"<YOUR_API_KEY>\"); // Your API key\n\n let functions = Functions::new(&client);\n\n functions.delete_variable(\n \"<FUNCTION_ID>\",\n \"<VARIABLE_ID>\"\n ).await?;\n\n Ok(())\n}\n```\n{% /multicode %}\n\n# Read variables in your function {% #read-variables %}\n\nOnce a variable is set, you can read it inside your function using your runtime language's standard environment lookup.\n\n{% multicode %}\n```server-nodejs\nexport default async ({ req, res, log }) => {\n return res.text(process.env.MY_VAR);\n}\n```\n```server-deno\nexport default async ({ req, res, log }) => {\n return res.text(Deno.env.get('MY_VAR'));\n}\n```\n```server-php\n<?php\n\nreturn function ($context) {\n return $context->res->text(getenv('MY_VAR'));\n};\n```\n```server-python\ndef main(context):\n return context.res.text(os.environ['MY_VAR'])\n```\n```server-ruby\ndef main(context)\n return context.res.text(ENV['MY_VAR'])\nend\n```\n```server-dotnet\nnamespace DotNetRuntime;\n\npublic class Handler {\n public async Task<RuntimeOutput> Main(RuntimeContext Context) {\n var myVar = Environment.GetEnvironmentVariable(\"MY_VAR\");\n return Context.Res.Text(myVar);\n }\n}\n```\n```server-dart\nimport 'dart:io';\nimport 'dart:async';\n\nFuture<dynamic> main(final context) async {\n return context.res.text(Platform.environment['MY_VAR']);\n}\n```\n```server-kotlin\nimport io.openruntimes.kotlin.RuntimeContext\nimport io.openruntimes.kotlin.RuntimeOutput\n\nclass Handler {\n suspend fun main(context: RuntimeContext): RuntimeOutput {\n return context.res.text(System.getenv(\"MY_VAR\"))\n }\n}\n```\n```server-java\nimport io.openruntimes.java.RuntimeContext;\nimport io.openruntimes.java.RuntimeOutput;\n\npublic class Handler {\n public RuntimeOutput main(RuntimeContext context) throws Exception {\n return context.res.text(System.getenv(\"MY_VAR\"));\n }\n}\n```\n```server-swift\nimport Foundation\n\nfunc main(context: RuntimeContext) async throws -> RuntimeOutput {\n return context.res.text(ProcessInfo.processInfo.environment[\"MY_VAR\"] ?? \"\")\n}\n```\n```server-go\npackage handler\n\nimport (\n \"os\"\n\n \"github.com/open-runtimes/types-for-go/v4/openruntimes\"\n)\n\nfunc Main(Context openruntimes.Context) openruntimes.Response {\n return Context.Res.Text(os.Getenv(\"MY_VAR\"))\n}\n```\n```server-rust\nuse openruntimes::{Context, Response};\nuse std::env;\n\npub fn main(context: Context) -> Response {\n context\n .res\n .text(env::var(\"MY_VAR\").unwrap_or_default(), None, None)\n}\n```\n{% /multicode %}\n\n# Appwrite-injected variables {% #appwrite-variables %}\n\nAppwrite 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.\n\n{% partial file=\"functions-env-vars.md\" /%}\n\nDuring 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.\n\n# Secret variables {% #secret-variables %}\n\nMark 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.\n\nYou 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.\n\n# Limits {% #limits %}\n\n| Field | Limit |\n|----------------|------------------------------------------------|\n| Variable ID | 36 characters, `a-z A-Z 0-9 . - _` |\n| Key | 255 characters |\n| Value | 8192 characters |"}, {"path": "docs/products/functions/examples", "title": "Examples", "description": "Accelerate your serverless development with Appwrite Functions examples. Access a library of code samples and use cases to jumpstart your projects.", "content": "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.\n\n{% section #currency-conversion step=1 title=\"Currency conversion API\" %}\n\nHere'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.\n\n## Prerequisites\n\n{% tabs %}\n{% tabsitem #node title=\"Node.js\" %}\nRun the following bash command to create a `package.json` file. This file is used to manage your Node.js project's dependencies.\n```bash\nnpm init -y\n```\nInstall the `undici` library. This library includes a `fetch` function that you can use to make HTTP requests.\n\n```bash\nnpm install undici\n```\nFinally, add `npm install` to your function's build commands in the Appwrite Console.\n{% /tabsitem %}\n\n{% tabsitem #php title=\"PHP\" %}\nRun the following bash command to create a `composer.json` file. This file is used to manage your PHP project's dependencies.\n\n```bash\ncomposer init -y\n```\n\nInstall the `guzzlehttp/guzzle` library. This library includes a `get` function that you can use to make HTTP requests.\n\n```bash\ncomposer require guzzlehttp/guzzle\n```\n\nFinally, add `composer install` to your function's build commands in the Appwrite Console.\n\n{% /tabsitem %}\n\n{% tabsitem #python title=\"Python\" %}\n\nRun the following bash command to create a `requirements.txt` file. This file is used to manage your Python project's dependencies.\n\n```bash\ntouch requirements.txt\n```\n\nInstall the `requests` library. This library includes a `get` function that you can use to make HTTP requests.\n\n```bash\necho \"requests\" >> requirements.txt\npip install -r requirements.txt\n```\n\nFinally, add `pip install -r requirements.txt` to your function's build commands in the Appwrite Console.\n\n{% /tabsitem %}\n{% tabsitem #dart title=\"Dart\" %}\n\nCreate a `pubspec.yaml` file with the following contents. This file is used to manage your Dart project's dependencies.\n\n```yaml\nname: appwrite_function\ndescription: Appwrite Function\nversion: 1.0.0\nenvironment:\n sdk: '>=2.12.0 <3.0.0'\n```\n\nInstall the `http` library. This library includes a `get` function that you can use to make HTTP requests.\n\n```bash\npub install http\n```\n\nFinally, add `pub get` to your function's build commands in the Appwrite Console.\n\n{% /tabsitem %}\n{% tabsitem #ruby title=\"Ruby\" %}\n\nCreate a `Gemfile` file with the following contents. This file is used to manage your Ruby project's dependencies.\n\n```ruby\nsource 'https://rubygems.org'\n```\n\nInstall the `httparty` library. This library includes a `get` function that you can use to make HTTP requests.\n\n```bash\necho \"gem 'httparty'\" >> Gemfile\nbundle install\n```\n\nFinally, add `bundle install` to your function's build commands in the Appwrite Console.\n{% /tabsitem %}\n\n{% tabsitem #rust title=\"Rust\" %}\n\nCreate a `Cargo.toml` file with the following contents. This file is used to manage your Rust project's dependencies.\n\n```toml\n[package]\nname = \"handler\"\nversion = \"0.1.0\"\nedition = \"2021\"\nrust-version = \"1.83\"\n\n[lib]\nname = \"handler\"\npath = \"lib.rs\"\n\n[dependencies]\nopenruntimes = { version = \"1.0\", package = \"openruntimes-types-for-rust\" }\nserde_json = \"1.0\"\nureq = { version = \"2.10\", features = [\"json\"] }\nappwrite = \"0.4\"\ntokio = { version = \"1\", features = [\"rt\"] }\nurlencoding = \"2.1\"\n```\n\n`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.\n{% /tabsitem %}\n\n{% /tabs %}\n\n\n## Code\n{% multicode %}\n```server-nodejs\nimport { fetch } from 'undici';\n\nexport default async function ({ req, res }) {\n if (req.path === '/eur') {\n const amountInEuros = Number(req.query.amount);\n const response = await fetch('https://api.exchangerate.host/latest?base=EUR&symbols=USD');\n const data = await response.json();\n const amountInDollars = amountInEuros * data.rates.USD;\n return res.text(amountInDollars.toString());\n }\n\n if (req.path === '/inr') {\n const amountInRupees = Number(req.query.amount);\n const response = await fetch('https://api.exchangerate.host/latest?base=INR&symbols=USD');\n const data = await response.json();\n const amountInDollars = amountInRupees * data.rates.USD;\n return res.text(amountInDollars.toString());\n }\n\n return res.text('Invalid path');\n};\n```\n```php\n<?php\n\nrequire(__DIR__ . '/../vendor/autoload.php');\n\nuse Appwrite\\Client;\nuse Appwrite\\Exception;\nuse Appwrite\\Services\\Database;\nuse GuzzleHttp\\Client as GuzzleClient;\n\nreturn function ($context) {\n $client = new GuzzleClient();\n\n if ($context->req->path === '/eur') {\n $amountInEuros = floatval($context->req->query['amount']);\n $response = $client->get('https://api.exchangerate.host/latest?base=EUR&symbols=USD');\n $data = $response->json();\n $amountInDollars = $amountInEuros * $data['rates']['USD'];\n return $context->res->text(strval($amountInDollars));\n }\n\n if ($context->req->path === '/inr') {\n $amountInRupees = floatval($context->req->query['amount']);\n $response = $client->get('https://api.exchangerate.host/latest?base=INR&symbols=USD');\n $data = $response->json();\n $amountInDollars = $amountInRupees * $data['rates']['USD'];\n return $context->res->text(strval($amountInDollars));\n }\n\n return $context->res->text('Invalid path');\n};\n```\n```python\nimport requests\n\ndef main(context):\n if context.req.path == '/eur':\n amount_in_euros = float(context.req.query['amount'])\n response = requests.get('https://api.exchangerate.host/latest?base=EUR&symbols=USD')\n data = response.json()\n amount_in_dollars = amount_in_euros * data['rates']['USD']\n return context.res.text(str(amount_in_dollars))\n\n if context.req.path == '/inr':\n amount_in_rupees = float(context.req.query['amount'])\n response = requests.get('https://api.exchangerate.host/latest?base=INR&symbols=USD')\n data = response.json()\n amount_in_dollars = amount_in_rupees * data['rates']['USD']\n return context.res.text(str(amount_in_dollars))\n\n return 'Invalid path'\n```\n```dart\nimport 'dart:async';\nimport 'package:http/http.dart' as http;\nimport 'dart:io';\n\nFuture<dynamic> main(final context) async {\n if (context.req.path == '/eur') {\n final amountInEuros = double.parse(context.req.query['amount'])\n final response = await http.get(Uri.parse('https://api.exchangerate.host/latest?base=EUR&symbols=USD'));\n final data = json.decode(response.body);\n final amountInDollars = amountInEuros * data['rates']['USD'];\n return context.res.text(amountInDollars.toString());\n }\n\n if (context.req.path == '/inr') {\n final amountInRupees = double.parse(context.req.query['amount'])\n final response = await http.get(Uri.parse('https://api.exchangerate.host/latest?base=INR&symbols=USD'));\n final data = json.decode(response.body);\n final amountInDollars = amountInRupees * data['rates']['USD'];\n return context.res.text(amountInDollars.toString());\n }\n\n return 'Invalid path';\n}\n```\n```ruby\nrequire 'httparty'\n\ndef main(context)\n if context.req.path == '/eur'\n amount_in_euros = context.req.query['amount'].to_f\n response = HTTParty.get('https://api.exchangerate.host/latest?base=EUR&symbols=USD')\n data = JSON.parse(response.body)\n amount_in_dollars = amount_in_euros * data['rates']['USD']\n return context.res.text(amount_in_dollars.to_s)\n end\n\n if context.req.path == '/inr'\n amount_in_rupees = context.req.query['amount'].to_f\n response = HTTParty.get('https://api.exchangerate.host/latest?base=INR&symbols=USD')\n data = JSON.parse(response.body)\n amount_in_dollars = amount_in_rupees * data['rates']['USD']\n return context.res.text(amount_in_dollars.to_s)\n end\n\n return 'Invalid path'\nend\n```\n```rust\nuse openruntimes::{Context, Response};\nuse serde_json::Value;\n\npub fn main(context: Context) -> Response {\n let path = context.req.path.as_str();\n\n if path == \"/eur\" || path == \"/inr\" {\n let base = if path == \"/eur\" { \"EUR\" } else { \"INR\" };\n let amount: f64 = context\n .req\n .query\n .get(\"amount\")\n .and_then(|s| s.parse().ok())\n .unwrap_or(0.0);\n\n let url = format!(\n \"https://api.exchangerate.host/latest?base={}&symbols=USD\",\n base\n );\n\n let data: Value = ureq::get(&url)\n .call()\n .ok()\n .and_then(|r| r.into_json().ok())\n .unwrap_or(Value::Null);\n\n let rate = data\n .get(\"rates\")\n .and_then(|r| r.get(\"USD\"))\n .and_then(|v| v.as_f64())\n .unwrap_or(0.0);\n\n return context.res.text((amount * rate).to_string(), None, None);\n }\n\n context.res.text(\"Invalid path\", None, None)\n}\n```\n{% /multicode %}\n\n{% /section %}\n\n{% section #voting-system step=2 title=\"Voting system\" %}\n\nHere'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.\n\n## Prerequisites\n\nCreate a Topics table with the following columns:\n\n| Name | Type | Description |\n|---------------|--------|----------------------------------|\n| `title` | string | The name of the topic |\n| `description` | string | Long form description of the topic|\n\nCreate a Votes table with the following columns:\n\n| Name | Type | Description |\n|---------------|--------|------------------------------------------|\n| `userId` | string | The ID of the user who cast the vote |\n| `topicId` | string | The ID of the topic that was voted on |\n| `vote` | string | The vote cast by the user. Must be either \"yes\" or \"no\" |\n\n\n## Code\n{% multicode %}\n```server-nodejs\nimport { Client, TablesDB, Query, ID } from 'node-appwrite';\n\nexport default async function ({ req, res }) {\n const vote = {\n userId: req.query.userId,\n topicId: req.query.topicId,\n vote: req.query.vote\n };\n\n if (vote.vote !== 'yes' && vote.vote !== 'no') {\n return res.json({ ok: false, message: 'You must vote yes or no.' }, 400);\n }\n\n // Set project and set API key\n const client = new Client();\n client\n .setProject(process.env.APPWRITE_FUNCTION_PROJECT_ID)\n .setKey(req.headers['x-appwrite-key']);\n\n const tablesDB = new TablesDB(client);\n\n const existingVotes = await tablesDB.listRows({\n databaseId: '<DATABASE_ID>',\n tableId: '<VOTES_TABLE_ID>',\n queries: [\n Query.equals('userId', vote.userId),\n Query.equals('topicId', vote.topicId)\n ]\n });\n\n if (existingVotes.total > 0) {\n return res.json({ ok: false, message: 'You have already voted on this topic.' }, 400);\n }\n\n const voteDocument = await tablesDB.createRow({\n databaseId: '<DATABASE_ID>',\n tableId: '<VOTES_TABLE_ID>',\n rowId: ID.unique(),\n data: {\n userId: vote.userId,\n topicId: vote.topicId,\n vote: vote.vote,\n }\n });\n\n return res.json({ ok: true, message: 'Vote cast.', vote: voteDocument });\n};\n```\n```python\nfrom appwrite.client import Client\nfrom appwrite.services.tables_db import TablesDB\nfrom appwrite.query import Query\nimport os\n\ndef main(context):\n vote = {\n 'userId': context.req.query['userId'],\n 'topicId': context.req.query['topicId'],\n 'vote': context.req.query['vote']\n }\n\n if vote['vote'] != 'yes' and vote['vote'] != 'no':\n return context.res.json({'ok': False, 'message': 'You must vote yes or no.'}, 400)\n\n # Set project and set API key\n client = (\n Client()\n .set_project(os.environ['APPWRITE_FUNCTION_PROJECT_ID'])\n .set_key(context.req.headers['x-appwrite-key'])\n )\n\n tablesDB = TablesDB(client)\n\n existing_votes = tablesDB.list_rows('<VOTES_TABLE_ID>', [\n Query.equals('userId', vote['userId']),\n Query.equals('topicId', vote['topicId'])\n ])\n\n if existing_votes['total'] > 0:\n return context.res.json({\n 'ok': False,\n 'message': 'You have already voted on this topic.'\n }, 400)\n\n vote_row = database.create_row('<VOTES_TABLE_ID>', {\n 'userId': vote['userId'],\n 'topicId': vote['topicId'],\n 'vote': vote['vote'],\n })\n\n return context.res.json({'ok': True, 'message': 'Vote cast.', 'vote': vote_row})\n```\n```php\n<?php\n\nrequire(__DIR__ . '/../vendor/autoload.php');\n\nuse Appwrite\\Client;\nuse Appwrite\\Exception;\nuse Appwrite\\Services\\Database;\nuse Appwrite\\Query;\n\nreturn function ($context) {\n $vote = [\n 'userId' => $context->req->query['userId'],\n 'topicId' => $context->req->query['topicId'],\n 'vote' => $context->req->query['vote']\n ];\n\n if ($vote['vote'] !== 'yes' && $vote['vote'] !== 'no') {\n return $context->res->json(['ok' => false, 'message' => 'You must vote yes or no.'], 400);\n }\n\n // Set project and set API key\n $client = new Client();\n $client\n ->setProject(getenv('APPWRITE_FUNCTION_PROJECT_ID'))\n ->setKey($context->req->headers['x-appwrite-key']);\n\n $tablesDB = new TablesDB($client);\n\n $existingVotes = $database->listRows('<VOTES_TABLE_ID>', [\n Query->equal('userId', $vote['userId']),\n Query->equal('topicId', $vote['topicId'])\n ]);\n\n if ($existingVotes['total'] > 0) {\n return $context->res->json([\n 'ok' => false,\n 'message' => 'You have already voted on this topic.'\n ], 400);\n }\n\n $voteDocument = $database->createRow('<VOTES_TABLE_ID>', [\n 'userId' => $vote['userId'],\n 'topicId' => $vote['topicId'],\n 'vote' => $vote['vote'],\n ]);\n\n return $context->res->json([\n 'ok' => true,\n 'message' => 'Vote cast.',\n 'vote' => $voteDocument\n ]);\n};\n```\n```dart\nimport 'dart:async';\nimport 'package:dart_appwrite/dart_appwrite.dart';\nimport 'dart:io';\n\nFuture main(final context) async {\n final vote = {\n 'userId': context.req.query['userId'],\n 'topicId': context.req.query['topicId'],\n 'vote': context.req.query['vote']\n };\n\n if (vote['vote'] != 'yes' && vote['vote'] != 'no') {\n return context.res.json({'ok': false, 'message': 'You must vote yes or no.'}, 400);\n }\n\n // Set project and set API key\n final client = Client()\n .setProject(Platform.environment['APPWRITE_FUNCTION_PROJECT_ID'])\n .setKey(context.req.headers['x-appwrite-key']);\n\n final tablesDB = TablesDB(client);\n\n final existingVotes = await tablesDB.listRows('<VOTES_TABLE_ID>', [\n Query.equals('userId', vote['userId']),\n Query.equals('topicId', vote['topicId'])\n ]);\n\n if (existingVotes['total'] > 0) {\n return context.res.json({\n 'ok': false,\n 'message': 'You have already voted on this topic.'\n }, 400);\n }\n\n final voteDocument = await database.createRow('<VOTES_TABLE_ID>', {\n 'userId': vote['userId'],\n 'topicId': vote['topicId'],\n 'vote': vote['vote'],\n });\n\n return context.res.json({\n 'ok': true,\n 'message': 'Vote cast.',\n 'vote': voteDocument\n });\n}\n```\n```ruby\nrequire \"appwrite\"\n\ndef main(context)\n vote = {\n 'userId' => context.req.query['userId'],\n 'topicId' => context.req.query['topicId'],\n 'vote' => context.req.query['vote']\n }\n\n if vote['vote'] != 'yes' and vote['vote'] != 'no'\n return context.res.json({'ok': false, 'message': 'You must vote yes or no.'}, 400)\n end\n\n # Set project and set API key\n client = Appwrite::Client.new()\n client\n .set_project(ENV['APPWRITE_FUNCTION_PROJECT_ID'])\n .set_key(context.req.headers['x-appwrite-key'])\n\n tablesDB = Appwrite::TablesDB.new(client)\n\n existing_votes = tablesDB.list_rows('<DATABASE_ID>', '<VOTES_TABLE_ID>', [\n Appwrite::Query.equal('userId', vote['userId']),\n Appwrite::Query.equal('topicId', vote['topicId'])\n ])\n\n if existing_votes['total'] > 0\n return context.res.json({\n 'ok': false,\n 'message': 'You have already voted on this topic.'\n }, 400)\n end\n\n vote_row = database.create_row('<VOTES_TABLE_ID>', {\n 'userId': vote['userId'],\n 'topicId': vote['topicId'],\n 'vote': vote['vote'],\n })\n\n return context.res.json({\n 'ok': true,\n 'message': 'Vote cast.',\n 'vote': vote_row\n })\nend\n```\n```rust\nuse appwrite::error::AppwriteError;\nuse appwrite::id::ID;\nuse appwrite::query::Query;\nuse appwrite::services::tables_db::TablesDB;\nuse appwrite::Client;\nuse openruntimes::{Context, Response};\nuse serde_json::json;\nuse std::env;\n\npub fn main(context: Context) -> Response {\n let user_id = context.req.query.get(\"userId\").cloned().unwrap_or_default();\n let topic_id = context.req.query.get(\"topicId\").cloned().unwrap_or_default();\n let vote = context.req.query.get(\"vote\").cloned().unwrap_or_default();\n\n if vote != \"yes\" && vote != \"no\" {\n return context.res.json(\n json!({ \"ok\": false, \"message\": \"You must vote yes or no.\" }),\n Some(400),\n None,\n );\n }\n\n // Set project and set API key\n let client = Client::new()\n .set_endpoint(env::var(\"APPWRITE_FUNCTION_API_ENDPOINT\").unwrap_or_default())\n .set_project(env::var(\"APPWRITE_FUNCTION_PROJECT_ID\").unwrap_or_default())\n .set_key(\n context\n .req\n .headers\n .get(\"x-appwrite-key\")\n .cloned()\n .unwrap_or_default(),\n );\n\n let tables_db = TablesDB::new(&client);\n\n let runtime = tokio::runtime::Builder::new_current_thread()\n .enable_all()\n .build()\n .unwrap();\n\n let user_id_q = user_id.clone();\n let topic_id_q = topic_id.clone();\n\n let result: Result<Option<serde_json::Value>, AppwriteError> = runtime.block_on(async move {\n let existing = tables_db\n .list_rows(\n \"<DATABASE_ID>\",\n \"<VOTES_TABLE_ID>\",\n Some(vec![\n Query::equal(\"userId\", json!(user_id_q)).to_string(),\n Query::equal(\"topicId\", json!(topic_id_q)).to_string(),\n ]),\n None,\n None,\n None,\n )\n .await?;\n\n if existing.total > 0 {\n return Ok(None);\n }\n\n let row = tables_db\n .create_row(\n \"<DATABASE_ID>\",\n \"<VOTES_TABLE_ID>\",\n ID::unique(),\n json!({\n \"userId\": user_id,\n \"topicId\": topic_id,\n \"vote\": vote,\n }),\n None,\n None,\n )\n .await?;\n\n Ok(Some(json!(row)))\n });\n\n match result {\n Ok(Some(row)) => context.res.json(\n json!({ \"ok\": true, \"message\": \"Vote cast.\", \"vote\": row }),\n None,\n None,\n ),\n Ok(None) => context.res.json(\n json!({ \"ok\": false, \"message\": \"You have already voted on this topic.\" }),\n Some(400),\n None,\n ),\n Err(e) => {\n context.error(format!(\"Vote failed: {}\", e));\n context.res.text(\"Vote failed\", Some(500), None)\n }\n }\n}\n```\n{% /multicode %}\nUse the function by navigating to the function URL in the browser.\nThe URL should contain the required parameters.\nFor example, `<YOUR_FUNCTION_URL>/?userId=<USER_ID>&topicId=<TOPIC_ID>&vote=yes` to cast a vote.\n{% /section %}\n\n{% section #form-submission step=3 title=\"HTML contact form\" %}\nHere's a simple form page that handles form submissions, and can be used to store a user's message in a table.\nThe 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.\n\n## Prerequisites\nCreate a Messages table with the following columns:\n\n| Name | Type | Description |\n|------------|--------|----------------------------------|\n| `name` | string | The name of the message author |\n| `email` | string | The email of the message author |\n| `content` | string | The content of the message |\n\n## Code\n\n{% multicode %}\n\n```server-nodejs\nimport { Client, TablesDB, Query, ID } from 'node-appwrite';\nimport querystring from 'node:querystring';\n\nconst html = `<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\">\n <title>Contact Form\n \n \n
\n \n \n \n \n
\n \n`\n\nexport default async function ({ req, res }) {\n if (req.method === 'GET') {\n return res.text(html, 200, {'content-type': 'text/html'});\n }\n\n if (req.method === 'POST' && req.headers['content-type'] === 'application/x-www-form-urlencoded') {\n const formData = querystring.parse(req.body);\n\n const message = {\n name: formData.name,\n email: formData.email,\n content: formData.content\n };\n\n // Set project and set API key\n const client = new Client()\n .setProject(process.env.APPWRITE_FUNCTION_PROJECT_ID)\n .setKey(req.headers['x-appwrite-key']);\n\n const tablesDB = new TablesDB(client);\n const row = await tablesDB.createRow({\n databaseId: '',\n tableId: '',\n rowId: ID.unique(),\n data: message\n });\n\n return res.text(\"Message sent\");\n }\n\n return res.text('Not found', 404);\n}\n```\n\n```python\nfrom appwrite.client import Client\nfrom appwrite.services.tables_db import TablesDB\nfrom appwrite.query import Query\nfrom urllib.parse import parse_qs\nimport os\n\nhtml = '''\n\n \n \n Contact Form\n \n \n
\n \n \n \n \n
\n \n\n'''\n\ndef main(context):\n if context.req.method == 'GET':\n return context.res.text(html, 200, {'content-type': 'text/html'})\n\n if context.req.method == 'POST' and context.req.headers['content-type'] == 'application/x-www-form-urlencoded':\n formData = parse_qs(context.req.body)\n\n message = {\n 'name': formData['name'][0],\n 'email': formData['email'][0],\n 'content': formData['content'][0]\n }\n\n # Set project and set API key\n client = (\n Client()\n .set_project(os.environ[\"APPWRITE_FUNCTION_PROJECT_ID\"])\n .set_key(context.req.headers[\"x-appwrite-key\"])\n )\n\n tablesDB = TablesDB(client)\n row = tablesDB.create_row('', '', ID.unique(), message)\n\n return context.res.text(\"Message sent\")\n\n return context.res.text('Not found', 404)\n```\n\n```php\n\n\n \n \n Contact Form\n \n \n
\n \n \n \n \n
\n \n';\n\nreturn function ($context) {\n global $html;\n\n if ($context->req->method === 'GET') {\n return $context->res->text($html, 200, ['content-type' => 'text/html']);\n }\n\n if ($context->req->method === 'POST' && $context->req->headers['content-type'] === 'application/x-www-form-urlencoded') {\n \\parse_str($context->req->body, $formData);\n\n $message = [\n 'name' => $formData['name'],\n 'email' => $formData['email'],\n 'content' => $formData['content']\n ];\n\n // Set project and set API key\n $client = (new Client())\n ->setProject(getenv('APPWRITE_FUNCTION_PROJECT_ID'))\n ->setKey($context->req->headers['x-appwrite-key']);\n\n $tablesDB = new TablesDB($client);\n $row = $tablesDB->createRow('', '', ID::unique(), $message);\n\n return $context->res->text(\"Message sent\");\n }\n\n return $context->res->text('Not found', 404);\n};\n```\n\n```ruby\nrequire \"appwrite\"\n\nhtml = '''\n\n \n \n Contact Form\n \n \n
\n \n \n \n \n
\n \n\n'''\n\ndef main(context)\n if context.req.method == 'GET'\n return context.res.text(html, 200, {'content-type': 'text/html'})\n end\n\n if context.req.method == 'POST' and context.req.headers['content-type'] == 'application/x-www-form-urlencoded'\n formData = URI.decode_www_form(context.req.body).to_h\n\n message = {\n 'name' => formData['name'],\n 'email' => formData['email'],\n 'content' => formData['content']\n }\n\n # Set project and set API key\n client = Appwrite::Client.new\n .set_project(ENV['APPWRITE_FUNCTION_PROJECT_ID'])\n .set_key(context.req.headers['x-appwrite-key'])\n\n tablesDB = Appwrite::TablesDB.new(client)\n row = tablesDB.create_row('', '', ID.unique(), message)\n\n return context.res.text(\"Message sent\")\n end\n\n return context.res.text('Not found', 404)\nend\n```\n\n```dart\nimport 'dart:async';\nimport 'package:dart_appwrite/dart_appwrite.dart';\n\nFuture main(final context) async {\n final html = '''\n\n \n \n Contact Form\n \n \n
\n \n \n \n \n
\n \n\n''';\n\n if (context.req.method == 'GET') {\n return context.res.text(html, 200, {'content-type': 'text/html'});\n }\n\n if (context.req.method == 'POST' && context.req.headers['content-type'] == 'application/x-www-form-urlencoded') {\n final formData = Uri.splitQueryString(context.req.body);\n\n final message = {\n 'name': formData['name'],\n 'email': formData['email'],\n 'content': formData['content']\n };\n\n // Set project and set API key\n final client = Client()\n .setProject(Platform.environment['APPWRITE_FUNCTION_PROJECT_ID'])\n .setKey(context.req.headers['x-appwrite-key']);\n\n final tablesDB = TablesDB(client);\n final row = await tablesDB.createRow('', '', ID.unique(), message);\n\n return context.res.text(\"Message sent\");\n }\n\n return context.res.text('Not found', 404);\n}\n```\n\n```rust\nuse appwrite::id::ID;\nuse appwrite::services::tables_db::TablesDB;\nuse appwrite::Client;\nuse openruntimes::{Context, Response};\nuse serde_json::json;\nuse std::collections::HashMap;\nuse std::env;\n\nconst HTML: &str = r#\"\n\n \n \n Contact Form\n \n \n
\n \n \n \n \n
\n \n\"#;\n\nfn parse_form(body: &str) -> HashMap {\n body.split('&')\n .filter_map(|pair| {\n let mut split = pair.splitn(2, '=');\n // application/x-www-form-urlencoded encodes spaces as `+`, so\n // replace `+` before percent-decoding any `%xx` sequences.\n let key = split.next()?.replace('+', \" \");\n let value = split.next().unwrap_or(\"\").replace('+', \" \");\n Some((\n urlencoding::decode(&key).ok()?.into_owned(),\n urlencoding::decode(&value).ok()?.into_owned(),\n ))\n })\n .collect()\n}\n\npub fn main(context: Context) -> Response {\n if context.req.method == \"GET\" {\n let mut headers = HashMap::new();\n headers.insert(\"content-type\".to_string(), \"text/html\".to_string());\n return context.res.text(HTML, Some(200), Some(headers));\n }\n\n let content_type = context\n .req\n .headers\n .get(\"content-type\")\n .map(|s| s.as_str())\n .unwrap_or(\"\");\n\n if context.req.method == \"POST\" && content_type == \"application/x-www-form-urlencoded\" {\n let body = context.req.body_text();\n let form = parse_form(&body);\n\n let client = Client::new()\n .set_endpoint(env::var(\"APPWRITE_FUNCTION_API_ENDPOINT\").unwrap_or_default())\n .set_project(env::var(\"APPWRITE_FUNCTION_PROJECT_ID\").unwrap_or_default())\n .set_key(\n context\n .req\n .headers\n .get(\"x-appwrite-key\")\n .cloned()\n .unwrap_or_default(),\n );\n\n let tables_db = TablesDB::new(&client);\n\n let runtime = tokio::runtime::Builder::new_current_thread()\n .enable_all()\n .build()\n .unwrap();\n\n let result = runtime.block_on(async move {\n tables_db\n .create_row(\n \"\",\n \"\",\n ID::unique(),\n json!({\n \"name\": form.get(\"name\").cloned().unwrap_or_default(),\n \"email\": form.get(\"email\").cloned().unwrap_or_default(),\n \"content\": form.get(\"content\").cloned().unwrap_or_default(),\n }),\n None,\n None,\n )\n .await\n });\n\n return match result {\n Ok(_) => context.res.text(\"Message sent\", None, None),\n Err(e) => {\n context.error(format!(\"Failed to save message: {}\", e));\n context.res.text(\"Failed to save message\", Some(500), None)\n }\n };\n }\n\n context.res.text(\"Not found\", Some(404), None)\n}\n```\n\n{% /multicode %}\n\nUse the function by navigating to the function URL in the browser. Submit the form to store the message in the table.\n{% /section %}"}, {"path": "docs/products/functions/execute", "title": "Execution", "description": "Understand serverless function execution in Appwrite. Explore how triggers, events, and data flow enable dynamic execution of your code.", "content": "Appwrite Functions can be executed in several ways.\nExecutions can be invoked through the Appwrite SDK and visiting its REST endpoint. Functions can also be triggered by events and scheduled executions.\nHere are all the different ways to consume your Appwrite Functions.\n\n# Execution modes {% #execution-modes %}\n\nAppwrite Functions support two execution modes: **synchronous** and **asynchronous**.\n\n## Synchronous executions {% #synchronous-executions %}\n\nSynchronous 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.\n\nSynchronous executions are created via:\n- The [Create execution](/docs/references/cloud/client-web/functions#createExecution) endpoint where the `async` parameter is `false`\n- Requests to custom or auto-generated [domains](/docs/products/functions/execute#domains)\n\nSynchronous executions:\n- Return response bodies and headers directly to the client\n- Have a **30-second hard timeout limit** to discourage slow API calls that cause poor user experience in apps\n- Are ideal for short-running operations where you need response data quickly\n\n## Asynchronous executions {% #asynchronous-executions %}\n\nAsynchronous executions are added to a queue and processed by the function worker as background jobs.\n\nAsynchronous executions are created via:\n- The [Create execution](/docs/references/cloud/client-web/functions#createExecution) endpoint where the `async` parameter is `true`\n- Event triggers (when functions are triggered by [platform events](/docs/apis/events))\n- Scheduled executions (cron jobs or delayed executions)\n\nAsynchronous executions:\n- Have no timeout limitations beyond your function's configured timeout\n- Are ideal for background processing and event-driven workflows\n\n{% info title=\"Response body storage\" %}\nResponse bodies and headers are not stored anywhere, so they are only ever returned via synchronous executions.\n{% /info %}\n\n# Domains {% #domains %}\nYou can execute a function through HTTP requests, using a browser or by sending an HTTP request.\n1. In the Appwrite Console's sidebar, click **Functions**.\n1. 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.\n1. 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).\n\n```bash\nhttps://64d4d22db370ae41a32e.fra.appwrite.run\n```\n\nWhen requests are made to this domain, whether through a browser or through an HTTP request,\nthe request information like request URL, request headers, and request body will be passed to the function.\n\n```bash\ncurl -X POST https://64d4d22db370ae41a32e.fra.appwrite.run \\\n-H \"X-Custom-Header: 123\" \\\n-H \"x-appwrite-user-jwt: \" \\\n-H \"Content-Type: application/json\" \\\n-d '{\"data\":\"this is json data\"}'\n```\n\nNotice how a `x-appwrite-user-jwt` header is passed in the request, you will use this to authenticate users.\n[Learn more about JWTs](/docs/products/auth/jwt).\n\nThis unlocks the ability for you to develop custom HTTP endpoints with Appwrite Functions.\nIt also allows accepting incoming webhooks for handling online payments, hosting social platform bots, and much more.\n\n# SDK {% #sdk %}\n\nYou can invoke your Appwrite Functions directly from the [Appwrite SDKs](/docs/sdks).\n\n{% tabs %}\n{% tabsitem #client title=\"Client SDKs\" %}\n\n{% multicode %}\n```client-web\nimport { Client, Functions } from \"appwrite\";\n\nconst client = new Client();\n\nconst functions = new Functions(client);\n\nclient\n .setProject('') // Your project ID\n;\n\nconst promise = functions.createExecution({\n functionId: '',\n body: '', // optional\n async: false, // optional\n xpath: '', // optional\n method: 'GET', // optional\n headers: {} // optional\n});\n\npromise.then(function (response) {\n console.log(response); // Success\n}, function (error) {\n console.log(error); // Failure\n});\n```\n```dart\nimport 'package:appwrite/appwrite.dart';\n\nvoid main() { // Init SDK\n Client client = Client();\n Functions functions = Functions(client);\n\n client\n .setProject('') // Your project ID\n ;\n Future result = functions.createExecution(\n functionId: '',\n body: '', // optional\n xasync: false, // optional\n path: '', // optional\n method: 'GET', // optional\n headers: {}, // optional\n );\n\n result\n .then((response) {\n print(response); // Success\n }).catchError((error) {\n print(error.response); // Failure\n });\n}\n```\n```swift\nimport Appwrite\n\nlet client = Client()\n .setProject(\"\") // Your project ID\n\nlet functions = Functions(client)\n\nlet execution = try await functions.createExecution(\n functionId: \"\",\n body: \"\", // optional\n async: xfalse, // optional\n path: \"\", // optional\n method: \"GET\", // optional\n headers: [:] // optional\n)\n```\n```kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Functions\n\nval client = Client(context)\n .setProject(\"\") // Your project ID\n\nval functions = Functions(client)\n\nval response = functions.createExecution(\n functionId = \"\",\n body = \"\", // optional\n async = false, // optional\n path = \"\", // optional\n method = \"GET\", // optional\n headers = mapOf( \"a\" to \"b\" ) // optional\n)\n```\n```java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Functions;\n\nClient client = new Client(context)\n .setProject(\"\"); // Your project ID\n\nFunctions functions = new Functions(client);\n\nfunctions.createExecution(\n \"\", // functionId\n \"\", // body (optional)\n false, // async (optional)\n \"\", // path (optional)\n \"GET\", // method (optional)\n mapOf( \"a\" to \"b\" ) // headers (optional)\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n Log.d(\"Appwrite\", result.toString());\n })\n);\n```\n{% /multicode %}\n\n{% /tabsitem %}\n\n{% tabsitem #server title=\"Server SDKs\" %}\n{% multicode %}\n```server-nodejs\nconst sdk = require('node-appwrite');\n\n// Init SDK\nconst client = new sdk.Client();\n\nconst functions = new sdk.Functions(client);\n\nclient\n .setProject('') // Your project ID\n;\n\nconst promise = functions.createExecution({\n functionId: '',\n body: '', // optional\n async: false, // optional\n path: '', // optional\n method: 'GET', // optional\n headers: {} // optional\n});\n\npromise.then(function (response) {\n console.log(response); // Success\n}, function (error) {\n console.log(error); // Failure\n});\n```\n```deno\nimport * as sdk from \"npm:node-appwrite\";\n\n// Init SDK\nlet client = new sdk.Client();\n\nlet functions = new sdk.Functions(client);\n\nclient\n .setProject('') // Your project ID\n;\n\nconst promise = functions.createExecution({\n functionId: '',\n body: '', // optional\n async: false, // optional\n xpath: '', // optional\n method: 'GET', // optional\n headers: {} // optional\n});\n\npromise.then(function (response) {\n console.log(response); // Success\n}, function (error) {\n console.log(error); // Failure\n});\n```\n```go\npackage main\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/appwrite/sdk-for-go/appwrite\"\n)\n\nfunc main() {\n\tclient := appwrite.NewClient(\n\t\tappwrite.WithProject(\"\"),\n\t)\n\n\tfunctions := appwrite.NewFunctions(client)\n\n\texecution, err := functions.CreateExecution(\n\t\t\"\", // functionId\n\t\tfunctions.WithCreateExecutionBody(\"\"), // body (optional)\n\t\tfunctions.WithCreateExecutionAsync(false), // async (optional)\n\t\tfunctions.WithCreateExecutionPath(\"\"), // path (optional)\n\t\tfunctions.WithCreateExecutionMethod(\"GET\"), // method (optional)\n\t\tfunctions.WithCreateExecutionHeaders(map[string]interface{}{})) // headers (optional)\n\n\tfmt.Println(execution)\n\n\tif err != nil {\n\t\tfmt.Println(err)\n\t}\n}\n```\n```php\nsetProject('') // Your project ID\n;\n\n$functions = new Functions($client);\n\n$result = $functions->createExecution(\n functionId: '',\n body: '', // optional\n async: false, // optional\n path: '', // optional\n method: 'GET', // optional\n headers: [] // optional\n);\n```\n```python\nfrom appwrite.client import Client\nfrom appwrite.services.functions import Functions\n\nclient = Client()\n\n(client\n .set_project('') # Your project ID\n)\n\nfunctions = Functions(client)\n\nresult = functions.create_execution(\n function_id = '',\n body = '', # optional\n async = False, # optional\n path = '', # optional\n method = 'GET', # optional\n headers = {} # optional\n)\n```\n```ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_project('') # Your project ID\n\nfunctions = Functions.new(client)\n\nresponse = functions.create_execution(\n function_id: '',\n body: '', # optional\n async: false, # optional\n path: '', # optional\n method: 'GET', # optional\n headers: {} # optional\n)\n\nputs response.inspect\n```\n```csharp\nusing Appwrite;\nusing Appwrite.Services;\nusing Appwrite.Models;\n\nvar client = new Client()\n .SetProject(\"\"); // Your project ID\n\nvar functions = new Functions(client);\n\nExecution result = await functions.CreateExecution(\n functionId: \"\"\n body: \"\" // optional\n async: false // optional\n path: \"\" // optional\n method: \"GET\" // optional\n headers: [object]); // optional\n```\n```dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\n\nvoid main() { // Init SDK\n Client client = Client();\n Functions functions = Functions(client);\n\n client\n .setProject('') // Your project ID\n ;\n\n Future result = functions.createExecution(\n functionId: '',\n body: '', // optional\n xasync: false, // optional\n path: '', // optional\n method: 'GET', // optional\n headers: {}, // optional\n );\n\n result\n .then((response) {\n print(response); // Success\n }).catchError((error) {\n print(error.response); // Failure\n });\n}\n```\n```kotlin\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Functions;\n\nClient client = new Client()\n .setProject(\"\"); // Your project ID\n\nFunctions functions = new Functions(client);\n\nfunctions.createExecution(\n \"\", // functionId\n \"\", // body (optional)\n false, // async (optional)\n \"\", // path (optional)\n \"GET\", // method (optional)\n mapOf( \"a\" to \"b\" ) // headers (optional)\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(result);\n })\n);\n```\n```java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Functions;\n\nClient client = new Client()\n .setProject(\"\"); // Your project ID\n\nFunctions functions = new Functions(client);\n\nfunctions.createExecution(\n \"\", // functionId\n \"\", // body (optional)\n false, // async (optional)\n \"\", // path (optional)\n \"GET\", // method (optional)\n mapOf( \"a\" to \"b\" ) // headers (optional)\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(result);\n })\n);\n```\n```swift\nimport Appwrite\n\nlet client = Client()\n .setProject(\"\") // Your project ID\n\nlet functions = Functions(client)\n\nlet execution = try await functions.createExecution(\n functionId: \"\",\n body: \"\", // optional\n async: xfalse, // optional\n path: \"\", // optional\n method: \"GET\", // optional\n headers: [:] // optional\n)\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::functions::Functions;\nuse appwrite::enums::execution_method::ExecutionMethod;\nuse serde_json::json;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box> {\n let client = Client::new()\n .set_project(\"\");\n\n let functions = Functions::new(&client);\n\n let execution = functions.create_execution(\n \"\", // functionId\n Some(\"\"), // body (optional)\n Some(false), // async (optional)\n Some(\"\"), // path (optional)\n Some(ExecutionMethod::GET), // method (optional)\n Some(json!({})), // headers (optional)\n None, // scheduledAt (optional)\n ).await?;\n\n println!(\"{:?}\", execution);\n Ok(())\n}\n```\n{% /multicode %}\n{% /tabsitem %}\n{% /tabs %}\n\n# Console {% #console %}\nAnother easy way to test a function is directly in the Appwrite Console.\nYou test a function by hitting the **Execute now** button, which will display a modal below.\n\nYou'll be able to mock executions by configuring the path, method, headers, and body.\n\n{% only_dark %}\n![Create project screen](/images/docs/functions/execution/dark/execute-function.avif)\n{% /only_dark %}\n{% only_light %}\n![Create project screen](/images/docs/functions/execution/execute-function.avif)\n{% /only_light %}\n\n\n# Events {% #events %}\n\nChanges in Appwrite emit events. You can configure Functions to be executed in response to these events.\n\n1. In Appwrite Console, navigate to **Functions**.\n2. Click to open a function you wish to configure.\n3. Under the **Settings** tab, navigate to **Events**.\n4. Add one or multiple events as triggers for the function.\n5. 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.\n\nIn these executions, the event that triggered the function will be passed as the header `x-appwrite-event` to the function.\nThe `request.body` parameter will contain the event data. [Learn more about events](/docs/apis/events).\n\nYou can use one of the following events.\n{% accordion %}\n{% accordion_item title=\"Authentication\" %}\n{% partial file=\"auth-events.md\" /%}\n{% /accordion_item %}\n{% accordion_item title=\"Databases\" %}\n{% partial file=\"databases-events.md\" /%}\n{% /accordion_item %}\n{% accordion_item title=\"Storage\" %}\n{% partial file=\"storage-events.md\" /%}\n{% /accordion_item %}\n{% accordion_item title=\"Functions\" %}\n{% partial file=\"functions-events.md\" /%}\n{% /accordion_item %}\n{% accordion_item title=\"Messaging\" %}\n{% partial file=\"messaging-events.md\" /%}\n{% /accordion_item %}\n{% /accordion %}\n\n# Schedule {% #schedule %}\n\nAppwrite 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**.\n\nHere are some cron expressions for common intervals:\n\n| Cron Expression | Schedule |\n| ---------------- | --------------------- |\n| `*/15 * * * *` | Every 15 minutes |\n| `0 * * * *` | Every Hour |\n| `0 0 * * *` | Every day at 00:00 |\n| `0 0 * * 1` | Every Monday at 00:00 |\n\n# Delayed executions {% #delayed-executions %}\n\nYou 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.\n\n{% tabs %}\n{% tabsitem #console title=\"Console\" %}\nTo schedule an execution, navigate to **Your function** > **Executions** > **Execute now** > **Schedule** in the Appwrite Console.\n\n{% only_dark %}\n![Scheduled execution details screen](/images/docs/functions/execution/dark/scheduled-execution-function.avif)\n{% /only_dark %}\n{% only_light %}\n![Scheduled execution details screen](/images/docs/functions/execution/scheduled-execution-function.avif)\n{% /only_light %}\n{% /tabsitem %}\n\n{% tabsitem #client-sdk title=\"Client SDK\" %}\nYou can also schedule your function executions using a supported [Client SDK](/docs/sdks/#client).\n\n{% multicode %}\n\n```client-web\nimport { Client, Functions, ExecutionMethod } from \"appwrite\";\n\nconst client = new Client()\n .setProject(''); // Your project ID\n\nconst functions = new Functions(client);\n\nconst result = await functions.createExecution({\n functionId: '',\n body: '', // optional\n async: true, // Scheduled executions need to be async\n xpath: '', // optional\n method: ExecutionMethod.GET, // optional\n headers: {}, // optional\n scheduledAt: '2020-10-15T06:38:00.000+00:00' // Schedule execution (optional)\n});\n\nconsole.log(result);\n```\n\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nClient client = Client()\n .setProject(''); // Your project ID\n\nFunctions functions = Functions(client);\n\nExecution result = await functions.createExecution(\n functionId: '', // functionId\n body: '', // optional\n xasync: true, // Scheduled executions need to be async\n path: '', // optional\n method: ExecutionMethod.gET, // optional\n headers: {}, // optional\n scheduledAt: '2020-10-15T06:38:00.000+00:00' // Schedule execution (optional)\n);\n```\n\n```client-react-native\nimport { Client, Functions, ExecutionMethod } from \"react-native-appwrite\";\n\nconst client = new Client()\n .setProject(''); // Your project ID\n\nconst functions = new Functions(client);\n\nconst result = await functions.createExecution({\n functionId: '',\n body: '', // optional\n async: true, // Scheduled executions need to be async\n xpath: '', // optional\n method: ExecutionMethod.GET, // optional\n headers: {}, // optional\n scheduledAt: '2020-10-15T06:38:00.000+00:00' // Schedule execution (optional)\n});\n\nconsole.log(result);\n```\n\n```client-apple\nimport Appwrite\nimport AppwriteEnums\n\nlet client = Client()\n .setProject(\"\") // Your project ID\n\nlet functions = Functions(client)\n\nlet execution = try await functions.createExecution(\n functionId: \"\", // functionId\n body: \"\", // optional\n async: true, // Scheduled executions need to be async\n path: \"\", // optional\n method: .gET, // optional\n headers: [:], // optional\n scheduledAt: \"2020-10-15T06:38:00.000+00:00\" // Schedule execution (optional)\n)\n```\n\n```client-android-kotlin\nimport io.appwrite.Client\nimport io.appwrite.coroutines.CoroutineCallback\nimport io.appwrite.services.Functions\n\nval client = Client(context)\n .setProject(\"\") // Your project ID\n\nval functions = Functions(client)\n\nval result = functions.createExecution(\n functionId = \"\", // functionId\n body = \"\", // (optional)\n async = true, // Scheduled executions need to be async\n path = \"\", // (optional)\n method = ExecutionMethod.GET, // (optional)\n headers = mapOf( \"a\" to \"b\" ), // (optional)\n scheduledAt = \"2020-10-15T06:38:00.000+00:00\" // Schedule execution (optional)\n)\n```\n\n```graphql\nmutation {\n functionsCreateExecution(\n functionId: \"\",\n body: \"\",\n async: true,\n path: \"\",\n method: \"GET\",\n headers: \"{}\",\n scheduledAt: \"2020-10-15T06:38:00.000+00:00\"\n ) {\n _id\n _createdAt\n _updatedAt\n _permissions\n functionId\n trigger\n status\n requestMethod\n requestPath\n requestHeaders {\n name\n value\n }\n responseStatusCode\n responseBody\n responseHeaders {\n name\n value\n }\n logs\n errors\n duration\n }\n}\n```\n\n\n```http\nPOST https://.cloud.appwrite.io/v1/functions//executions HTTP/1.1\nX-Appwrite-Project: \"\"\nX-Appwrite-Response-Format: 1.5.0\nContent-Type: application/json\n\n{\n \"body\": \"\",\n \"async\": true,\n \"path\": \"\",\n \"method\": \"GET\",\n \"headers\": {},\n \"scheduledAt\": \"2025-10-15T06:38:00.000+00:00\"\n}\n```\n\n{% /multicode %}\n\n{% /tabsitem %}\n\n{% tabsitem #sdk title=\"Server SDK\" %}\nYou can also schedule your function executions using a supported [Server SDK](/docs/sdks/#server).\n\n{% multicode %}\n```server-nodejs\nconst sdk = require('node-appwrite');\n\n// Init SDK\nconst client = new sdk.Client();\n\nconst functions = new sdk.Functions(client);\n\nclient\n .setProject('') // Your project ID\n;\n\nconst promise = functions.createExecution(\n '', // functionId\n '', // body (optional)\n true, // Scheduled executions need to be async\n '', // path (optional)\n ExecutionMethod.GET, // method (optional)\n {}, // headers (optional)\n '2020-10-15T06:38:00.000+00:00' // Schedule execution (optional)\n );\n\npromise.then(function (response) {\n console.log(response); // Success\n}, function (error) {\n console.log(error); // Failure\n});\n```\n```deno\nimport * as sdk from \"npm:node-appwrite\";\n\n// Init SDK\nlet client = new sdk.Client();\n\nlet functions = new sdk.Functions(client);\n\nclient\n .setProject('') // Your project ID\n;\n\nconst promise = functions.createExecution(\n '', // functionId\n '', // body (optional)\n true, // Scheduled executions need to be async\n '', // path (optional)\n ExecutionMethod.GET, // method (optional)\n {}, // headers (optional)\n '2020-10-15T06:38:00.000+00:00' // Schedule execution (optional)\n );\n\npromise.then(function (response) {\n console.log(response); // Success\n}, function (error) {\n console.log(error); // Failure\n});\n```\n```go\npackage main\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/appwrite/sdk-for-go/appwrite\"\n)\n\nfunc main() {\n\tclient := appwrite.NewClient(\n\t\tappwrite.WithProject(\"\"),\n\t)\n\n\tfunctions := appwrite.NewFunctions(client)\n\n\texecution, err := functions.CreateExecution(\n\t\t\"\", // functionId\n\t\tfunctions.WithCreateExecutionBody(\"\"), // body (optional)\n\t\tfunctions.WithCreateExecutionAsync(true), // Scheduled executions need to be async\n\t\tfunctions.WithCreateExecutionPath(\"\"), // path (optional)\n\t\tfunctions.WithCreateExecutionMethod(\"GET\"), // method (optional)\n\t\tfunctions.WithCreateExecutionHeaders(map[string]interface{}{}), // headers (optional)\n\t\tfunctions.WithCreateExecutionScheduledAt(\"2025-10-15T06:38:00.000+00:00\")) // Schedule execution (optional)\n\n\tfmt.Println(execution)\n\n\tif err != nil {\n\t\tfmt.Println(err)\n\t}\n}\n```\n```php\nsetProject('') // Your project ID\n;\n\n$functions = new Functions($client);\n\n$result = $functions->createExecution(\n '', // functionId\n '', // body (optional)\n true, // Scheduled executions need to be async\n '', // path (optional)\n ExecutionMethod.GET, // method (optional)\n {}, // headers (optional)\n '2020-10-15T06:38:00.000+00:00' // Schedule execution (optional)\n);\n```\n```python\nfrom appwrite.client import Client\nfrom appwrite.services.functions import Functions\n\nclient = Client()\n\n(client\n .set_project('') # Your project ID\n)\n\nfunctions = Functions(client)\n\nresult = functions.create_execution(\n function_id = '', # functionId\n body = '', # body (optional)\n async = True, # Scheduled executions need to be async\n path = '', # path (optional)\n method = ExecutionMethod.GET, # method (optional)\n headers = {} # headers (optional)\n scheduled_at = '2020-10-15T06:38:00.000+00:00' # Schedule execution (optional)\n)\n```\n```ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_project('') # Your project ID\n\nfunctions = Functions.new(client)\n\nresponse = functions.create_execution(\n function_id: '', # functionId\n body: '', # body (optional)\n async: true, # Scheduled executions need to be async\n path: '', # path (optional)\n method: ExecutionMethod::GET, # method (optional)\n headers: {} # headers (optional)\n scheduled_at: '2020-10-15T06:38:00.000+00:00' # Schedule execution (optional)\n)\n\nputs response.inspect\n```\n```csharp\nusing Appwrite;\nusing Appwrite.Services;\nusing Appwrite.Models;\n\nvar client = new Client()\n .SetProject(\"\"); // Your project ID\n\nvar functions = new Functions(client);\n\nExecution result = await functions.CreateExecution(\n functionId: \"\", // functionId\n body: \"\", // body (optional)\n async: true, // Scheduled executions need to be async\n path: \"\", // path (optional)\n method: ExecutionMethod.GET, // method (optional)\n headers: [object] // headers (optional)\n scheduledAt: \"2020-10-15T06:38:00.000+00:00\"; // Schedule execution (optional)\n```\n```dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\n\nvoid main() { // Init SDK\n Client client = Client();\n Functions functions = Functions(client);\n\n client\n .setProject('') // Your project ID\n ;\n\n Future result = functions.createExecution(\n functionId: '', // functionId\n body: '', // (optional)\n xasync: true, // Scheduled executions need to be async\n path: '', // (optional)\n method: ExecutionMethod.GET, // (optional)\n headers: {}, // (optional)\n scheduledAt: '2020-10-15T06:38:00.000+00:00' // Schedule execution (optional)\n );\n\n result\n .then((response) {\n print(response); // Success\n }).catchError((error) {\n print(error.response); // Failure\n });\n}\n```\n```kotlin\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Functions;\n\nClient client = new Client()\n .setProject(\"\"); // Your project ID\n\nFunctions functions = new Functions(client);\n\nfunctions.createExecution(\n \"\", // functionId\n \"\", // body (optional)\n true, // Scheduled executions need to be async\n \"\", // path (optional)\n \"GET\", // method (optional)\n mapOf( \"a\" to \"b\" ), // headers (optional)\n \"2020-10-15T06:38:00.000+00:00\", // Schedule execution (optional)\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(result);\n })\n);\n```\n```java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Functions;\n\nClient client = new Client()\n .setProject(\"\"); // Your project ID\n\nFunctions functions = new Functions(client);\n\nfunctions.createExecution(\n \"\", // functionId\n \"\", // body (optional)\n true, // Scheduled executions need to be async\n \"\", // path (optional)\n ExecutionMethod.GET, // method (optional)\n mapOf( \"a\" to \"b\" ), // headers (optional)\n \"2020-10-15T06:38:00.000+00:00\" // Schedule execution (optional)\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(result);\n })\n);\n```\n```swift\nimport Appwrite\n\nlet client = Client()\n .setProject(\"\") // Your project ID\n\nlet functions = Functions(client)\n\nlet execution = try await functions.createExecution(\n functionId: \"\",\n body: \"\", // optional\n async: true, // Scheduled executions need to be async\n path: \"\", // optional\n method: .gET, // optional\n headers: [:] // optional\n scheduledAt: \"2020-10-15T06:38:00.000+00:00\" // Schedule execution (optional)\n)\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::functions::Functions;\nuse appwrite::enums::execution_method::ExecutionMethod;\nuse serde_json::json;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box> {\n let client = Client::new()\n .set_project(\"\");\n\n let functions = Functions::new(&client);\n\n let execution = functions.create_execution(\n \"\", // functionId\n Some(\"\"), // body (optional)\n Some(true), // async - scheduled executions need to be async\n Some(\"\"), // path (optional)\n Some(ExecutionMethod::GET), // method (optional)\n Some(json!({})), // headers (optional)\n Some(\"2020-10-15T06:38:00.000+00:00\"), // scheduledAt (optional)\n ).await?;\n\n println!(\"{:?}\", execution);\n Ok(())\n}\n```\n{% /multicode %}\n\n{% /tabsitem %}\n{% /tabs %}\n\n# Permissions {% #permission %}\n\nAppwrite 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.\n\nIf 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."}, {"path": "docs/products/functions/executions", "title": "Execution", "description": "Learn how Appwrite handles serverless function executions. More specifically, execution status, details and function logging.", "content": "Each time an Appwrite Function runs, an **execution** is created.\nEach execution has a unique ID. If [you enable execution logs](/docs/products/functions/functions#execution-logs) in your function,\nyou can find function executions logged in the **Executions** tab.\n\n# Execution table {% #execution-table %}\n\nIn your function's **Executions** tab, you will see a table of your recent executions.\nHere's the information shown on this table.\n\n{% table %}\n- Column\n- Description\n---\n- Execution ID\n- Unique identifier for each execution\n---\n- Status\n- The current status of the execution\n---\n- Created\n- Timestamp of when the execution was created\n---\n- Trigger\n- The [platform event](/docs/apis/events) that triggered the execution\n---\n- Method\n- The HTTP method used to create the execution\n---\n- Path\n- The URL path the function execution was called with\n---\n- Duration\n- The time taken for the execution\n{% /table %}\n\n## Execution status {% #execution-status %}\n\nEach execution can have one of the follow status.\n\n{% table %}\n- Status\n- description\n---\n- `scheduled`\n- The function execution will trigger later.\n---\n- `waiting`\n- The execution is queued but has not been picked up for processing.\n---\n- `processing`\n- The function execution has begun and has not finished.\n---\n- `completed`\n- The function executed successfully.\n---\n- `failed`\n- The function execution was not successful.\n{% /table %}\n\n# Execution details {% #execution-details %}\n\nWhen you click on an execution, you will be taken to an execution detail screen.\n\n{% only_dark %}\n![Execution details screen](/images/docs/functions/execution/dark/execute-function.avif)\n{% /only_dark %}\n\n{% only_light %}\n![Execution details screen](/images/docs/functions/execution/execute-function.avif)\n{% /only_light %}\n\nYou can find both request and response details.\nRequest and response body are **not logged** to protect user privacy.\nThis ensures that developers do not see user data by default and no sensitive data is retained.\n\nIf you need to log debug data or audit logs, you can use [function logging features](/docs/products/functions/develop#logging)\nto explicitly log the information you need.\n\n{% info title=\"Access control\" %}\nOnly the user who created an execution can retrieve it using the [Get execution](/docs/references/cloud/client-web/functions#getExecution) endpoint.\n{% /info %}\n\n# Log retention {% #log-retention %}\n\nLogs are not retained forever in order to be compliant with GDPR and other data privacy standards.\nFree plan organizations will retain logs for 24 hours, Pro plan organizations will retain logs for 7 days.\n\nIf you need longer log retention, you can log to an Appwrite table.\nRemember to configure proper permissions and implement Appwrite Functions or other scheduled tasks to expire and clean up logs."}, {"path": "docs/products/functions/functions", "title": "Functions", "description": "Learn what an Appwrite Function can do for you and how to create a new Appwrite Function", "content": "Each Appwrite Function is a piece of developer defined code that can be executed on demand.\nWhen you create a new Appwrite Function, you select a name, ID, and [runtime language](/docs/products/functions/runtimes).\n\nEach time a function's code is updated, a [deployment](/docs/products/functions/deployments) is created, which is like a version of a function.\nEach function has a single active deployment, which is the version of code that's executed when it's called.\nYou can update the Appwrite Function's code by creating new [deployments](/docs/products/functions/deploy-from-git). \nYou can also switch between different deployments by activating them.\n\n# Create function {% #create-function %}\nYou can create Appwrite Functions in three different ways.\n\n{% tabs %}\n{% tabsitem #git title=\"Git\" %}\nIt's recommended to create functions that are connected to version control. \nThis lets you track your code using Git, which makes it easy to integrate Appwrite Functions into your existing code base.\n1. In the Appwrite Console's sidebar, click **Functions**.\n2. Click **Create function**.\n3. 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.\n4. If you already have a repository containing an Appwrite Function, select it under **Connect Git repository**. \nIf you need to create a new function, select a **Quick start** template or search for more templates under **All templates**.\n5. Follow the wizard to configure your new Appwrite Function.\n{% only_dark %}\n![Create project screen](/images/docs/functions/dark/template.avif)\n{% /only_dark %}\n{% only_light %}\n![Create project screen](/images/docs/functions/template.avif)\n{% /only_light %}\n{% /tabsitem %}\n\n{% tabsitem #manual title=\"Manual\" %}\nYou can also create Appwrite Functions manually by uploading your code in a zipped file.\nIn the **Create Function** modal, click **create a function manually** at the bottom to switch to manual create wizard.\nYou will be asked to upload a zip file with your code.\n\nFirst, navigate inside the folder that contains your dependency file. \nFor example, when you list the content of your folder for a Node.js function, it will look like this.\n\n```text\n.\n├── package.json\n└── index.js\n```\n\nPackage your code files into the `.tar.gz` format. **Don't include your dependencies folder**, such as `node_modules`.\n\n{% multicode %}\n ```bash\n tar --exclude code.tar.gz -czf code.tar.gz .\n ```\n ```cmd\n tar --exclude code.tar.gz -czf code.tar.gz .\n ```\n ```powershell\n tar --exclude code.tar.gz -czf code.tar.gz .\n ```\n{% /multicode %}\n\nUpload your `.tar.gz` file and specify the entry point of your function, in this case `index.js`.\nRemember to specify the build commands for your function to install dependencies.\n{% /tabsitem %}\n\n{% tabsitem #cli title=\"CLI\" %}\n\n{% partial file=\"cli-function.md\" /%}\n\n{% /tabsitem %}\n{% /tabs %}\n\n# Configuration {% #configuration %}\n\n## Name {% #name %}\nYou can update the name of your function by navigating to your function > **Settings** > **Name**.\nUpdate your function's name and click **Update**.\nFunctions are executed using it's ID, updating name does not affect references to your function.\n\n## Runtime {% #runtime %}\nEvery deployment of a function uses the same runtime. \nYou can update the runtime of a function by navigating to function > **Settings** > **Runtime**.\nSelect a new runtime and click **Update**.\n\n{% info title=\"Redeployment required\" %}\nThis change requires your function to be redeployed to take effect.\n{% /info %}\n\n## Build configuration {% #build-configuration %}\nYou can update the entrypoint file and build settings of your function by navigating to your function > \n**Settings** > **Configuration**. \n\n{% info title=\"Redeployment required\" %}\nThis change requires your function to be redeployed to take effect.\n{% /info %}\n\nThe **Entrypoint** refers to the file imported and executed by the function executor. \nIt must export a [valid function entrypoint function](/docs/products/functions/develop#entrypoint).\nIt's recommended you use one of the [starter function templates](/docs/products/functions/templates) and edit from there.\n\nUnder **Build settings**, you can update your build commands. \nThese are terminal commands that will be executed in the runtime containers in the build step of the deployment process.\n\n## Resource limits {% #resource-limits %}\n\nUnder **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.\n\nOn Appwrite Cloud, customizing specifications requires the **Pro** plan. See [Compute](/docs/advanced/billing/compute) for tiers, GB-hours, and pricing.\n\n## Build timeout {% #build-timeout %}\n\nOn 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).\n\n## Git integration {% #git-integration %}\nYou can update the entrypoint file and build settings of your function by navigating to your function > **Settings** > **Configuration**.\n\n{% info title=\"Redeployment required\" %}\nThis change requires your function to be redeployed to take effect.\n{% /info %}\n\nUnder **Git settings** you can configure the Git repository and branch that your function is connected to.\n\nBuild commands and entrypoint are executed relative to the configured **Root directory** of your Git respository.\n\nBy default, Appwrite will create comments in PRs to your connected branch. You can use **Silent mode** to suppress these comments.\n\n## Execution logs {% #execution-logs %}\nYou can enable and disable execution logs for your functions by navigating to your function > **Settings** > **Execution logs**\nIn production environments, you can choose to disable execution logs to protect user privacy.\n\n## Execute access {% #execution-access %}\nYou can control who can execute your functions\nby navigating to your function > **Settings** > **Execute access**\nand granting access to select [permission roles](/docs/advanced/security/permissions#permission-roles).\n\nIf this is left empty, no user can execute your function.\nServer SDKs, scheduled executions, and event function triggers don't require permissions to execute a function.\n\n## Events{% #events %}\nFunctions can be triggered by [platform events](/docs/apis/events) which reflect changes \nthat occur in your Appwrite project.\nYou can configure events triggers by navigating to your function > **Settings** > **Events**.\n\n## Schedule {% #schedule %}\n\nAppwrite supports scheduled function executions. \nYou can schedule executions using [cron expressions](https://en.wikipedia.org/wiki/Cron) in the settings of your function. \nCron supports recurring executions as frequently as **every minute**.\n\nYou can configure events triggers by navigating to your function > **Settings** > **Schedule**.\n\nHere are some cron expressions for common intervals:\n\n| Cron Expression | Schedule |\n| ---------------- | --------------------- |\n| `*/15 * * * *` | Every 15 minutes |\n| `0 * * * *` | Every Hour |\n| `0 0 * * *` | Every day at 00:00 |\n| `0 0 * * 1` | Every Monday at 00:00 |\n\n## Timeout {% #timeout %}\nYou can limit the execution time of your function by navigating to your function > **Settings** > **Timeout**.\nThere is a system wide maximum timeout of 900 seconds (15 minutes).\n\n## Scopes {% #scopes %}\n\nYou 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.\n\n{% arrow_link href=\"/docs/advanced/security/api-keys#scopes\" %}\nLearn more about scopes\n{% /arrow_link %}"}, {"path": "docs/products/functions/quick-start", "title": "Start with Functions", "description": "Get started quickly with Appwrite Functions. Follow a step-by-step guide to create your first serverless function, define triggers, and execute code.", "content": "You can create and execute your first Appwrite Function in minutes.\n\n# Create function {% #create-function %} \n\nBefore deploying your function with Git, create a new function attached to your Git repository.\n\n{% only_dark %}\n![Create function screen](/images/docs/functions/quick-start/dark/create-function.avif)\n{% /only_dark %}\n{% only_light %}\n![Create function screen](/images/docs/functions/quick-start/create-function.avif)\n{% /only_light %}\n\n1. In the Appwrite Console's sidebar, click **Functions**.\n2. Click **Create function**.\n3. Under **Connect Git repository**, select your provider.\n4. After connecting to GitHub, under **Quick start**, select a starter template.\n5. Follow the step-by-step wizard and create the function.\n6. The function will be created and a build will begin. Once the build completes, you'll have created your first function.\n\nYou can find the code used by the starter template in your newly created Git repository. \nEach push to your Git repo will trigger a new deployment.\n\n## Execute {% #execute %} \nYou can execute your Appwrite Function through [many different triggers](/docs/products/functions/execute).\nThe easiest way to execute your first function is to use the Appwrite Console.\n\n{% only_dark %}\n![Execution-screen](/images/docs/functions/quick-start/dark/function-execution.avif)\n{% /only_dark %}\n{% only_light %}\n![Execution-screen](/images/docs/functions/quick-start/function-execution.avif)\n{% /only_light %}\n\n1. In the Appwrite Console's sidebar, click **Functions**.\n1. Under the **Executions** tab, click **Execute now**.\n1. Click **Execute** to execute the starter template function.\n1. Wait for the execution to be marked **completed** and click to view the execution logs.\n\n\n# Explore {% #explore %}\nUse this your first function as a springboard to explore the flexible and powerful features of Appwrite Functions.\n\n{% cards %}\n{% cards_item href=\"/docs/products/functions/templates\" title=\"Template\" %}\nGet a template function up and running with a single click.\n{% /cards_item %}\n{% cards_item href=\"/docs/products/functions/develop\" title=\"Develop\" %}\nLearn about developing your own Appwrite Function.\n{% /cards_item %}\n{% cards_item href=\"/docs/products/functions/deploy-from-git\" title=\"Deploy\" %}\nLearn to deploy Appwrite Functions from Git.\n{% /cards_item %}\n{% cards_item href=\"/docs/products/functions/execute\" title=\"Execute\" %}\nExplore the different ways an Appwrite Function can be executed.\n{% /cards_item %}\n{% cards_item href=\"/docs/products/functions/runtimes\" title=\"Runtimes\" %}\nWrite Appwrite Functions in your favorite language.\n{% /cards_item %}\n{% /cards %}"}, {"path": "docs/products/functions/runtimes", "title": "Runtimes", "description": "Choose the right runtime environment for your serverless functions in Appwrite. Explore available runtimes, dependencies, and runtime-specific considerations.", "content": "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.\n\n# Available runtimes {% #available-runtimes %}\nBelow is a list of available Functions runtimes. The Appwrite team continually adds support for new runtimes.\nWhile 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.\n\n{% tabs %}\n{% tabsitem #all title=\"All runtimes\" %}\n{% table %}\n*   {% width=48 %}\n* Name {% width=100 %}\n* Versions\n* Architectures\n---\n* {% icon icon=\"node_js\" size=\"l\" /%}\n* [Node.js](https://hub.docker.com/r/openruntimes/node/tags)\n* `node-14.5`\n`node-16.0`\n`node-18.0`\n`node-19.0`\n`node-20.0`\n`node-21.0`\n`node-22`\n`node-25`\n* x86 / arm64 / armv7 / armv8\n---\n* {% icon icon=\"bun-sh\" size=\"l\" /%}\n* [Bun](https://hub.docker.com/r/openruntimes/bun/tags)\n* `bun-1.0`\n`bun-1.1`\n`bun-1.3`\n* x86 / arm64\n---\n* {% icon icon=\"deno\" size=\"l\" /%}\n* [Deno](https://hub.docker.com/r/openruntimes/deno/tags)\n* `deno-1.21`\n`deno-1.24`\n`deno-1.35`\n`deno-1.40`\n`deno-1.46`\n`deno-2.0`\n`deno-2.6`\n* x86\n---\n* {% icon icon=\"go\" size=\"l\" /%}\n* [Go](https://hub.docker.com/r/openruntimes/go/tags)\n* `go-1.23`\n`go-1.26`\n* x86 / arm64\n---\n* {% icon icon=\"python\" size=\"l\" /%}\n* [Python](https://hub.docker.com/r/openruntimes/python/tags)\n* `python-3.8`\n`python-3.9`\n`python-3.10`\n`python-3.11`\n`python-3.12`\n`python-3.14`\n* x86 / arm64 / armv7 / armv8\n---\n* {% icon icon=\"python\" size=\"l\" /%}\n* [Python ML](https://hub.docker.com/r/openruntimes/python-ml)\n* `python-ml-3.11`\n* x86 / arm64\n---\n* {% icon icon=\"dart\" size=\"l\" /%}\n* [Dart](https://hub.docker.com/r/openruntimes/dart/tags)\n* `dart-2.15`\n`dart-2.16`\n`dart-2.17`\n`dart-2.18`\n`dart-3.0`\n`dart-3.1`\n`dart-3.3`\n`dart-3.5`\n`dart-3.10`\n`dart-3.11`\n* x86 / arm64 / armv7 / armv8\n---\n* {% icon icon=\"php\" size=\"l\" /%}\n* [PHP](https://hub.docker.com/r/openruntimes/php/tags)\n* `php-8.0`\n`php-8.1`\n`php-8.2`\n`php-8.3`\n`php-8.4`\n* x86 / arm64 / armv7 / armv8\n---\n* {% icon icon=\"ruby\" size=\"l\" /%}\n* [Ruby](https://hub.docker.com/r/openruntimes/ruby/tags)\n* `ruby-3.0`\n`ruby-3.1`\n`ruby-3.2`\n`ruby-3.3`\n`ruby-4.0`\n* x86 / arm64 / armv7 / armv8\n---\n* {% icon icon=\"dotnet\" size=\"l\" /%}\n* [.NET](https://hub.docker.com/r/openruntimes/dotnet/tags)\n* `dotnet-6.0`\n`dotnet-7.0`\n`dotnet-8.0`\n* x86 / arm64 / armv7 / armv8\n---\n* {% icon icon=\"java\" size=\"l\" /%}\n* [Java](https://hub.docker.com/r/openruntimes/java/tags)\n* `java-8.0`\n`java-11.0`\n`java-17.0`\n`java-18.0`\n`java-21.0`\n`java-22`\n* x86 / arm64 / armv7 / armv8\n---\n* {% icon icon=\"swift\" size=\"l\" /%}\n* [Swift](https://hub.docker.com/r/openruntimes/swift/tags)\n* `swift-5.5`\n`swift-5.8`\n`swift-5.9`\n`swift-5.10`\n* x86 / arm64 / armv7 / armv8\n---\n* {% icon icon=\"kotlin\" size=\"l\" /%}\n* [Kotlin](https://hub.docker.com/r/openruntimes/kotlin/tags)\n* `kotlin-1.6`\n`kotlin-1.8`\n`kotlin-1.9`\n`kotlin-2.0`\n* x86 / arm64 / armv7 / armv8\n---\n* {% icon icon=\"flutter\" size=\"l\" /%}\n* [Flutter](https://hub.docker.com/r/openruntimes/flutter/tags)\n* `flutter-3.38`\n`flutter-3.41`\n* x86 / arm64\n---\n* {% icon icon=\"cpp\" size=\"l\" /%}\n* [C++](https://hub.docker.com/r/openruntimes/cpp/tags)\n* `cpp-17`\n`cpp-20`\n* x86 / arm64 / armv7 / armv8\n---\n* {% only_dark %}{% icon_image src=\"/images/platforms/dark/rust.svg\" alt=\"Rust logo\" size=\"l\" /%}{% /only_dark %}\n{% only_light %}{% icon_image src=\"/images/platforms/light/rust.svg\" alt=\"Rust logo\" size=\"l\" /%}{% /only_light %}\n* [Rust](https://hub.docker.com/r/openruntimes/rust/tags)\n* `rust-1.83`\n* x86 / arm64\n---\n{% /table %}\n{% /tabsitem %}\n\n{% tabsitem #available-on-cloud title=\"Available on Cloud\" %}\n{% table %}\n*   {% width=48 %}\n* Name {% width=120 %}\n* Versions\n* Architectures\n---\n* {% icon icon=\"node_js\" size=\"l\" /%}\n* [Node.js](https://hub.docker.com/r/openruntimes/node/tags)\n* `node-16.0`\n`node-18.0`\n`node-22`\n`node-25`\n* x86 / arm64 / armv7 / armv8\n---\n* {% icon icon=\"bun-sh\" size=\"l\" /%}\n* [Bun](https://hub.docker.com/r/openruntimes/bun/tags)\n* `bun-1.0`\n`bun-1.1`\n`bun-1.3`\n* x86 / arm64\n---\n* {% icon icon=\"deno\" size=\"l\" /%}\n* [Deno](https://hub.docker.com/r/openruntimes/deno/tags)\n* `deno-2.0`\n`deno-2.6`\n* x86\n---\n* {% icon icon=\"go\" size=\"l\" /%}\n* [Go](https://hub.docker.com/r/openruntimes/go/tags)\n* `go-1.23`\n`go-1.26`\n* x86 / arm64\n---\n* {% icon icon=\"python\" size=\"l\" /%}\n* [Python](https://hub.docker.com/r/openruntimes/python/tags)\n* `python-3.9`\n`python-3.12`\n`python-3.14`\n* x86 / arm64 / armv7 / armv8\n---\n* {% icon icon=\"python\" size=\"l\" /%}\n* [Python ML](https://hub.docker.com/r/openruntimes/python-ml)\n* `python-ml-3.11`\n* x86 / arm64\n---\n* {% icon icon=\"dart\" size=\"l\" /%}\n* [Dart](https://hub.docker.com/r/openruntimes/dart/tags)\n* `dart-2.17`\n`dart-3.1`\n`dart-3.5`\n`dart-3.10`\n`dart-3.11`\n* x86 / arm64 / armv7 / armv8\n---\n* {% icon icon=\"flutter\" size=\"l\" /%}\n* [Flutter](https://hub.docker.com/r/openruntimes/flutter/tags)\n* `flutter-3.38`\n`flutter-3.41`\n* x86 / arm64\n---\n* {% icon icon=\"php\" size=\"l\" /%}\n* [PHP](https://hub.docker.com/r/openruntimes/php/tags)\n* `php-8.0`\n`php-8.3`\n`php-8.4`\n* x86 / arm64 / armv7 / armv8\n---\n* {% icon icon=\"ruby\" size=\"l\" /%}\n* [Ruby](https://hub.docker.com/r/openruntimes/ruby/tags)\n* `ruby-3.0`\n`ruby-3.3`\n`ruby-4.0`\n* x86 / arm64 / armv7 / armv8\n---\n* {% only_dark %}{% icon_image src=\"/images/platforms/dark/rust.svg\" alt=\"Rust logo\" size=\"l\" /%}{% /only_dark %}\n{% only_light %}{% icon_image src=\"/images/platforms/light/rust.svg\" alt=\"Rust logo\" size=\"l\" /%}{% /only_light %}\n* [Rust](https://hub.docker.com/r/openruntimes/rust/tags)\n* `rust-1.83`\n* x86 / arm64\n---\n{% /table %}\n{% /tabsitem %}\n{% /tabs %}"}, {"path": "docs/products/functions/templates", "title": "Templates", "description": "Learn about Appwrite Functions' templates that let you jump start function development to extend your Appwrite APIs.", "content": "Appwrite provides a variety of Function Templates to help you jump start your function development.\nYou can use Appwrite Function Templates as examples or boilerplates to add new functionality to your Appwrite project.\n\n# Find templates {% #find-templates %}\nYou can find all available templates by navigating to the Appwrite Console, under your project > **Functions** > **Templates**.\n\n{% only_dark %}\n![Templates screen](/images/docs/functions/templates/dark/templates.avif)\n{% /only_dark %}\n{% only_light %}\n![Templates screen](/images/docs/functions/templates/templates.avif)\n{% /only_light %}\n\nYou can filter functions by searching, filter by use case, or filter by runtime.\nClick **Create function** to create a function from a template.\n\n# Create with templates {% #create-with-templates %}\n\nThe create function wizard has five steps.\n\n## Configuration {% #configuration %}\nPick a display name for your function and an ID. You will later use the ID to programmatically execute or configure the function.\nPick the runtime language you wish the function to be created in, not all runtimes are available for all templates.\n\n## Variables {% #variables %}\nAppwrite Functions uses [environment variables](/docs/products/functions/develop#environment-variables)\nto pass constants and secrets to your Appwrite Functions. You'll provide information like API keys and other\nsecrets to integrations in this step. If you need an Appwrite API key, you'll be propted to generate one.\n\n## Connect {% #connect %}\nYou can choose to clone a new repository to your GitHub profile or organization, or to connect to an existing repository.\n\nIf you choose connect to an existing repository, the function's code will be cloned to the root folder\nyou specify under the **Branch** step.\n\n## Repository {% #repository %}\nConfigure the connected respository for your Appwrite Function. This will be the repository\nholding the source code for your function. When the code in this repository is updated, new \ndeployments will be created.\n\n## Branch {% #branch %}\nProduction branch specifies the branch connected to your Appwrite Function. When new commits are made to\nthis branch, a new deployment is automatically created and deployed.\n\nThe root directory specifies the folder holding your function template's code.\n\nWhen a PR is made to the branch, a new deployment is built, but not activated.\nA comment is made to your PR about the build, unless you enable **Silent mode**.\n\n# Available templates {% #available-templates %}\n{% table %}\n* Template {% width=100 %}\n* Description\n* Runtimes {% width=200 %}\n---\n* Starter\n* A simple starter function that returns \"Hello, world!\"\n* Node.js, Python, PHP, Dart, Node.js (TypeScript), Bun, Deno, Ruby, Kotlin, C++, .NET, Java, Swift, Go, Rust\n---\n* Sync with Meilisearch\n* Syncs rows in an Appwrite database table to a Meilisearch index to add search-as-you-type search boxes to your app.\n* Node.js, Python, PHP, Node.js (TypeScript), Bun, Deno, Ruby, Kotlin\n---\n* WhatsApp with Vonage\n* Simple bot to answer WhatsApp messages.\n* Node.js, Python, PHP, Dart, Node.js (TypeScript), Bun, Deno, Ruby\n---\n* Prompt ChatGPT\n* Ask question, and let OpenAI GPT-3.5-turbo answer.\n* Node.js, Python, PHP, Dart\n---\n* Censor with Redact\n* Automatically remove sensitive data from messages.\n* Node.js, Python, Dart\n---\n* Email Contact Form\n* Sends an email with the contents of a HTML form.\n* Node.js, Python, PHP\n---\n* Sync with Algolia\n* Intuitive search bar for any data in Appwrite Databases.\n* Node.js, Python, PHP\n---\n* Discord Command Bot\n* Add Discord commands to your servers using Discord Interactions.\n* Node.js, Python, Go\n---\n* Github Issue Bot\n* Automate the process of responding to newly opened issues on a GitHub repository.\n* Node.js, Node.js (TypeScript)\n---\n* Analyze with PerspectiveAPI\n* Automate moderation by using AI to measure the toxicity of messages.\n* Node.js\n---\n* Generate PDF\n* Generate PDFs programmatically with Appwrite Functions.\n* Node.js\n---\n* Payments with Stripe\n* Receive card payments and store paid orders.\n* Node.js\n---\n* Push Notification with FCM\n* Send push notifications to your users using Firebase Cloud Messaging (FCM).\n* Node.js\n---\n* Slack Command Bot\n* Simple command bot using Slack API\n* Node.js\n---\n* Storage Cleaner\n* Storage cleaner function to remove all files older than X number of days from the specified bucket.\n* Node.js\n---\n* Subscriptions with Stripe\n* Receive recurring card payments and grant subscribers extra permissions.\n* Node.js\n---\n* URL Shortener\n* Generate URL with short ID and redirect to the original URL when visited.\n* Node.js\n{% /table %}"}, {"path": "docs/products/messaging", "title": "Messaging", "description": "Send push notifications, text, or emails to users or groups of users using your app.", "content": "Appwrite Messaging helps you communicate with your users through push notifications, emails, and SMS text messages.\nSending personalized communication for marketing, updates, and realtime alerts can increase user engagement and retention.\nYou can also use Appwrite Messaging to implement security checks and custom authentication flows.\n\n{% only_dark %}\n![Messaging overview](/images/docs/messaging/dark/message-overview.avif)\n{% /only_dark %}\n{% only_light %}\n![Messaging overview](/images/docs/messaging/message-overview.avif)\n{% /only_light %}\n\nExplore what you can build with Appwrite Messaging. \n{% cards %}\n{% cards_item href=\"/docs/products/messaging/send-email-messages\" title=\"Emails\" icon=\"icon-mail\" %}\nSend newsletters, invoices, promotions and other emails.\n{% /cards_item %}\n{% cards_item href=\"/docs/products/messaging/send-sms-messages\" title=\"SMS messages\" icon=\"icon-annotation\" %}\nSend SMS messages straight to your user's phone.\n{% /cards_item %}\n{% cards_item href=\"/docs/products/messaging/send-push-notifications\" title=\"Push notifications\" icon=\"icon-device-mobile\" %}\nSend push notifications to your user's devices.\n{% /cards_item %}\n{% /cards %}"}, {"path": "docs/products/messaging/apns", "title": "Apple Push Notification service", "description": "Send push notifications to apps on Apple devices through Apple Push Notification service (APNs) using Appwrite Messaging.", "content": "Apple Push Notification service (APNs) lets you send push notifications to Apple devices like macOS, iOS, tvOS, iPadOS, and watchOS devices.\nAPNs is a best-effort service, and will attempt to deliver you messages to your device when it's online and available again.\nAPNs will save the last message for 30 days or less and attempt delivery as soon as it's online.\n\n{% section #add-provider step=1 title=\"Add provider\" %}\n\nTo add APNs as a provider, navigate to **Messaging** > **Providers** > {% icon icon=\"plus\" size=\"m\" /%} **Create provider** > **Push notification**.\n\n{% only_dark %}\n![Add a FCM provider](/images/docs/messaging/providers/apns/dark/provider.avif)\n{% /only_dark %}\n{% only_light %}\n![Add a FCM provider](/images/docs/messaging/providers/apns/provider.avif)\n{% /only_light %}\n\nGive your provider a name > choose **APNS** > click **Save and continue**.\nThe provider will be saved to your project, but not enabled until you complete its configuration.\n{% /section %}\n{% section #configure-provider step=2 title=\"Configure provider\" %}\n\nIn the **Configure** step, you will need to provide details from your Apple developer account to connect your Appwrite project with your\nApple developer account.\n\nYou will need to provide the following information from the **Apple Developer Member Center**.\n\n{% accordion %}\n{% accordion_item title=\"Team ID\" %}\nHead to **Apple Developer Member Center** > **Membership details** > **Team ID**\n\n{% only_dark %} ![Team ID](/images/docs/messaging/providers/apns/dark/team-id.avif) {% /only_dark %} {% only_light %} ![Team ID](/images/docs/messaging/providers/apns/team-id.avif) {% /only_light %}\n{% /accordion_item %}\n{% accordion_item title=\"Bundle ID\" %}\nHead to **Apple Developer Member Center** > **Program resources** > **Certificates, Identifiers & Profiles** > **Identifiers**\n\n{% only_dark %}\n![Bundle ID](/images/docs/messaging/providers/apns/dark/bundle-id.avif)\n{% /only_dark %}\n{% only_light %}\n![Bundle ID](/images/docs/messaging/providers/apns/bundle-id.avif)\n{% /only_light %}\n{% /accordion_item %}\n{% accordion_item title=\"Authentication key ID\" %}\nHead 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.\n\n{% only_dark %}\n![Authentication Key ID](/images/docs/messaging/providers/apns/dark/key-id.avif)\n{% /only_dark %}\n{% only_light %}\n![Authentication Key ID](/images/docs/messaging/providers/apns/key-id.avif)\n{% /only_light %}\n{% /accordion_item %}\n{% accordion_item title=\"Authentication key (.p8 file)\" %}\nHead 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.\n\n{% only_dark %}\n![Authentication Key](/images/docs/messaging/providers/apns/dark/authentication-key.avif)\n{% /only_dark %}\n {% only_light %}\n![Authentication Key](/images/docs/messaging/providers/apns/authentication-key.avif)\n{% /only_light %}\n{% /accordion_item %}\n{% accordion_item title=\"Sandbox\" %}\nEnable 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.\n{% /accordion_item %}\n{% /accordion %}\n\nAfter adding the following details, click **Save and continue** to enable the provider.\n{% /section %}\n{% section #configure-app step=3 title=\"Configure app\" %}\nSome additional configuration is required to enable push notifications in your iOS app.\nAdd push notification capability to your app by clicking your root-level app in XCode > **Signing & Capabilities** > {% icon icon=\"plus\" size=\"m\" /%} Capabilities > Search for **Push Notifications**.\n\n{% only_dark %}\n![Enable PN on Xcode](/images/docs/messaging/providers/apns/dark/xcode-enable-pn.avif)\n{% /only_dark %}\n{% only_light %}\n![Enable PN on Xcode](/images/docs/messaging/providers/apns/xcode-enable-pn.avif)\n{% /only_light %}\n{% /section %}\n{% section #test-provider step=4 title=\"Test provider\" %}\nPush notification requires special handling on the client side. Follow the [Send push notification](/docs/products/messaging/send-push-notifications) flow to test your provider.\n{% /section %}\n\n{% section #manage-provider step=5 title=\"Manage provider\" %}\n{% tabs %}\n{% tabsitem #console title=\"Console\" %}\nYou can update or delete a provider in the Appwrite Console.\n\nNavigate to **Messaging** > **Providers** > click your provider.\nIn the settings, you can update a provider's configuration or delete the provider.\n{% /tabsitem %}\n\n{% tabsitem #server-sdk title=\"Server SDK\" %}\nTo update or delete providers programmatically, use an [Appwrite Server SDK](/docs/sdks#server).\n\n{% multicode %}\n\n```server-nodejs\nconst sdk = require('node-appwrite');\n\n// Init SDK\nconst client = new sdk.Client();\n\nconst messaging = new sdk.Messaging(client);\n\nclient\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n;\n\nconst provider = await messaging.updateApnsProvider(\n '', // providerId\n '', // name (optional)\n false, // enabled (optional)\n '', // authKey (optional)\n '', // authKeyId (optional)\n '', // teamId (optional)\n '' // bundleId (optional)\n );\n```\n```deno\nimport * as sdk from \"npm:node-appwrite\";\n\n// Init SDK\nlet client = new sdk.Client();\n\nlet messaging = new sdk.Messaging(client);\n\nclient\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n;\n\nconst provider = await messaging.updateApnsProvider(\n '', // providerId\n '', // name (optional)\n false, // enabled (optional)\n '', // authKey (optional)\n '', // authKeyId (optional)\n '', // teamId (optional)\n '' // bundleId (optional)\n );\n```\n```php\nsetEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n ->setProject('') // Your project ID\n ->setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n;\n\n$messaging = new Messaging($client);\n\n$result = $messaging->updateApnsProvider(\n providerId: '',\n name: '', // optional\n enabled: false, // optional\n authKey: '', // optional\n authKeyId: '', // optional\n teamId: '', // optional\n bundleId: '' // optional\n);\n```\n```python\nfrom appwrite.client import Client\nfrom appwrite.services.messaging import Messaging\n\nclient = Client()\n\n(client\n .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('') # Your project ID\n .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key\n)\n\nmessaging = Messaging(client)\n\nresult = messaging.update_apns_provider(\n provider_id = '',\n name = '', # optional\n enabled = False, # optional\n auth_key = '', # optional\n auth_key_id = '', # optional\n team_id = '', # optional\n bundle_id = '' # optional\n)\n```\n```ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('') # Your project ID\n .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key\n\nmessaging = Messaging.new(client)\n\nresponse = messaging.update_apns_provider(\n provider_id: '',\n name: '', # optional\n enabled: false, # optional\n auth_key: '', # optional\n auth_key_id: '', # optional\n team_id: '', # optional\n bundle_id: '' # optional\n)\n\nputs response.inspect\n```\n```csharp\nusing Appwrite;\nusing Appwrite.Services;\nusing Appwrite.Models;\n\nvar client = new Client()\n .SetEndPoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .SetProject(\"\") // Your project ID\n .SetKey(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\nvar messaging = new Messaging(client);\n\nProvider result = await messaging.updateApnsProvider(\n providerId: \"\"\n name: \"\" // optional\n enabled: false // optional\n authKey: \"\" // optional\n authKeyId: \"\" // optional\n teamId: \"\" // optional\n bundleId: \"\"); // optional\n```\n```dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\nimport 'package:dart_appwrite/enums.dart';\nimport 'package:dart_appwrite/models.dart';\n\nvoid main() async { // Init SDK\n Client client = Client();\n Messaging messaging = Messaging(client);\n\n client\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n ;\n\n Future result = await messaging.updateApnsProvider(\n providerId: '',\n name: '', // optional\n enabled: false, // optional\n authKey: '', // optional\n authKeyId: '', // optional\n teamId: '', // optional\n bundleId: '', // optional\n );\n\n result\n .then((response) {\n print(response);\n }).catchError((error) {\n print(error.response);\n });\n}\n```\n```kotlin\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Messaging;\n\nClient client = new Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\nMessaging messaging = new Messaging(client);\n\nmessaging.updateApnsProvider(\n \"\", // providerId\n \"\", // name (optional)\n false, // enabled (optional)\n \"\", // authKey (optional)\n \"\", // authKeyId (optional)\n \"\", // teamId (optional)\n \"\" // bundleId (optional)\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(result);\n })\n);\n```\n```java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Messaging;\n\nClient client = new Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\nMessaging messaging = new Messaging(client);\n\nmessaging.updateApnsProvider(\n \"\", // providerId\n \"\", // name (optional)\n false, // enabled (optional)\n \"\", // authKey (optional)\n \"\", // authKeyId (optional)\n \"\", // teamId (optional)\n \"\" // bundleId (optional)\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(result);\n })\n);\n```\n```swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\") // Your secret API key\n\nlet messaging = Messaging(client)\n\nlet provider = try await messaging.updateApnsProvider(\n providerId: \"\",\n name: \"\", // optional\n enabled: xfalse, // optional\n authKey: \"\", // optional\n authKeyId: \"\", // optional\n teamId: \"\", // optional\n bundleId: \"\" // optional\n)\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::messaging::Messaging;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box> {\n let client = Client::new()\n .set_endpoint(\"https://.cloud.appwrite.io/v1\")\n .set_project(\"\")\n .set_key(\"919c2d18fb5d4...a2ae413da83346ad2\");\n\n let messaging = Messaging::new(&client);\n\n let provider = messaging.update_apns_provider(\n \"\", // providerId\n Some(\"\"), // name (optional)\n Some(false), // enabled (optional)\n Some(\"\"), // authKey (optional)\n Some(\"\"), // authKeyId (optional)\n Some(\"\"), // teamId (optional)\n Some(\"\"), // bundleId (optional)\n None, // sandbox (optional)\n ).await?;\n\n println!(\"{:?}\", provider);\n Ok(())\n}\n```\n{% /multicode %}\n{% /tabsitem %}\n{% /tabs %}\n{% /section %}"}, {"path": "docs/products/messaging/fcm", "title": "Firebase Cloud Messaging", "description": "Send push notifications to Android, Apple, or Web app with Firebase Cloud Messaging (FCM).", "content": "Firebase Cloud Messaging (FCM) lets you send push notifications to your iOS, Android, and web apps through Appwrite Messaging.\nBefore you can deliver messages, you must connect to a messaging provider.\n\n{% section #add-provider step=1 title=\"Add provider\" %}\n\nTo add FCM as a provider, navigate to **Messaging** > **Providers** > {% icon icon=\"plus\" size=\"m\" /%} **Add provider** > **Push notification**.\n\n{% only_dark %}\n![Add a FCM provider](/images/docs/messaging/providers/fcm/dark/provider.avif)\n{% /only_dark %}\n{% only_light %}\n![Add a FCM provider](/images/docs/messaging/providers/fcm/provider.avif)\n{% /only_light %}\n\nGive your provider a name > choose **FCM** > click **Save and continue**.\nThe provider will be saved to your project, but not enabled until you complete its configuration.\n{% /section %}\n{% section #configure-provider step=2 title=\"Configure provider\" %}\n\nIn the **Configure** step, you will need to provide details from your Firebase console to connect your Appwrite project.\n\nYou will need to provide the following information from the **Firebase console**.\n\n{% info title=\"Enable FCM\" %}\nFCM must be enabled on your Firebase project.\n\nHead to Firebase console -> Settings -> Project settings -> Cloud Messaging.\nIf 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).\n{% /info %}\n\nHead to **Project settings** > **Service accounts** > **Generate new private key**.\n\n{% only_dark %}\n![FCM admin key](/images/docs/messaging/providers/fcm/dark/admin-key.avif)\n{% /only_dark %}\n{% only_light %}\n![FCM admin key](/images/docs/messaging/providers/fcm/admin-key.avif)\n{% /only_light %}\n\nAfter all the relevant details are provided, you can enable the provider.\n{% /section %}\n\n{% section #configure-app step=3 title=\"Configure app\" %}\nSome additional configuration is required to enable push notifications in your mobile app.\n\n{% tabs %}\n{% tabsitem #fcm-android title=\"Android with FCM\" %}\n1. Install the `com.google.firebase:firebase-messaging` Firebase SDK.\n1. In your Firebase console, navigate to **Settings** > **General** > **Your apps** > add an **Android** app.\n1. Register and download your `google-services.json` config file.\n1. Add `google-services.json` at the root of your project.\n1. Add Google Services class path to your app-level Gradle dependencies block `\"com.google.gms:google-services:4.4.0\"`.\n1. Add Google Services plugin to your app-level Gradle in the plugins block as `\"com.google.gms.google-services\"`.\n1. Add notification handler service to `AndroidManifest.xml` inside the application tag, alongside other activities. Find an example of this service in the [Send push notification](/docs/products/messaging/send-push-notifications#add-targets) journey.\n```xml\n\" android:exported=\"false\">\n \n \n \n\n```\n{% /tabsitem %}\n{% tabsitem #fcm-ios title=\"iOS with FCM\" %}\n1. In your Firebase console, navigate to **Settings** > **General** > **Your apps** > add an **iOS** app.\n1. Register and download your `GoogleService-Info.plist` and add it to the root of your project.\n1. Head to **Apple Developer Member Center** > **Program resources** > **Certificates, Identifiers & Profiles** > **Keys**. The key needs **Apple Push Notification Service** enabled.\n1. Create a new key, note down the key ID and download your key.\n1. In Firebase console, go to *Settings** > **Cloud Messaging** > **APNs authentication key** > click **Upload**. Upload your key here.\n1. Add push notification capability to your app by clicking your root-level app in XCode > **Signing & Capabilities** > {% icon icon=\"plus\" size=\"m\" /%} Capabilities > Search for **Push Notifications**.\n1. If using SwiftUI, disable swizzling by setting `FirebaseAppDelegateProxyEnabled` to `NO` in your `Info.plist`.\n{% /tabsitem %}\n{% tabsitem #fcm-flutter title=\"Flutter with FCM\" %}\n1. Install the [Firebase CLI](https://firebase.google.com/docs/cli) and [FlutterFire CLI](https://pub.dev/packages/flutterfire_cli).\n1. From your Flutter project directory, configure Firebase by running `flutterfire configure`.\n1. Add the Firebase messaging plugin to your Flutter project with `flutter pub add firebase_messaging`.\n1. Add the Firebase core plugin if not already added with `flutter pub add firebase_core`.\n1. Initialize Firebase in your `lib/main.dart` file:\n```dart\nimport 'package:flutter/widgets.dart';\nimport 'package:firebase_core/firebase_core.dart';\nimport 'firebase_options.dart';\n\nvoid main() async {\n WidgetsFlutterBinding.ensureInitialized();\n await Firebase.initializeApp(\n options: DefaultFirebaseOptions.currentPlatform,\n );\n runApp(MyApp());\n}\n```\n1. **For iOS**:\n - 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)\n - Upload your APNs authentication key to Firebase console under **Cloud Messaging** settings\n - Request notification permission at runtime using `FirebaseMessaging.instance.requestPermission()`\n1. **For Android**: FCM requires devices running Android 5.0 or higher with Google Play services installed.\n1. **For Web**: Add a `firebase-messaging-sw.js` file in your `web/` directory that imports the Firebase messaging SDK and handles background messages.\n{% /tabsitem %}\n{% /tabs %}\n\n{% /section %}\n\n{% section #test-provider step=4 title=\"Test provider\" %}\nPush notification requires special handling on the client side. Follow the [Send push notification](/docs/products/messaging/send-push-notifications) flow to test your provider.\n{% /section %}\n\n\n{% section #manage-provider step=5 title=\"Manage provider\" %}\n{% tabs %}\n{% tabsitem #console title=\"Console\" %}\nYou can update or delete a provider in the Appwrite Console.\n\nNavigate to **Messaging** > **Providers** > click your provider.\nIn the settings, you can update a provider's configuration or delete the provider.\n{% /tabsitem %}\n\n{% tabsitem #server-sdk title=\"Server SDK\" %}\nTo update or delete providers programmatically, use an [Appwrite Server SDK](/docs/sdks#server).\n\n{% multicode %}\n\n```server-nodejs\nconst sdk = require('node-appwrite');\n\n// Init SDK\nconst client = new sdk.Client();\n\nconst messaging = new sdk.Messaging(client);\n\nclient\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n;\n\nconst provider = await messaging.updateFCMProvider(\n '', // providerId\n '', // name (optional)\n false, // enabled (optional)\n {} // serviceAccountJSON (optional)\n );\n```\n```deno\nimport * as sdk from \"npm:node-appwrite\";\n\n// Init SDK\nlet client = new sdk.Client();\n\nlet messaging = new sdk.Messaging(client);\n\nclient\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n;\n\nconst provider = await messaging.updateFCMProvider(\n '', // providerId\n '', // name (optional)\n false, // enabled (optional)\n {} // serviceAccountJSON (optional)\n );\n```\n```php\nsetEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n ->setProject('') // Your project ID\n ->setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n;\n\n$messaging = new Messaging($client);\n\n$result = $messaging->updateFCMProvider(\n providerId: '',\n name: '', // optional\n enabled: false, // optional\n serviceAccountJSON: [] // optional\n);\n```\n```python\nfrom appwrite.client import Client\nfrom appwrite.services.messaging import Messaging\n\nclient = Client()\n\n(client\n .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('') # Your project ID\n .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key\n)\n\nmessaging = Messaging(client)\n\nresult = messaging.update_fcm_provider(\n provider_id = '',\n name = '', # optional\n enabled = False, # optional\n service_account_json = {} # optional\n)\n```\n```ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('') # Your project ID\n .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key\n\nmessaging = Messaging.new(client)\n\nresponse = messaging.update_fcm_provider(\n provider_id: '',\n name: '', # optional\n enabled: false, # optional\n service_account_json: {} # optional\n)\n\nputs response.inspect\n```\n```csharp\nusing Appwrite;\nusing Appwrite.Services;\nusing Appwrite.Models;\n\nvar client = new Client()\n .SetEndPoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .SetProject(\"\") // Your project ID\n .SetKey(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\nvar messaging = new Messaging(client);\n\nProvider result = await messaging.UpdateFCMProvider(\n providerId: \"\"\n name: \"\" // optional\n enabled: false // optional\n serviceAccountJSON: [object]); // optional\n```\n```dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\nimport 'package:dart_appwrite/enums.dart';\nimport 'package:dart_appwrite/models.dart';\n\nvoid main() async { // Init SDK\n Client client = Client();\n Messaging messaging = Messaging(client);\n\n client\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n ;\n\n Future result = await messaging.updateFCMProvider(\n providerId: '',\n name: '', // optional\n enabled: false, // optional\n serviceAccountJSON: {}, // optional\n );\n\n result\n .then((response) {\n print(response);\n }).catchError((error) {\n print(error.response);\n });\n}\n```\n```kotlin\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Messaging;\n\nClient client = new Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\nMessaging messaging = new Messaging(client);\n\nmessaging.updateFCMProvider(\n \"\", // providerId\n \"\", // name (optional)\n false, // enabled (optional)\n mapOf( \"a\" to \"b\" ) // serviceAccountJSON (optional)\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(result);\n })\n);\n```\n```java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Messaging;\n\nClient client = new Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\nMessaging messaging = new Messaging(client);\n\nmessaging.updateFCMProvider(\n \"\", // providerId\n \"\", // name (optional)\n false, // enabled (optional)\n mapOf( \"a\" to \"b\" ) // serviceAccountJSON (optional)\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(result);\n })\n);\n```\n```swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\") // Your secret API key\n\nlet messaging = Messaging(client)\n\nlet provider = try await messaging.updateFCMProvider(\n providerId: \"\",\n name: \"\", // optional\n enabled: xfalse, // optional\n serviceAccountJSON: [:] // optional\n)\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::messaging::Messaging;\nuse serde_json::json;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box> {\n let client = Client::new()\n .set_endpoint(\"https://.cloud.appwrite.io/v1\")\n .set_project(\"\")\n .set_key(\"919c2d18fb5d4...a2ae413da83346ad2\");\n\n let messaging = Messaging::new(&client);\n\n let provider = messaging.update_fcm_provider(\n \"\", // providerId\n Some(\"\"), // name (optional)\n Some(false), // enabled (optional)\n Some(json!({})), // serviceAccountJSON (optional)\n ).await?;\n\n println!(\"{:?}\", provider);\n Ok(())\n}\n```\n{% /multicode %}\n{% /tabsitem %}\n{% /tabs %}\n{% /section %}"}, {"path": "docs/products/messaging/mailgun", "title": "Mailgun", "description": "Send emails to your Appwrite users using Mailgun and Appwrite Messaging.", "content": "Mailgun lets you send customized email messages to your users.\nThese emails can be sent immediately or scheduled.\nYou can send emails for purposes like reminders, promotions, announcements, and even custom authentication flows.\n\n{% section #add-provider step=1 title=\"Add provider\" %}\nTo add Mailgun as a provider, navigate to **Messaging** > **Providers** > {% icon icon=\"plus\" size=\"m\" /%} **Add provider** > **Email**.\n{% only_dark %}\n![Add a SMTP provider](/images/docs/messaging/providers/mailgun/dark/add-mailgun.avif)\n{% /only_dark %}\n{% only_light %}\n![Add a SMTP provider](/images/docs/messaging/providers/mailgun/add-mailgun.avif)\n{% /only_light %}\n\nGive your provider a name > choose **Mailgun** > click **Save and continue**.\nThe provider will be saved to your project, but not enabled until you complete its configuration.\n{% /section %}\n{% section #configure-provider step=2 title=\"Configure provider\" %}\n\nIn the **Configure** step, you will need to provide details from your Mailgun dashboard to connect your Appwrite project.\n\n\n{% only_dark %}\n![Configure SMTP provider](/images/docs/messaging/providers/mailgun/dark/configure-mailgun.avif)\n{% /only_dark %}\n{% only_light %}\n![Configure SMTP provider](/images/docs/messaging/providers/mailgun/configure-mailgun.avif)\n{% /only_light %}\n\nYou will need to provide the following information from your **Mailgun dashboard**.\n\n{% table %}\n* Field name\n*\n---\n* API key\n* Head to Profile -> API Security -> Add new key.\n---\n* Domain\n* Head to Sending -> Domains -> Add new domain.\nFollow [Mailgun's instructions](https://help.mailgun.com/hc/en-us/articles/360026833053-Domain-Verification-Walkthrough) to verify the domain name.\n---\n* EU region\n* Enable the EU region setting if your domain is within the European Union.\n---\n* Sender email\n* The provider sends emails from this sender email. The sender email needs to be an email under the configured domain.\n---\n* Sender name\n* The sender name that appears in the emails sent from this provider.\n---\n* Reply-to email\n* 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.\n---\n* Reply-to name\n* The reply-to name that appears in the emails sent from this provider.\n{% /table %}\n\nAfter adding the following details, click **Save and continue** to enable the provider.\n{% /section %}\n\n{% section #test-provider step=3 title=\"Test provider\" %}\nBefore sending your first message,\nmake sure you've configured [a topic](/docs/products/messaging/topics) and [a target](/docs/products/messaging/targets) to send messages to.\n{% tabs %}\n{% tabsitem #console title=\"Console\" %}\nTo send a test message, navigate to **Messaging** > **Messages** > {% icon icon=\"plus\" size=\"m\" /%} **Create message** > **Email**.\n{% only_dark %}\n![Create email message](/images/docs/messaging/messages/dark/create-email-message.avif)\n{% /only_dark %}\n{% only_light %}\n![Create email message](/images/docs/messaging/messages/create-email-message.avif)\n{% /only_light %}\n\nAdd your message and in the targets step, select one of your test targets. Set the schedule to **Now** and click **Send**.\n\nVerify that you can receive the message in your inbox. If not, check for logs in the Appwrite Console or in your provider's logs.\n{% /tabsitem %}\n\n{% tabsitem #server-sdk title=\"Server SDK\" %}\nTo send a message programmatically, use an [Appwrite Server SDK](/docs/sdks#server).\n{% multicode %}\n```server-nodejs\nconst sdk = require('node-appwrite');\n\n// Init SDK\nconst client = new sdk.Client();\n\nconst messaging = new sdk.Messaging(client);\n\nclient\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n .setKey('') // Your secret API key\n;\n\nconst message = await messaging.createEmail({\n messageId: '',\n subject: '',\n content: ''\n});\n```\n```deno\nimport * as sdk from \"npm:node-appwrite\";\n\n// Init SDK\nlet client = new sdk.Client();\n\nlet messaging = new sdk.Messaging(client);\n\nclient\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n .setKey('') // Your secret API key\n;\n\nconst message = await messaging.createEmail({\n messageId: '',\n subject: '',\n content: ''\n});\n```\n```php\nsetEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n ->setProject('') // Your project ID\n ->setKey('') // Your secret API key\n;\n\n$messaging = new Messaging($client);\n\n$result = $messaging->createEmail('', '', '');\n```\n```python\nfrom appwrite.client import Client\n\nclient = Client()\n\n(client\n .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('') # Your project ID\n .set_key('') # Your secret API key\n)\n\nmessaging = Messaging(client)\n\nresult = messaging.create_email('', '', '')\n```\n```ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('') # Your project ID\n .set_key('') # Your secret API key\n\nmessaging = Messaging.new(client)\n\nresponse = messaging.create_email(message_id: '', subject: '', content: '')\n\nputs response.inspect\n```\n```csharp\nusing Appwrite;\nusing Appwrite.Services;\nusing Appwrite.Models;\nusing Appwrite.Enums;\nusing Appwrite.Enums;\nusing Appwrite.Enums;\n\nvar client = new Client()\n .SetEndPoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .SetProject(\"\") // Your project ID\n .SetKey(\"\"); // Your secret API key\n\nvar messaging = new Messaging(client);\n\nMessage result = await messaging.CreateEmail(\n messageId: \"\",\n subject: \"\",\n content: \"\");\n```\n```dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\nimport 'package:dart_appwrite/enums.dart';\nimport 'package:dart_appwrite/models.dart';\n\nvoid main() async { // Init SDK\n Client client = Client();\n Messaging messaging = Messaging(client);\n\n client\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n .setKey('') // Your secret API key\n ;\n\n Future result = await messaging.createEmail(\n messageId:'' ,\n subject:'' ,\n content:'' ,\n );\n\n result\n .then((response) {\n print(response);\n }).catchError((error) {\n print(error.response);\n });\n}\n```\n```kotlin\nimport io.appwrite.Client\nimport io.appwrite.coroutines.CoroutineCallback\nimport io.appwrite.services.Messaging\n\nval client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n .setKey(\"\") // Your secret API key\n\nval messaging = Messaging(client)\n\nval response = messaging.createEmail(\n messageId = \"\",\n subject = \"\",\n content = \"\",\n)\n```\n```java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Messaging;\n\nClient client = new Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n .setKey(\"\"); // Your secret API key\n\nMessaging messaging = new Messaging(client);\n\nmessaging.createEmail(\n \"\",\n \"\",\n \"\",\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(result);\n })\n);\n```\n```swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n .setKey(\"\") // Your secret API key\n\nlet messaging = Messaging(client)\n\nlet message = try await messaging.createEmail(\n messageId: \"\",\n subject: \"\",\n content: \"\"\n)\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::messaging::Messaging;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box> {\n let client = Client::new()\n .set_endpoint(\"https://.cloud.appwrite.io/v1\")\n .set_project(\"\")\n .set_key(\"\");\n\n let messaging = Messaging::new(&client);\n\n let message = messaging.create_email(\n \"\", // messageId\n \"\", // subject\n \"\", // content\n None, // topics (optional)\n None, // users (optional)\n None, // targets (optional)\n None, // cc (optional)\n None, // bcc (optional)\n None, // attachments (optional)\n None, // draft (optional)\n None, // html (optional)\n None, // scheduledAt (optional)\n ).await?;\n\n println!(\"{:?}\", message);\n Ok(())\n}\n```\n{% /multicode %}\n{% /tabsitem %}\n{% /tabs %}\n\nYou can follow the [Send email messages](/docs/products/messaging/send-push-notifications) journey to send your first push notification and test your provider.\n{% /section %}\n\n{% section #manage-provider step=4 title=\"Manage provider\" %}\n{% tabs %}\n{% tabsitem #console title=\"Console\" %}\nYou can update or delete a provider in the Appwrite Console.\n\nNavigate to **Messaging** > **Providers** > click your provider.\nIn the settings, you can update a provider's configuration or delete the provider.\n{% /tabsitem %}\n\n{% tabsitem #server-sdk title=\"Server SDK\" %}\nTo update or delete providers programmatically, use an [Appwrite Server SDK](/docs/sdks#server).\n{% multicode %}\n```server-nodejs\nconst sdk = require('node-appwrite');\n\n// Init SDK\nconst client = new sdk.Client();\n\nconst messaging = new sdk.Messaging(client);\n\nclient\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n .setKey('') // Your secret API key\n;\n\n// update provider\nmessaging.updateSendgridProvider(\n '',\n '',\n '',\n '',\n '',\n '',\n '',\n '',\n '',\n '',\n).then(function (response) {\n console.log(response);\n}, function (error) {\n console.log(error);\n});\n\n// delete provider\nmessaging.deleteProvider('')\n.then(function (response) {\n console.log(response);\n}, function (error) {\n console.log(error);\n});\n```\n```deno\nimport * as sdk from \"npm:node-appwrite\";\n\n// Init SDK\nlet client = new sdk.Client();\n\nlet messaging = new sdk.Messaging(client);\n\n// update provider\nmessaging.updateSendgridProvider(\n '',\n '',\n '',\n '',\n '',\n '',\n '',\n '',\n '',\n '',\n).then(function (response) {\n console.log(response);\n}, function (error) {\n console.log(error);\n});\n\n// delete provider\nmessaging.deleteProvider('')\n.then(function (response) {\n console.log(response);\n}, function (error) {\n console.log(error);\n});\n```\n```php\nsetEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n ->setProject('') // Your project ID\n ->setKey('') // Your secret API key\n;\n\n$messaging = new Messaging($client);\n\n$result = $messaging->updateSendgridProvider(\n '',\n '',\n '',\n '',\n '',\n '',\n '',\n '',\n '',\n '',\n);\n```\n```python\nfrom appwrite.client import Client\n\nclient = Client()\n\n(client\n .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('') # Your project ID\n .set_key('') # Your secret API key\n)\n\nmessaging = Messaging(client)\n\nresult = messaging.update_sendgrid_provider(\n '',\n '',\n '',\n '',\n '',\n '',\n '',\n '',\n '',\n '',\n)\n```\n```ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('') # Your project ID\n .set_key('') # Your secret API key\n\nmessaging = Messaging.new(client)\n\nresponse = messaging.update_sendgrid_provider(\n provider_id: \"\",\n name: \"\",\n api_key: \"\",\n domain: \"\",\n isEuRegion: \"\",\n from_name: \"\",\n from_email: \"\",\n reply_to_name: \"\",\n reply_to_email: \"\",\n enabled: \"\",\n)\n\nputs response.inspect\n```\n```csharp\nusing Appwrite;\nusing Appwrite.Services;\nusing Appwrite.Models;\nusing Appwrite.Enums;\n\nvar client = new Client()\n .SetEndPoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .SetProject(\"\") // Your project ID\n .SetKey(\"\"); // Your secret API key\n\nvar messaging = new Messaging(client);\n\nProvider result = await messaging.UpdateSendgridProvider(\n providerId: \"\",\n name: \"\",\n apiKey: \"\",\n domain: \"\",\n isEuRegion: \"\",\n fromName: \"\",\n fromEmail: \"\",\n replyToName: \"\",\n replyToEmail: \"\",\n enabled: \"\",\n);\n```\n```dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\nimport 'package:dart_appwrite/enums.dart';\nimport 'package:dart_appwrite/models.dart';\n\nvoid main() { // Init SDK\n Client client = Client();\n Messaging messaging = Messaging(client);\n\n client\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n .setKey('') // Your secret API key\n ;\n\n Future result = messaging.updateSendgridProvider(\n providerId: \"\",\n name: \"\",\n apiKey: \"\",\n domain: \"\",\n isEuRegion: \"\",\n fromName: \"\",\n fromEmail: \"\",\n replyToName: \"\",\n replyToEmail: \"\",\n enabled: \"\",\n );\n\n result\n .then((response) {\n print(response);\n }).catchError((error) {\n print(error.response);\n });\n}\n```\n```kotlin\nimport io.appwrite.Client\nimport io.appwrite.coroutines.CoroutineCallback\nimport io.appwrite.services.Messaging\n\nval client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n .setKey(\"\") // Your secret API key\n\nval messaging = Messaging(client)\n\nval response = messaging.updateSendgridProvider(\n providerId = \"\",\n name = \"\",\n apiKey = \"\",\n domain = \"\",\n isEuRegion = \"\",\n fromName = \"\",\n fromEmail = \"\",\n replyToName = \"\",\n replyToEmail = \"\",\n enabled = \"\",\n)\n\n```\n```java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Messaging;\n\nClient client = new Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n .setKey(\"\"); // Your secret API key\n\nMessaging messaging = new Messaging(client);\n\nmessaging.updateSendgridProvider(\n \"\",\n \"\",\n \"\",\n \"\",\n \"\",\n \"\",\n \"\",\n \"\",\n \"\",\n \"\",\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(result);\n })\n);\n```\n```swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n .setKey(\"\") // Your secret API key\n\nlet messaging = Messaging(client)\n\nlet provider = try await messaging.updateSendgridProvider(\n providerId: \"\",\n name: \"\",\n apiKey: \"\",\n domain: \"\",\n isEuRegion: \"\",\n fromName: \"\",\n fromEmail: \"\",\n replyToName: \"\",\n replyToEmail: \"\",\n enabled: \"\",\n)\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::messaging::Messaging;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box> {\n let client = Client::new()\n .set_endpoint(\"https://.cloud.appwrite.io/v1\")\n .set_project(\"\")\n .set_key(\"\");\n\n let messaging = Messaging::new(&client);\n\n let provider = messaging.update_mailgun_provider(\n \"\", // providerId\n Some(\"\"), // name (optional)\n Some(\"\"), // apiKey (optional)\n Some(\"\"), // domain (optional)\n Some(false), // isEuRegion (optional)\n Some(true), // enabled (optional)\n Some(\"\"), // fromName (optional)\n Some(\"\"), // fromEmail (optional)\n Some(\"\"), // replyToName (optional)\n Some(\"\"), // replyToEmail (optional)\n ).await?;\n\n println!(\"{:?}\", provider);\n Ok(())\n}\n```\n{% /multicode %}\n{% /tabsitem %}\n{% /tabs %}\n{% /section %}"}, {"path": "docs/products/messaging/messages", "title": "Messages", "description": "Learn about Appwrite messages, the different types of messages, what can be sent in different message types.", "content": "Each time you send or schedule a push notification, email, or SMS text, it's recorded in Appwrite\nas a **message** is displayed in the **Messages** tab.\n\n{% only_dark %}\n![Add a target](/images/docs/messaging/messages/dark/messages-overview.avif)\n{% /only_dark %}\n{% only_light %}\n![Add a target](/images/docs/messaging/messages/messages-overview.avif)\n{% /only_light %}\n\n# Messages {% #messages %}\n\nEach message displays with the following information.\n\n{% table %}\n* Column {% width=200 %}\n* Description\n---\n* Message ID\n* The unique ID of the message.\n---\n* Description\n* The developer defined description of the message. End users do not see this description.\n---\n* Message\n* The message delivered to end users.\n---\n* Type\n* Type of message, either `Push`, `Email`, and `SMS`.\n---\n* Status\n* Indicates the status of the message, can be one of `draft`, `scheduled`, `processing`, `failed`, `success`.\n---\n* Scheduled at\n* Indicates the scheduled delivery time of the message.\n---\n* Delivered at\n* Indicates the time at which the message was successfully delivered.\n{% /table %}\n\n\n# Messages types {% #messages-types %}\n\nThere are three types of messages\n\n{% table %}\n* Message type {% width=200 %}\n* Description\n---\n* Push notifications\n* Push notifications are alerts that show up on a user device's notification center.\nThis can be used to deliver messages to the user whether their application is open or not.\n---\n* Emails\n* Emails let you deliver rich content to a users' inbox.\nAppwrite allows you to send customized HTML email messages so you can include links, styling, and more.\n---\n* SMS\n* SMS messages let you deliver text messages to your user's phone.\nThis helps you reach your user, even when their device do not have internet access.\n{% /table %}\n\n# Messages lifecycle {% #messages-lifecycle %}\nMessages can begin as a `draft`, or proceed directly to `processing` if it's sent immediately.\nIf the message is scheduled to be sent later, its status is set to `scheduled`, then to `processing` at schedule time.\nAfter attempted delivery, it is marked as `sent` or `failed` depending on if the message was successfully delivered.\n{% only_dark %}\n![Message lifecycle](/images/docs/messaging/dark/message-status.avif)\n{% /only_dark %}\n{% only_light %}\n![Message lifecycle](/images/docs/messaging/message-status.avif)\n{% /only_light %}\n\n# Choosing a message type {% #choosing-a-message-type %}\nChoosing the right type of notification to reach your audience is important for your app's success.\nHere are some common factors to consider when deciding what type of message should be sent.\n\n{% table %}\n* Message type {% width=200 %}\n* Description\n---\n* Time-sensitive messages\n* Push notifications or SMS messages are ideal for time-sensitive messages, as they are typically checked frequently and opened within minutes, ensuring prompt attention.\n---\n* Guaranteed delivery\n* 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.\n---\n* Content-rich messages\n* 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.\n---\n* Increasing engagement\n* Push notifications are effective for increasing engagement with users, as they can be clicked on to link directly to your app, promoting immediate interaction.\n---\n* Accessibility and reach\n* 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.\n{% /table %}\n\n# Composing messages {% #composing-messages %}\nDifferent types of messages have different content and configurable options.\nHere are the different components that make up a message.\n\n{% tabs %}\n{% tabsitem #push-notification title=\"Push notifications\" %}\n{% table %}\n* Parameter {% width=100 %}\n* Required {% width=100 %}\n* Description\n---\n* `messageId`\n* required\n* The title of the push notification. This is the headline text that recipients see first.\n---\n* `title`\n* optional\n* The title of the push notification. This is the headline text that recipients see first. Can be omitted for background notifications.\n---\n* `body`\n* optional\n* The main content or body of the push notification. Provides the details or message you want to convey. Can be omitted for background notifications.\n---\n* `data`\n* optional\n* 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.\n---\n* `action`\n* optional\n* Specifies which activity or view controller to open within the app when the notification is tapped.\n---\n* `icon`\n* optional\n* Sets the icon of the notification, used only for Android devices. This can help in branding the notification.\n---\n* `sound`\n* optional\n* 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.\n---\n* `color`\n* optional\n* Specifies a color tint for the notification icon, used only for Android devices. This can be used to align with brand colors.\n---\n* `tag`\n* optional\n* Can be used to replace an existing notification with the same tag, used only for Android devices. Useful for updating or canceling notifications.\n---\n* `badge`\n* optional\n* 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.\n---\n* `contentAvailable`\n* optional\n* 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.\n---\n* `critical`\n* optional\n* 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.\n---\n* `priority`\n* optional\n* 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.\n---\n* `draft`\n* optional\n* If the message is a draft, can be `true` or `false`.\n---\n* `scheduledAt`\n* optional\n* An ISO date time string specifying when the push notification should be sent.\n{% /table %}\n\n{% /tabsitem %}\n{% tabsitem #emails title=\"Emails\" %}\n\n{% table %}\n* Parameter {% width=100 %}\n* Required {% width=100 %}\n* Description\n---\n* `subject`\n* required\n* The subject line of the email. This is what recipients see first in their inbox.\n---\n* `content`\n* required\n* The main content of the email. This can be plain text or HTML, depending on the `html` flag.\n---\n* `cc`\n* optional\n* An array of target IDs to be included in the carbon copy (CC) field. These recipients can see each other's email addresses.\n---\n* `bcc`\n* optional\n* An array of target IDs to be included in the blind carbon copy (BCC) field. These recipients cannot see each other's email addresses.\n---\n* `html`\n* optional\n* A boolean indicating whether the `content` is in HTML format. This allows for rich text, links, and styling in the email content.\n---\n* `draft`\n* optional\n* If the message is a draft, can be `true` or `false`.\n---\n* `scheduledAt`\n* optional\n* An ISO date time string specifying when the email should be sent.\n{% /table %}\n\n{% /tabsitem %}\n{% tabsitem #sms title=\"SMS\" %}\n{% table %}\n* Parameter {% width=100 %}\n* Required {% width=100 %}\n* Description\n---\n* `content`\n* required\n* The main content of the SMS. This should be concise and clear, as SMS messages have character limits.\n---\n* `draft`\n* optional\n* If the message is a draft, can be `true` or `false`.\n---\n* `scheduledAt`\n* optional\n* An ISO date time string specifying when the SMS should be sent.\n{% /table %}\n{% /tabsitem %}\n{% /tabs %}\n\n# Sending a message {% #create-a-message %}\nYou can create a message with a Server SDK. You can send a push notification like this.\n{% multicode %}\n```server-nodejs\nconst sdk = require('node-appwrite');\n\n// Init SDK\nconst client = new sdk.Client();\n\nconst messaging = new sdk.Messaging(client);\n\nclient\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n;\n\nconst message = await messaging.createPush(\n '', // messageId\n '', // title\n '<BODY>', // body\n [], // topics (optional)\n [], // users (optional)\n [], // targets (optional)\n {}, // data (optional)\n '<ACTION>', // action (optional)\n '<ICON>', // icon (optional)\n '<SOUND>', // sound (optional)\n '<COLOR>', // color (optional)\n '<TAG>', // tag (optional)\n 1, // badge (optional)\n false, // contentAvailable (optional)\n false, // critical (optional)\n 'normal', // priority (optional)\n true, // draft (optional)\n '' // scheduledAt (optional)\n );\n```\n```deno\nimport * as sdk from \"npm:node-appwrite\";\n\n// Init SDK\nlet client = new sdk.Client();\n\nlet messaging = new sdk.Messaging(client);\n\nclient\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n;\n\nconst message = await messaging.createPush(\n '<MESSAGE_ID>', // messageId\n '<TITLE>', // title\n '<BODY>', // body\n [], // topics (optional)\n [], // users (optional)\n [], // targets (optional)\n {}, // data (optional)\n '<ACTION>', // action (optional)\n '<ICON>', // icon (optional)\n '<SOUND>', // sound (optional)\n '<COLOR>', // color (optional)\n '<TAG>', // tag (optional)\n 1, // badge (optional)\n false, // contentAvailable (optional)\n false, // critical (optional)\n 'normal', // priority (optional)\n true, // draft (optional)\n '' // scheduledAt (optional)\n );\n```\n```php\n<?php\n\nuse Appwrite\\Client;\nuse Appwrite\\Services\\Messaging;\n\n$client = new Client();\n\n$client\n ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n ->setProject('<PROJECT_ID>') // Your project ID\n ->setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n;\n\n$messaging = new Messaging($client);\n\n$result = $messaging->createPush(\n messageId: '<MESSAGE_ID>',\n title: '<TITLE>',\n body: '<BODY>',\n topics: [], // optional\n users: [], // optional\n targets: [], // optional\n data: [], // optional\n action: '<ACTION>', // optional\n icon: '<ICON>', // optional\n sound: '<SOUND>', // optional\n color: '<COLOR>', // optional\n tag: '<TAG>', // optional\n badge: 1, // optional\n contentAvailable: false, // optional\n critical: false, // optional\n priority: 'normal', // optional\n draft: true, // optional\n scheduledAt: '' // optional\n);\n```\n```python\nfrom appwrite.client import Client\nfrom appwrite.services.messaging import Messaging\n\nclient = Client()\n\n(client\n .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('<PROJECT_ID>') # Your project ID\n .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key\n)\n\nmessaging = Messaging(client)\n\nresult = messaging.create_push(\n message_id = '<MESSAGE_ID>',\n title = '<TITLE>',\n body = '<BODY>',\n topics = [], # optional\n users = [], # optional\n targets = [], # optional\n data = {}, # optional\n action = '<ACTION>', # optional\n icon = '<ICON>', # optional\n sound = '<SOUND>', # optional\n color = '<COLOR>', # optional\n tag = '<TAG>', # optional\n badge = 1, # optional\n content_available = False, # optional\n critical = False, # optional\n priority = 'normal', # optional\n draft = True, # optional\n scheduled_at = '' # optional\n)\n```\n```ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('<PROJECT_ID>') # Your project ID\n .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key\n\nmessaging = Messaging.new(client)\n\nresponse = messaging.create_push(\n message_id: '<MESSAGE_ID>',\n title: '<TITLE>',\n body: '<BODY>',\n topics: [], # optional\n users: [], # optional\n targets: [], # optional\n data: {}, # optional\n action: '<ACTION>', # optional\n icon: '<ICON>', # optional\n sound: '<SOUND>', # optional\n color: '<COLOR>', # optional\n tag: '<TAG>', # optional\n badge: '<BADGE>', # optional\n content_available: false, # optional\n critical: false, # optional\n priority: 'normal', # optional\n draft: true, # optional\n scheduled_at: '' # optional\n)\n\nputs response.inspect\n```\n```csharp\nusing Appwrite;\nusing Appwrite.Services;\nusing Appwrite.Models;\n\nvar client = new Client()\n .SetEndPoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .SetProject(\"<PROJECT_ID>\") // Your project ID\n .SetKey(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\nvar messaging = new Messaging(client);\n\nMessage result = await messaging.CreatePush(\n messageId: \"<MESSAGE_ID>\",\n title: \"<TITLE>\",\n body: \"<BODY>\"\n topics: new List<string> {}, // optional\n users: new List<string> {}, // optional\n targets: new List<string> {}, // optional\n data: [object] // optional\n action: \"<ACTION>\", // optional\n icon: \"<ICON>\", // optional\n sound: \"<SOUND>\", // optional\n color: \"<COLOR>\", // optional\n tag: \"<TAG>\", // optional\n badge: 1, // optional\n contentAvailable: false, // optional\n critical: false, // optional\n priority: \"normal\", // optional\n draft: true, // optional\n scheduledAt: \"\" // optional\n);\n```\n```dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\nimport 'package:dart_appwrite/enums.dart';\nimport 'package:dart_appwrite/models.dart';\n\nvoid main() async { // Init SDK\n Client client = Client();\n Messaging messaging = Messaging(client);\n\n client\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n ;\n\n Future result = await messaging.createPush(\n messageId: '<MESSAGE_ID>',\n title: '<TITLE>',\n body: '<BODY>',\n topics: [], // optional\n users: [], // optional\n targets: [], // optional\n data: {}, // optional\n action: '<ACTION>', // optional\n icon: '<ICON>', // optional\n sound: '<SOUND>', // optional\n color: '<COLOR>', // optional\n tag: '<TAG>', // optional\n badge: 1, // optional\n content_available: false, // optional\n critical: false, // optional\n priority: 'normal', // optional\n draft: true, // optional\n scheduledAt: '', // optional\n );\n\n result\n .then((response) {\n print(response);\n }).catchError((error) {\n print(error.response);\n });\n}\n```\n```kotlin\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Messaging;\n\nClient client = new Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\nMessaging messaging = new Messaging(client);\n\nmessaging.createPush(\n \"<MESSAGE_ID>\", // messageId\n \"<TITLE>\", // title\n \"<BODY>\", // body\n listOf(), // topics (optional)\n listOf(), // users (optional)\n listOf(), // targets (optional)\n mapOf( \"a\" to \"b\" ), // data (optional)\n \"<ACTION>\", // action (optional)\n \"<ICON>\", // icon (optional)\n \"<SOUND>\", // sound (optional)\n \"<COLOR>\", // color (optional)\n \"<TAG>\", // tag (optional)\n 1, // badge (optional)\n true, // draft (optional)\n \"\" // scheduledAt (optional)\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(result);\n })\n);\n```\n```java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Messaging;\n\nClient client = new Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\nMessaging messaging = new Messaging(client);\n\nmessaging.createPush(\n \"<MESSAGE_ID>\", // messageId\n \"<TITLE>\", // title\n \"<BODY>\", // body\n listOf(), // topics (optional)\n listOf(), // users (optional)\n listOf(), // targets (optional)\n mapOf( \"a\" to \"b\" ), // data (optional)\n \"<ACTION>\", // action (optional)\n \"<ICON>\", // icon (optional)\n \"<SOUND>\", // sound (optional)\n \"<COLOR>\", // color (optional)\n \"<TAG>\", // tag (optional)\n 1, // badge (optional)\n true, // draft (optional)\n \"\" // scheduledAt (optional)\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(result);\n })\n);\n```\n```swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\") // Your secret API key\n\nlet messaging = Messaging(client)\n\nlet message = try await messaging.createPush(\n messageId: \"<MESSAGE_ID>\",\n title: \"<TITLE>\",\n body: \"<BODY>\",\n topics: [], // optional\n users: [], // optional\n targets: [], // optional\n data: [:], // optional\n action: \"<ACTION>\", // optional\n icon: \"<ICON>\", // optional\n sound: \"<SOUND>\", // optional\n color: \"<COLOR>\", // optional\n tag: \"<TAG>\", // optional\n badge: 1, // optional\n content_available: false, // optional\n critical: false, // optional\n priority: \"normal\", // optional\n draft: true, // optional\n scheduledAt: \"\" // optional\n)\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::messaging::Messaging;\nuse appwrite::enums::MessagePriority;\nuse serde_json::json;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n let client = Client::new()\n .set_endpoint(\"https://<REGION>.cloud.appwrite.io/v1\")\n .set_project(\"<PROJECT_ID>\")\n .set_key(\"919c2d18fb5d4...a2ae413da83346ad2\");\n\n let messaging = Messaging::new(&client);\n\n let message = messaging.create_push(\n \"<MESSAGE_ID>\", // messageId\n Some(\"<TITLE>\"), // title (optional)\n Some(\"<BODY>\"), // body (optional)\n Some(vec![]), // topics (optional)\n Some(vec![]), // users (optional)\n Some(vec![]), // targets (optional)\n Some(json!({})), // data (optional)\n Some(\"<ACTION>\"), // action (optional)\n None, // image (optional)\n Some(\"<ICON>\"), // icon (optional)\n Some(\"<SOUND>\"), // sound (optional)\n Some(\"<COLOR>\"), // color (optional)\n Some(\"<TAG>\"), // tag (optional)\n Some(1), // badge (optional)\n Some(true), // draft (optional)\n None, // scheduledAt (optional)\n Some(false), // contentAvailable (optional)\n Some(false), // critical (optional)\n Some(MessagePriority::Normal), // priority (optional)\n ).await?;\n\n println!(\"{:?}\", message);\n Ok(())\n}\n```\n{% /multicode %}\n\n{% arrow_link href=\"/docs/products/messaging/send-push-notifications\" %}\nLearn more about sending a push notification\n{% /arrow_link %}\n\nYou can send an email like this.\n\n{% multicode %}\n```server-nodejs\nconst sdk = require('node-appwrite');\n\n// Init SDK\nconst client = new sdk.Client();\n\nconst messaging = new sdk.Messaging(client);\n\nclient\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n;\n\nconst message - await messaging.createEmail(\n '<MESSAGE_ID>', // messageId\n '<SUBJECT>', // subject\n '<CONTENT>', // content\n [], // topics (optional)\n [], // users (optional)\n [], // targets (optional)\n [], // cc (optional)\n [], // bcc (optional)\n true, // draft (optional)\n false, // html (optional)\n '' // scheduledAt (optional)\n );\n```\n```deno\nimport * as sdk from \"npm:node-appwrite\";\n\n// Init SDK\nlet client = new sdk.Client();\n\nlet messaging = new sdk.Messaging(client);\n\nclient\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n;\n\nconst message - await messaging.createEmail(\n '<MESSAGE_ID>', // messageId\n '<SUBJECT>', // subject\n '<CONTENT>', // content\n [], // topics (optional)\n [], // users (optional)\n [], // targets (optional)\n [], // cc (optional)\n [], // bcc (optional)\n true, // draft (optional)\n false, // html (optional)\n '' // scheduledAt (optional)\n );\n```\n```php\n<?php\n\nuse Appwrite\\Client;\nuse Appwrite\\Services\\Messaging;\n\n$client = new Client();\n\n$client\n ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n ->setProject('<PROJECT_ID>') // Your project ID\n ->setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n;\n\n$messaging = new Messaging($client);\n\n$result = $messaging->createEmail(\n messageId: '<MESSAGE_ID>',\n subject: '<SUBJECT>',\n content: '<CONTENT>',\n topics: [], // optional\n users: [], // optional\n targets: [], // optional\n cc: [], // optional\n bcc: [], // optional\n draft: true, // optional\n html: false, // optional\n scheduledAt: '' // optional\n);\n```\n```python\nfrom appwrite.client import Client\nfrom appwrite.services.messaging import Messaging\n\nclient = Client()\n\n(client\n .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('<PROJECT_ID>') # Your project ID\n .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key\n)\n\nmessaging = Messaging(client)\n\nresult = messaging.create_email(\n message_id = '<MESSAGE_ID>',\n subject = '<SUBJECT>',\n content = '<CONTENT>',\n topics = [], # optional\n users = [], # optional\n targets = [], # optional\n cc = [], # optional\n bcc = [], # optional\n draft = True, # optional\n html = False, # optional\n scheduled_at = '' # optional\n)\n```\n```ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('<PROJECT_ID>') # Your project ID\n .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key\n\nmessaging = Messaging.new(client)\n\nresponse = messaging.create_email(\n message_id: '<MESSAGE_ID>',\n subject: '<SUBJECT>',\n content: '<CONTENT>',\n topics: [], # optional\n users: [], # optional\n targets: [], # optional\n cc: [], # optional\n bcc: [], # optional\n draft: true, # optional\n html: false, # optional\n scheduled_at: '' # optional\n)\n\nputs response.inspect\n```\n```csharp\nusing Appwrite;\nusing Appwrite.Services;\nusing Appwrite.Models;\n\nvar client = new Client()\n .SetEndPoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .SetProject(\"<PROJECT_ID>\") // Your project ID\n .SetKey(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\nvar messaging = new Messaging(client);\n\nMessage result = await messaging.CreateEmail(\n messageId: \"<MESSAGE_ID>\",\n subject: \"<SUBJECT>\",\n content: \"<CONTENT>\"\n topics: new List<string> {} // optional\n users: new List<string> {} // optional\n targets: new List<string> {} // optional\n cc: new List<string> {} // optional\n bcc: new List<string> {} // optional\n draft: true // optional\n html: false // optional\n scheduledAt: \"\"); // optional\n```\n```dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\nimport 'package:dart_appwrite/enums.dart';\nimport 'package:dart_appwrite/models.dart';\n\nvoid main() { // Init SDK\n Client client = Client();\n Messaging messaging = Messaging(client);\n\n client\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n ;\n\n Future result = messaging.createEmail(\n messageId: '<MESSAGE_ID>',\n subject: '<SUBJECT>',\n content: '<CONTENT>',\n topics: [], // optional\n users: [], // optional\n targets: [], // optional\n cc: [], // optional\n bcc: [], // optional\n draft: true, // optional\n html: false, // optional\n scheduledAt: '', // optional\n );\n\n result\n .then((response) {\n print(response);\n }).catchError((error) {\n print(error.response);\n });\n}\n```\n```kotlin\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Messaging;\n\nClient client = new Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\nMessaging messaging = new Messaging(client);\n\nmessaging.createEmail(\n \"<MESSAGE_ID>\", // messageId\n \"<SUBJECT>\", // subject\n \"<CONTENT>\", // content\n listOf(), // topics (optional)\n listOf(), // users (optional)\n listOf(), // targets (optional)\n listOf(), // cc (optional)\n listOf(), // bcc (optional)\n true, // draft (optional)\n false, // html (optional)\n \"\" // scheduledAt (optional)\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(result);\n })\n);\n```\n```java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Messaging;\n\nClient client = new Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\nMessaging messaging = new Messaging(client);\n\nmessaging.createEmail(\n \"<MESSAGE_ID>\", // messageId\n \"<SUBJECT>\", // subject\n \"<CONTENT>\", // content\n listOf(), // topics (optional)\n listOf(), // users (optional)\n listOf(), // targets (optional)\n listOf(), // cc (optional)\n listOf(), // bcc (optional)\n true, // draft (optional)\n false, // html (optional)\n \"\" // scheduledAt (optional)\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(result);\n })\n);\n```\n```swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\") // Your secret API key\n\nlet messaging = Messaging(client)\n\nlet message = try await messaging.createEmail(\n messageId: \"<MESSAGE_ID>\",\n subject: \"<SUBJECT>\",\n content: \"<CONTENT>\",\n topics: [], // optional\n users: [], // optional\n targets: [], // optional\n cc: [], // optional\n bcc: [], // optional\n draft: true, // optional\n html: xfalse, // optional\n scheduledAt: \"\" // optional\n)\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::messaging::Messaging;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n let client = Client::new()\n .set_endpoint(\"https://<REGION>.cloud.appwrite.io/v1\")\n .set_project(\"<PROJECT_ID>\")\n .set_key(\"919c2d18fb5d4...a2ae413da83346ad2\");\n\n let messaging = Messaging::new(&client);\n\n let message = messaging.create_email(\n \"<MESSAGE_ID>\", // messageId\n \"<SUBJECT>\", // subject\n \"<CONTENT>\", // content\n Some(vec![]), // topics (optional)\n Some(vec![]), // users (optional)\n Some(vec![]), // targets (optional)\n Some(vec![]), // cc (optional)\n Some(vec![]), // bcc (optional)\n None, // attachments (optional)\n Some(true), // draft (optional)\n Some(false), // html (optional)\n None, // scheduledAt (optional)\n ).await?;\n\n println!(\"{:?}\", message);\n Ok(())\n}\n```\n{% /multicode %}\n\n{% arrow_link href=\"/docs/products/messaging/send-email-messages\" %}\nLearn more about sending an email\n{% /arrow_link %}\n\nYou can send an SMS message like this.\n\n{% multicode %}\n```server-nodejs\nconst sdk = require('node-appwrite');\n\n// Init SDK\nconst client = new sdk.Client();\n\nconst messaging = new sdk.Messaging(client);\n\nclient\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n;\n\nconst message = await messaging.createSms(\n '<MESSAGE_ID>', // messageId\n '<CONTENT>', // content\n [], // topics (optional)\n [], // users (optional)\n [], // targets (optional)\n true, // draft (optional)\n '' // scheduledAt (optional)\n );\n```\n```deno\nimport * as sdk from \"npm:node-appwrite\";\n\n// Init SDK\nlet client = new sdk.Client();\n\nlet messaging = new sdk.Messaging(client);\n\nclient\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n;\n\nconst message = await messaging.createSms(\n '<MESSAGE_ID>', // messageId\n '<CONTENT>', // content\n [], // topics (optional)\n [], // users (optional)\n [], // targets (optional)\n true, // draft (optional)\n '' // scheduledAt (optional)\n );\n```\n```php\n<?php\n\nuse Appwrite\\Client;\nuse Appwrite\\Services\\Messaging;\n\n$client = new Client();\n\n$client\n ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n ->setProject('<PROJECT_ID>') // Your project ID\n ->setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n;\n\n$messaging = new Messaging($client);\n\n$result = $messaging->createSms(\n messageId: '<MESSAGE_ID>',\n content: '<CONTENT>',\n topics: [], // optional\n users: [], // optional\n targets: [], // optional\n draft: true, // optional\n scheduledAt: '' // optional\n);\n```\n```python\nfrom appwrite.client import Client\nfrom appwrite.services.messaging import Messaging\n\nclient = Client()\n\n(client\n .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('<PROJECT_ID>') # Your project ID\n .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key\n)\n\nmessaging = Messaging(client)\n\nresult = messaging.create_sms(\n message_id = '<MESSAGE_ID>',\n content = '<CONTENT>',\n topics = [], # optional\n users = [], # optional\n targets = [], # optional\n draft = True, # optional\n scheduled_at = '' # optional\n)\n```\n```ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('<PROJECT_ID>') # Your project ID\n .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key\n\nmessaging = Messaging.new(client)\n\nresponse = messaging.create_sms(\n message_id: '<MESSAGE_ID>',\n content: '<CONTENT>',\n topics: [], # optional\n users: [], # optional\n targets: [], # optional\n draft: true, # optional\n scheduled_at: '' # optional\n)\n\nputs response.inspect\n```\n```csharp\nusing Appwrite;\nusing Appwrite.Services;\nusing Appwrite.Models;\n\nvar client = new Client()\n .SetEndPoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .SetProject(\"<PROJECT_ID>\") // Your project ID\n .SetKey(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\nvar messaging = new Messaging(client);\n\nMessage result = await messaging.CreateSMS(\n messageId: \"<MESSAGE_ID>\",\n content: \"<CONTENT>\"\n topics: new List<string> {} // optional\n users: new List<string> {} // optional\n targets: new List<string> {} // optional\n draft: true // optional\n scheduledAt: \"\"); // optional\n```\n```dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\nimport 'package:dart_appwrite/enums.dart';\nimport 'package:dart_appwrite/models.dart';\n\nvoid main() { // Init SDK\n Client client = Client();\n Messaging messaging = Messaging(client);\n\n client\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n ;\n\n Future result = messaging.createSms(\n messageId: '<MESSAGE_ID>',\n content: '<CONTENT>',\n topics: [], // optional\n users: [], // optional\n targets: [], // optional\n draft: true, // optional\n scheduledAt: '', // optional\n );\n\n result\n .then((response) {\n print(response);\n }).catchError((error) {\n print(error.response);\n });\n}\n```\n```kotlin\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Messaging;\n\nClient client = new Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\nMessaging messaging = new Messaging(client);\n\nmessaging.createSms(\n \"<MESSAGE_ID>\", // messageId\n \"<CONTENT>\", // content\n listOf(), // topics (optional)\n listOf(), // users (optional)\n listOf(), // targets (optional)\n true, // draft (optional)\n \"\" // scheduledAt (optional)\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(result);\n })\n);\n```\n```java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Messaging;\n\nClient client = new Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\nMessaging messaging = new Messaging(client);\n\nmessaging.createSms(\n \"<MESSAGE_ID>\", // messageId\n \"<CONTENT>\", // content\n listOf(), // topics (optional)\n listOf(), // users (optional)\n listOf(), // targets (optional)\n true, // draft (optional)\n \"\" // scheduledAt (optional)\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(result);\n })\n);\n```\n```swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\") // Your secret API key\n\nlet messaging = Messaging(client)\n\nlet message = try await messaging.createSms(\n messageId: \"<MESSAGE_ID>\",\n content: \"<CONTENT>\",\n topics: [], // optional\n users: [], // optional\n targets: [], // optional\n draft: true, // optional\n scheduledAt: \"\" // optional\n)\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::messaging::Messaging;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n let client = Client::new()\n .set_endpoint(\"https://<REGION>.cloud.appwrite.io/v1\")\n .set_project(\"<PROJECT_ID>\")\n .set_key(\"919c2d18fb5d4...a2ae413da83346ad2\");\n\n let messaging = Messaging::new(&client);\n\n let message = messaging.create_sms(\n \"<MESSAGE_ID>\", // messageId\n \"<CONTENT>\", // content\n Some(vec![]), // topics (optional)\n Some(vec![]), // users (optional)\n Some(vec![]), // targets (optional)\n Some(true), // draft (optional)\n None, // scheduledAt (optional)\n ).await?;\n\n println!(\"{:?}\", message);\n Ok(())\n}\n```\n{% /multicode %}\n\n{% arrow_link href=\"/docs/products/messaging/send-sms-messages\" %}\nLearn more about sending a SMS message\n{% /arrow_link %}"}, {"path": "docs/products/messaging/msg91", "title": "MSG91", "description": "Send SMS messages to your Appwrite users using MSG91 and Appwrite Messaging.", "content": "MSG91 lets you send customized SMS messages to your users.\nThese SMS messages can be sent immediately or scheduled.\nYou can send SMS messages for purposes like reminders, promotions, announcements, and even custom authentication flows.\n\n{% section #add-provider step=1 title=\"Add provider\" %}\n\nTo add MSG91 as a provider, navigate to **Messaging** > **Providers** > {% icon icon=\"plus\" size=\"m\" /%} **Add provider** > **SMS**.\n{% only_dark %}\n![Add a MSG91 provider](/images/docs/messaging/providers/msg91/dark/provider.avif)\n{% /only_dark %}\n{% only_light %}\n![Add a MSG91 provider](/images/docs/messaging/providers/msg91/provider.avif)\n{% /only_light %}\n\nGive your provider a name > choose **MSG91** > click **Save and continue**.\nThe provider will be saved to your project, but not enabled until you complete its configuration.\n{% /section %}\n{% section #configure-provider step=2 title=\"Configure provider\" %}\n\nIn the **Configure** step, you will need to provide details from your MSG91 dashboard to connect your Appwrite project.\n\nYou will need to provide the following information from your **MSG91 dashboard**.\n\n{% table %}\n* Field name\n*\n---\n* Auth key\n* Click to open the Username dropdown > **Authkey** > **Verify your mobile number** > **Create Authkey**.\n---\n* Sender ID\n* Head to MSG91 dashboard > **SMS** > **Sender ID** > **Create sender ID**.\n---\n* Sender number\n*\n{% /table %}\n\nAfter adding the following details, click **Save and continue** to enable the provider.\n{% /section %}\n\n{% section #test-provider step=3 title=\"Test provider\" %}\nBefore sending your first message,\nmake sure you've configured [a topic](/docs/products/messaging/topics) and [a target](/docs/products/messaging/targets) to send messages to.\n{% tabs %}\n{% tabsitem #console title=\"Console\" %}\nTo send a test message, navigate to **Messaging** > **Messages** > {% icon icon=\"plus\" size=\"m\" /%} **Create message** > **SMS**.\n{% only_dark %}\n![Create an SMS message](/images/docs/messaging/messages/dark/create-sms-message.avif)\n{% /only_dark %}\n{% only_light %}\n![Create an SMS message](/images/docs/messaging/messages/create-sms-message.avif)\n{% /only_light %}\n\nAdd your message and in the targets step, select one of your test targets. Set the schedule to **Now** and click **Send**.\n\nVerify that you can receive the message in your inbox. If not, check for logs in the Appwrite Console or in your provider's logs.\n{% /tabsitem %}\n\n{% tabsitem #server-sdk title=\"Server SDK\" %}\nTo send a message programmatically, use an [Appwrite Server SDK](/docs/sdks#server).\n{% multicode %}\n\n```server-nodejs\nconst sdk = require('node-appwrite');\n\n// Init SDK\nconst client = new sdk.Client();\n\nconst messaging = new sdk.Messaging(client);\n\nclient\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n;\n\nconst message = await messaging.createSms(\n '<MESSAGE_ID>', // messageId\n '<CONTENT>', // content\n [], // topics (optional)\n [], // users (optional)\n [], // targets (optional)\n true, // draft (optional)\n '' // scheduledAt (optional)\n );\n```\n```deno\nimport * as sdk from \"npm:node-appwrite\";\n\n// Init SDK\nlet client = new sdk.Client();\n\nlet messaging = new sdk.Messaging(client);\n\nclient\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n;\n\nconst message = await messaging.createSms(\n '<MESSAGE_ID>', // messageId\n '<CONTENT>', // content\n [], // topics (optional)\n [], // users (optional)\n [], // targets (optional)\n true, // draft (optional)\n '' // scheduledAt (optional)\n );\n```\n```php\n<?php\n\nuse Appwrite\\Client;\nuse Appwrite\\Services\\Messaging;\n\n$client = new Client();\n\n$client\n ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n ->setProject('<PROJECT_ID>') // Your project ID\n ->setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n;\n\n$messaging = new Messaging($client);\n\n$result = $messaging->createSms(\n messageId: '<MESSAGE_ID>',\n content: '<CONTENT>',\n topics: [], // optional\n users: [], // optional\n targets: [], // optional\n draft: true, // optional\n scheduledAt: '' // optional\n);\n```\n```python\nfrom appwrite.client import Client\nfrom appwrite.services.messaging import Messaging\n\nclient = Client()\n\n(client\n .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('<PROJECT_ID>') # Your project ID\n .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key\n)\n\nmessaging = Messaging(client)\n\nresult = messaging.create_sms(\n message_id = '<MESSAGE_ID>',\n content = '<CONTENT>',\n topics = [], # optional\n users = [], # optional\n targets = [], # optional\n draft = True, # optional\n scheduled_at = '' # optional\n)\n```\n```ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('<PROJECT_ID>') # Your project ID\n .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key\n\nmessaging = Messaging.new(client)\n\nresponse = messaging.create_sms(\n message_id: '<MESSAGE_ID>',\n content: '<CONTENT>',\n topics: [], # optional\n users: [], # optional\n targets: [], # optional\n draft: true, # optional\n scheduled_at: '' # optional\n)\n\nputs response.inspect\n```\n```csharp\nusing Appwrite;\nusing Appwrite.Services;\nusing Appwrite.Models;\n\nvar client = new Client()\n .SetEndPoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .SetProject(\"<PROJECT_ID>\") // Your project ID\n .SetKey(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\nvar messaging = new Messaging(client);\n\nMessage result = await messaging.CreateSMS(\n messageId: \"<MESSAGE_ID>\",\n content: \"<CONTENT>\" \n topics: new List<string> {} // optional \n users: new List<string> {} // optional \n targets: new List<string> {} // optional \n draft: true // optional \n scheduledAt: \"\"); // optional\n```\n```dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\nimport 'package:dart_appwrite/enums.dart';\nimport 'package:dart_appwrite/models.dart';\n\nvoid main() async { // Init SDK\n Client client = Client();\n Messaging messaging = Messaging(client);\n\n client\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n ;\n\n Future result = await messaging.createSms(\n messageId: '<MESSAGE_ID>',\n content: '<CONTENT>',\n topics: [], // optional\n users: [], // optional\n targets: [], // optional\n draft: true, // optional\n scheduledAt: '', // optional\n );\n\n result\n .then((response) {\n print(response);\n }).catchError((error) {\n print(error.response);\n });\n}\n```\n```kotlin\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Messaging;\n\nClient client = new Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\nMessaging messaging = new Messaging(client);\n\nmessaging.createSms(\n \"<MESSAGE_ID>\", // messageId\n \"<CONTENT>\", // content\n listOf(), // topics (optional)\n listOf(), // users (optional)\n listOf(), // targets (optional)\n true, // draft (optional)\n \"\" // scheduledAt (optional)\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(result);\n })\n);\n```\n```java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Messaging;\n\nClient client = new Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\nMessaging messaging = new Messaging(client);\n\nmessaging.createSms(\n \"<MESSAGE_ID>\", // messageId\n \"<CONTENT>\", // content\n listOf(), // topics (optional)\n listOf(), // users (optional)\n listOf(), // targets (optional)\n true, // draft (optional)\n \"\" // scheduledAt (optional)\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(result);\n })\n);\n```\n```swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\") // Your secret API key\n\nlet messaging = Messaging(client)\n\nlet message = try await messaging.createSms(\n messageId: \"<MESSAGE_ID>\",\n content: \"<CONTENT>\",\n topics: [], // optional\n users: [], // optional\n targets: [], // optional\n draft: true, // optional\n scheduledAt: \"\" // optional\n)\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::messaging::Messaging;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n let client = Client::new()\n .set_endpoint(\"https://<REGION>.cloud.appwrite.io/v1\")\n .set_project(\"<PROJECT_ID>\")\n .set_key(\"919c2d18fb5d4...a2ae413da83346ad2\");\n\n let messaging = Messaging::new(&client);\n\n let message = messaging.create_sms(\n \"<MESSAGE_ID>\", // messageId\n \"<CONTENT>\", // content\n None, // topics (optional)\n None, // users (optional)\n None, // targets (optional)\n Some(true), // draft (optional)\n None, // scheduledAt (optional)\n ).await?;\n\n println!(\"{:?}\", message);\n Ok(())\n}\n```\n{% /multicode %}\n{% /tabsitem %}\n{% /tabs %}\n\nYou can follow the [Send email messages](/docs/products/messaging/send-sms-messages) journey to send your first push notification and test your provider.\n{% /section %}\n\n\n{% section #manage-provider step=4 title=\"Manage provider\" %}\n{% tabs %}\n{% tabsitem #console title=\"Console\" %}\nYou can update or delete a provider in the Appwrite Console.\n\nNavigate to **Messaging** > **Providers** > click your provider.\nIn the settings, you can update a provider's configuration or delete the provider.\n{% /tabsitem %}\n\n{% tabsitem #server-sdk title=\"Server SDK\" %}\nTo update or delete providers programmatically, use an [Appwrite Server SDK](/docs/sdks#server).\n{% multicode %}\n```server-nodejs\nconst sdk = require('node-appwrite');\n\n// Init SDK\nconst client = new sdk.Client();\n\nconst messaging = new sdk.Messaging(client);\n\nclient\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n;\n\nconst message = await messaging.updateMsg91Provider(\n '<PROVIDER_ID>', // providerId\n '<NAME>', // name (optional)\n false, // enabled (optional)\n '<SENDER_ID>', // senderId (optional)\n '<AUTH_KEY>', // authKey (optional)\n '<FROM>' // from (optional)\n );\n```\n```deno\nimport * as sdk from \"npm:node-appwrite\";\n\n// Init SDK\nlet client = new sdk.Client();\n\nlet messaging = new sdk.Messaging(client);\n\nclient\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n;\n\nconst message = await messaging.updateMsg91Provider(\n '<PROVIDER_ID>', // providerId\n '<NAME>', // name (optional)\n false, // enabled (optional)\n '<SENDER_ID>', // senderId (optional)\n '<AUTH_KEY>', // authKey (optional)\n '<FROM>' // from (optional)\n );\n```\n```php\n<?php\n\nuse Appwrite\\Client;\nuse Appwrite\\Services\\Messaging;\n\n$client = new Client();\n\n$client\n ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n ->setProject('<PROJECT_ID>') // Your project ID\n ->setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n;\n\n$messaging = new Messaging($client);\n\n$result = $messaging->updateMsg91Provider(\n providerId: '<PROVIDER_ID>',\n name: '<NAME>', // optional\n enabled: false, // optional\n senderId: '<SENDER_ID>', // optional\n authKey: '<AUTH_KEY>', // optional\n from: '<FROM>' // optional\n);\n```\n```python\nfrom appwrite.client import Client\nfrom appwrite.services.messaging import Messaging\n\nclient = Client()\n\n(client\n .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('<PROJECT_ID>') # Your project ID\n .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key\n)\n\nmessaging = Messaging(client)\n\nresult = messaging.update_msg91_provider(\n provider_id = '<PROVIDER_ID>',\n name = '<NAME>', # optional\n enabled = False, # optional\n sender_id = '<SENDER_ID>', # optional\n auth_key = '<AUTH_KEY>', # optional\n from = '<FROM>' # optional\n)\n```\n```ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('<PROJECT_ID>') # Your project ID\n .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key\n\nmessaging = Messaging.new(client)\n\nresponse = messaging.update_msg91_provider(\n provider_id: '<PROVIDER_ID>',\n name: '<NAME>', # optional\n enabled: false, # optional\n sender_id: '<SENDER_ID>', # optional\n auth_key: '<AUTH_KEY>', # optional\n from: '<FROM>' # optional\n)\n\nputs response.inspect\n```\n```csharp\nusing Appwrite;\nusing Appwrite.Services;\nusing Appwrite.Models;\n\nvar client = new Client()\n .SetEndPoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .SetProject(\"<PROJECT_ID>\") // Your project ID\n .SetKey(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\nvar messaging = new Messaging(client);\n\nProvider result = await messaging.UpdateMsg91Provider(\n providerId: \"<PROVIDER_ID>\"\n name: \"<NAME>\" // optional\n enabled: false // optional\n senderId: \"<SENDER_ID>\" // optional\n authKey: \"<AUTH_KEY>\" // optional\n from: \"<FROM>\"); // optional\n```\n```dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\nimport 'package:dart_appwrite/enums.dart';\nimport 'package:dart_appwrite/models.dart';\n\nvoid main() { // Init SDK\n Client client = Client();\n Messaging messaging = Messaging(client);\n\n client\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n ;\n\n Future result = messaging.updateMsg91Provider(\n providerId: '<PROVIDER_ID>',\n name: '<NAME>', // optional\n enabled: false, // optional\n senderId: '<SENDER_ID>', // optional\n authKey: '<AUTH_KEY>', // optional\n from: '<FROM>', // optional\n );\n\n result\n .then((response) {\n print(response);\n }).catchError((error) {\n print(error.response);\n });\n}\n```\n```kotlin\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Messaging;\n\nClient client = new Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\nMessaging messaging = new Messaging(client);\n\nmessaging.updateMsg91Provider(\n \"<PROVIDER_ID>\", // providerId\n \"<NAME>\", // name (optional)\n false, // enabled (optional)\n \"<SENDER_ID>\", // senderId (optional)\n \"<AUTH_KEY>\", // authKey (optional)\n \"<FROM>\" // from (optional)\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(result);\n })\n);\n```\n```java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Messaging;\n\nClient client = new Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\nMessaging messaging = new Messaging(client);\n\nmessaging.updateMsg91Provider(\n \"<PROVIDER_ID>\", // providerId\n \"<NAME>\", // name (optional)\n false, // enabled (optional)\n \"<SENDER_ID>\", // senderId (optional)\n \"<AUTH_KEY>\", // authKey (optional)\n \"<FROM>\" // from (optional)\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(result);\n })\n);\n```\n```swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\") // Your secret API key\n\nlet messaging = Messaging(client)\n\nlet provider = try await messaging.updateMsg91Provider(\n providerId: \"<PROVIDER_ID>\",\n name: \"<NAME>\", // optional\n enabled: xfalse, // optional\n senderId: \"<SENDER_ID>\", // optional\n authKey: \"<AUTH_KEY>\", // optional\n from: \"<FROM>\" // optional\n)\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::messaging::Messaging;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n let client = Client::new()\n .set_endpoint(\"https://<REGION>.cloud.appwrite.io/v1\")\n .set_project(\"<PROJECT_ID>\")\n .set_key(\"919c2d18fb5d4...a2ae413da83346ad2\");\n\n let messaging = Messaging::new(&client);\n\n let provider = messaging.update_msg91_provider(\n \"<PROVIDER_ID>\", // providerId\n Some(\"<NAME>\"), // name (optional)\n Some(false), // enabled (optional)\n Some(\"<TEMPLATE_ID>\"), // templateId (optional)\n Some(\"<SENDER_ID>\"), // senderId (optional)\n Some(\"<AUTH_KEY>\"), // authKey (optional)\n ).await?;\n\n println!(\"{:?}\", provider);\n Ok(())\n}\n```\n{% /multicode %}\n{% /tabsitem %}\n{% /tabs %}\n{% /section %}"}, {"path": "docs/products/messaging/providers", "title": "Providers", "description": "Learn the different providers that you can use to send messages with Appwrite.", "content": "Appwrite allows you to connect to a variety of third-party messaging providers to deliver push notifications, emails, and SMS messages to your users.\nBefore you can deliver messages, you must connect to a messaging provider.\n# Push notifications {% #push-notifications %}\nSend push notifications, which are little notification messages that appear on a user's browser or device to alert them\nof events or updates. Configure one of the following providers to send push notifications.\n{% cards %}\n{% cards_item href=\"/docs/products/messaging/apns\" title=\"APNS\" icon=\"icon-apple\" %}\nSend push notifications to apps on Apple devices through Apple Push Notification service (APNs).\n{% /cards_item %}\n{% cards_item href=\"/docs/products/messaging/fcm\" title=\"FCM\" icon=\"web-icon-firebase\" %}\nSend push notifications to Android, Apple, or Web app with Firebase Cloud Messaging (FCM).\n{% /cards_item %}\n{% /cards %}\n# Email {% #email %}\nDeliver customized emails to users to send reminders, updates, promotions, and custom authentication logic.\n{% cards %}\n{% cards_item href=\"/docs/products/messaging/mailgun\" title=\"Mailgun\" icon=\"web-icon-mailgun\" %}\nDeliver custom email messages to users using Mailgun.\n{% /cards_item %}\n{% cards_item href=\"/docs/products/messaging/sendgrid\" title=\"SendGrid\" icon=\"web-icon-sendgrid\" %}\nDeliver custom email messages to users using SendGrid.\n{% /cards_item %}\n{% cards_item href=\"/docs/products/messaging/smtp\" title=\"SMTP\" icon=\"icon-mail\" %}\nDeliver custom email messages to users using standard SMTP settings.\n{% /cards_item %}\n{% /cards %}\n# SMS {% #sms %}\nSend customized SMS messages to users by phone to send reminders, updates, promotions, and one-time passwords.\n{% cards %}\n{% cards_item href=\"/docs/products/messaging/twilio\" title=\"Twilio\" icon=\"icon-twilio\" %}\nDeliver custom SMS messages to users using Twilio.\n{% /cards_item %}\n{% cards_item href=\"/docs/products/messaging/msg91\" title=\"MSG91\" icon=\"icon-msg91\" %}\nDeliver custom SMS messages to users using MSG91.\n{% /cards_item %}\n{% cards_item href=\"/docs/products/messaging/telesign\" title=\"Telesign\" icon=\"icon-telesign\" %}\nDeliver custom SMS messages to users using Telesign.\n{% /cards_item %}\n{% cards_item href=\"/docs/products/messaging/textmagic\" title=\"Textmagic\" icon=\"icon-textmagic\" %}\nDeliver custom SMS messages to users using Textmagic.\n{% /cards_item %}\n{% cards_item href=\"/docs/products/messaging/vonage\" title=\"Vonage\" icon=\"icon-vonage\" %}\nDeliver custom SMS messages to users using Vonage.\n{% /cards_item %}\n{% /cards %}"}, {"path": "docs/products/messaging/send-email-messages", "title": "Send email messages", "description": "Send email messages to your users using Appwrite Messaging.", "content": "You can send custom email messages to your app's users using Appwrite Messaging and a connected SMTP service.\nThis guide takes you through the implementation path of adding email messaging to your app.\n\n# Add a provider {% #add-a-provider %}\nAppwrite supports [Mailgun](/docs/products/messaging/mailgun/) and [Sendgrid](/docs/products/messaging/sendgrid/) as\nSMTP providers. You must configure one of them as a provider.\n{% only_dark %}\n![Add a SMTP provider](/images/docs/messaging/providers/mailgun/dark/add-mailgun.avif)\n{% /only_dark %}\n{% only_light %}\n![Add a SMTP provider](/images/docs/messaging/providers/mailgun/add-mailgun.avif)\n{% /only_light %}\nTo add a new provider navigate to **Messaging** > **Providers** > {% icon icon=\"plus\" size=\"m\" /%} **Add provider** > **Email**\nand follow the wizard. You can find more details about configuring in the provider guides for\n[Mailgun](/docs/products/messaging/mailgun#configure-provider) and [Sendgrid](/docs/products/messaging/sendgrid#configure-provider).\n\n# Add targets {% #add-targets %}\nIn Appwrite Messaging, each user has **targets** like their email, phone number, and devices with your app installed.\nYou can deliver messages to users through their **targets**.\n\n{% only_dark %}\n![Target overview](/images/docs/messaging/targets/dark/target-overview.avif)\n{% /only_dark %}\n{% only_light %}\n![Target overview](/images/docs/messaging/targets/target-overview.avif)\n{% /only_light %}\n\nIf the user signed up with email and password, their account would already have email as a target.\nDuring development, you can add targets to existing accounts by navigating to **Authentication** > **Users** > **Select a user** > **Targets** > **Add a subscriber**.\n{% only_dark %}\n![Add a target](/images/docs/messaging/targets/dark/add-targets.avif)\n{% /only_dark %}\n{% only_light %}\n![Add a target](/images/docs/messaging/targets/add-targets.avif)\n{% /only_light %}\n\nYou 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.\n{% multicode %}\n```server-nodejs\nconst sdk = require('node-appwrite');\n\nconst client = new sdk.Client()\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2'); // Your secret API key\n\nconst users = new sdk.Users(client);\n\nconst target = await users.createTarget(\n '<USER_ID>', // userId\n '<TARGET_ID>', // targetId\n sdk.MessagingProviderType.Email, // providerType\n '<IDENTIFIER>', // identifier\n '<PROVIDER_ID>', // providerId (optional)\n '<NAME>' // name (optional)\n);\n```\n```deno\nimport * as sdk from \"npm:node-appwrite\";\n\nconst client = new sdk.Client()\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setJWT('eyJhbVCJ9.eyJ...'); // Your secret JSON Web Token\n\nconst users = new sdk.Users(client);\n\nconst target = await users.createTarget(\n '<USER_ID>', // userId\n '<TARGET_ID>', // targetId\n sdk.MessagingProviderType.Email, // providerType\n '<IDENTIFIER>', // identifier\n '<PROVIDER_ID>', // providerId (optional)\n '<NAME>' // name (optional)\n);\n```\n```php\n<?php\n\nuse Appwrite\\Client;\nuse Appwrite\\Services\\Users;\nuse Appwrite\\Enums\\MessagingProviderType;\n\n$client = (new Client())\n ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n ->setProject('<PROJECT_ID>') // Your project ID\n ->setKey('919c2d18fb5d4...a2ae413da83346ad2'); // Your secret API key\n\n$users = new Users($client);\n\n$target = $users->createTarget(\n userId: '<USER_ID>',\n targetId: '<TARGET_ID>',\n providerType: MessagingProviderType::EMAIL(),\n identifier: '<IDENTIFIER>',\n providerId: '<PROVIDER_ID>', // optional\n name: '<NAME>' // optional\n);\n```\n```python\nfrom appwrite.client import Client\nfrom appwrite.enums import MessagingProviderType\n\nclient = Client()\nclient.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\nclient.set_project('<PROJECT_ID>') # Your project ID\nclient.set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key\n\nusers = Users(client)\n\ntarget = users.create_target(\n user_id = '<USER_ID>',\n target_id = '<TARGET_ID>',\n provider_type = MessagingProviderType.EMAIL,\n identifier = '<IDENTIFIER>',\n provider_id = '<PROVIDER_ID>', # optional\n name = '<NAME>' # optional\n)\n```\n```ruby\nrequire 'appwrite'\n\ninclude Appwrite\ninclude Appwrite::Enums\n\nclient = Client.new\n .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('<PROJECT_ID>') # Your project ID\n .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key\n\nusers = Users.new(client)\n\ntarget = users.create_target(\n user_id: '<USER_ID>',\n target_id: '<TARGET_ID>',\n provider_type: MessagingProviderType::EMAIL,\n identifier: '<IDENTIFIER>',\n provider_id: '<PROVIDER_ID>', # optional\n name: '<NAME>' # optional\n)\n\nputs target.inspect\n```\n```csharp\nusing Appwrite;\nusing Appwrite.Services;\nusing Appwrite.Models;\nusing Appwrite.Enums;\n\nClient client = new Client()\n .SetEndPoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .SetProject(\"<PROJECT_ID>\") // Your project ID\n .SetKey(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\nUsers users = new Users(client);\n\nTarget target = await users.CreateTarget(\n userId: \"<USER_ID>\",\n targetId: \"<TARGET_ID>\",\n providerType: MessagingProviderType.Email,\n identifier: \"<IDENTIFIER>\",\n providerId: \"<PROVIDER_ID>\", // optional\n name: \"<NAME>\" // optional\n);\n```\n```dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\nimport 'package:dart_appwrite/enums.dart';\nimport 'package:dart_appwrite/models.dart';\n\nClient client = Client()\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2'); // Your secret API key\n\nUsers users = Users(client);\n\nfinal target = await users.createTarget(\n userId: '<USER_ID>',\n targetId: '<TARGET_ID>',\n providerType: MessagingProviderType.email,\n identifier: '<IDENTIFIER>',\n providerId: '<PROVIDER_ID>', // (optional)\n name: '<NAME>', // (optional)\n);\n\n```\n```kotlin\nimport io.appwrite.Client\nimport io.appwrite.coroutines.CoroutineCallback\nimport io.appwrite.services.Users\nimport io.appwrite.enums.MessagingProviderType\n\nval client = Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\") // Your secret API key\n\nval users = Users(client)\n\nval target = users.createTarget(\n userId = \"<USER_ID>\",\n targetId = \"<TARGET_ID>\",\n providerType = MessagingProviderType.EMAIL,\n identifier = \"<IDENTIFIER>\",\n providerId = \"<PROVIDER_ID>\", // optional\n name = \"<NAME>\" // optional\n)\n\n```\n```java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Users;\nimport io.appwrite.enums.MessagingProviderType;\n\nClient client = new Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\nUsers users = new Users(client);\n\nusers.createTarget(\n \"<USER_ID>\", // userId\n \"<TARGET_ID>\", // targetId\n MessagingProviderType.EMAIL, // providerType\n \"<IDENTIFIER>\", // identifier\n \"<PROVIDER_ID>\", // providerId (optional)\n \"<NAME>\", // name (optional)\n new CoroutineCallback<>((target, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(target);\n })\n);\n```\n```swift\nimport Appwrite\nimport AppwriteEnums\n\nlet client = Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\") // Your secret API key\n\nlet users = Users(client)\n\nlet target = try await users.createTarget(\n userId: \"<USER_ID>\",\n targetId: \"<TARGET_ID>\",\n providerType: .email,\n identifier: \"<IDENTIFIER>\",\n providerId: \"<PROVIDER_ID>\", // optional\n name: \"<NAME>\" // optional\n)\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::users::Users;\nuse appwrite::enums::messaging_provider_type::MessagingProviderType;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n let client = Client::new()\n .set_endpoint(\"https://<REGION>.cloud.appwrite.io/v1\")\n .set_project(\"<PROJECT_ID>\")\n .set_key(\"919c2d18fb5d4...a2ae413da83346ad2\");\n\n let users = Users::new(&client);\n\n let target = users.create_target(\n \"<USER_ID>\",\n \"<TARGET_ID>\",\n MessagingProviderType::Email,\n \"<IDENTIFIER>\",\n Some(\"<PROVIDER_ID>\"), // optional\n Some(\"<NAME>\"), // optional\n ).await?;\n\n println!(\"{:?}\", target);\n Ok(())\n}\n```\n{% /multicode %}\n# Create topics (optional) {% #create-topics %}\nYou 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.\nThis step is optional if you plan to only send emails to individual targets.\n\nTo create a topic in the Appwrite Console, navigate to **Messaging** > **Topics** > {% icon icon=\"plus\" size=\"m\" /%} **Create topic**.\n{% only_dark %}\n![Add a target](/images/docs/messaging/topics/dark/create-topics.avif)\n{% /only_dark %}\n{% only_light %}\n![Add a target](/images/docs/messaging/topics/create-topics.avif)\n{% /only_light %}\n\nYou can also create topics programmatically using an [Appwrite Server SDK](/docs/references/cloud/server-nodejs/messaging#createTopic).\n{% multicode %}\n```server-nodejs\nconst sdk = require('node-appwrite');\n\n// Init SDK\nconst client = new sdk.Client();\n\nconst messaging = new sdk.Messaging(client);\n\nclient\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n;\n\nconst topic = await messaging.createTopic(\n '<TOPIC_ID>', // topicId\n '<NAME>' // name\n );\n```\n```deno\nimport * as sdk from \"npm:node-appwrite\";\n\n// Init SDK\nlet client = new sdk.Client();\n\nlet messaging = new sdk.Messaging(client);\n\nclient\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n;\n\nconst topic = await messaging.createTopic(\n '<TOPIC_ID>', // topicId\n '<NAME>' // name\n );\n```\n```php\n<?php\n\nuse Appwrite\\Client;\nuse Appwrite\\Services\\Messaging;\n\n$client = new Client();\n\n$client\n ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n ->setProject('<PROJECT_ID>') // Your project ID\n ->setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n;\n\n$messaging = new Messaging($client);\n\n$result = $messaging->createTopic(\n topicId: '<TOPIC_ID>',\n name: '<NAME>'\n);\n```\n```python\nfrom appwrite.client import Client\nfrom appwrite.services.messaging import Messaging\n\nclient = Client()\n\n(client\n .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('<PROJECT_ID>') # Your project ID\n .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key\n)\n\nmessaging = Messaging(client)\n\nresult = messaging.create_topic(\n topic_id = '<TOPIC_ID>',\n name = '<NAME>'\n)\n```\n```ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('<PROJECT_ID>') # Your project ID\n .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key\n\nmessaging = Messaging.new(client)\n\nresponse = messaging.create_topic(\n topic_id: '<TOPIC_ID>',\n name: '<NAME>'\n)\n\nputs response.inspect\n```\n```csharp\nusing Appwrite;\nusing Appwrite.Services;\nusing Appwrite.Models;\n\nvar client = new Client()\n .SetEndPoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .SetProject(\"<PROJECT_ID>\") // Your project ID\n .SetKey(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\nvar messaging = new Messaging(client);\n\nTopic result = await messaging.CreateTopic(\n topicId: \"<TOPIC_ID>\",\n name: \"<NAME>\");\n```\n```dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\nimport 'package:dart_appwrite/enums.dart';\nimport 'package:dart_appwrite/models.dart';\n\nvoid main() async { // Init SDK\n Client client = Client();\n Messaging messaging = Messaging(client);\n\n client\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n ;\n\n Future result = await messaging.createTopic(\n topicId: '<TOPIC_ID>',\n name: '<NAME>',\n );\n\n result\n .then((response) {\n print(response);\n }).catchError((error) {\n print(error.response);\n });\n}\n```\n```kotlin\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Messaging;\n\nClient client = new Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\nMessaging messaging = new Messaging(client);\n\nmessaging.createTopic(\n \"<TOPIC_ID>\", // topicId\n \"<NAME>\" // name\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(result);\n })\n);\n```\n```java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Messaging;\n\nClient client = new Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\nMessaging messaging = new Messaging(client);\n\nmessaging.createTopic(\n \"<TOPIC_ID>\", // topicId\n \"<NAME>\" // name\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(result);\n })\n);\n```\n```swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\") // Your secret API key\n\nlet messaging = Messaging(client)\n\nlet topic = try await messaging.createTopic(\n topicId: \"<TOPIC_ID>\",\n name: \"<NAME>\"\n)\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::messaging::Messaging;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n let client = Client::new()\n .set_endpoint(\"https://<REGION>.cloud.appwrite.io/v1\")\n .set_project(\"<PROJECT_ID>\")\n .set_key(\"919c2d18fb5d4...a2ae413da83346ad2\");\n\n let messaging = Messaging::new(&client);\n\n let topic = messaging.create_topic(\n \"<TOPIC_ID>\",\n \"<NAME>\",\n None, // subscribe (optional)\n ).await?;\n\n println!(\"{:?}\", topic);\n Ok(())\n}\n```\n{% /multicode %}\n\n# Send emails {% #send-emails %}\nYou can send emails using a Server SDK. \nTo send an email immediately, you can call the `createEmail` endpoint with `schedule` left empty.\n{% multicode %}\n```server-nodejs\nconst sdk = require('node-appwrite');\n\n// Init SDK\nconst client = new sdk.Client();\n\nconst messaging = new sdk.Messaging(client);\n\nclient\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n;\n\nconst message = await messaging.createEmail(\n '<MESSAGE_ID>', // messageId\n '<SUBJECT>', // subject\n '<CONTENT>', // content\n [], // topics (optional)\n [], // users (optional)\n [], // targets (optional)\n [], // cc (optional)\n [], // bcc (optional)\n [], // attachments (optional)\n false, // draft (optional)\n false, // html (optional)\n '' // scheduledAt (optional)\n );\n```\n```deno\nimport * as sdk from \"npm:node-appwrite\";\n\n// Init SDK\nlet client = new sdk.Client();\n\nlet messaging = new sdk.Messaging(client);\n\nclient\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n;\n\nconst message = await messaging.createEmail(\n '<MESSAGE_ID>', // messageId\n '<SUBJECT>', // subject\n '<CONTENT>', // content\n [], // topics (optional)\n [], // users (optional)\n [], // targets (optional)\n [], // cc (optional)\n [], // bcc (optional)\n [], // attachments (optional)\n false, // draft (optional)\n false, // html (optional)\n '' // scheduledAt (optional)\n );\n```\n```php\n<?php\n\nuse Appwrite\\Client;\nuse Appwrite\\Services\\Messaging;\n\n$client = new Client();\n\n$client\n ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n ->setProject('<PROJECT_ID>') // Your project ID\n ->setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n;\n\n$messaging = new Messaging($client);\n\n$result = $messaging->createEmail(\n messageId: '<MESSAGE_ID>',\n subject: '<SUBJECT>',\n content: '<CONTENT>',\n topics: [], // optional\n users: [], // optional\n targets: [], // optional\n cc: [], // optional\n bcc: [], // optional\n draft: false, // optional\n html: false, // optional\n scheduledAt: '' // optional\n);\n```\n```python\nfrom appwrite.client import Client\nfrom appwrite.services.messaging import Messaging\n\nclient = Client()\n\n(client\n .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('<PROJECT_ID>') # Your project ID\n .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key\n)\n\nmessaging = Messaging(client)\n\nresult = messaging.create_email(\n message_id = '<MESSAGE_ID>',\n subject = '<SUBJECT>',\n content = '<CONTENT>',\n topics = [], # optional\n users = [], # optional\n targets = [], # optional\n cc = [], # optional\n bcc = [], # optional\n draft = False, # optional\n html = False, # optional\n scheduled_at = '' # optional\n)\n```\n```ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('<PROJECT_ID>') # Your project ID\n .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key\n\nmessaging = Messaging.new(client)\n\nresponse = messaging.create_email(\n message_id: '<MESSAGE_ID>',\n subject: '<SUBJECT>',\n content: '<CONTENT>',\n topics: [], # optional\n users: [], # optional\n targets: [], # optional\n cc: [], # optional\n bcc: [], # optional\n draft: false, # optional\n html: false, # optional\n scheduled_at: '' # optional\n)\n\nputs response.inspect\n```\n```csharp\nusing Appwrite;\nusing Appwrite.Services;\nusing Appwrite.Models;\n\nvar client = new Client()\n .SetEndPoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .SetProject(\"<PROJECT_ID>\") // Your project ID\n .SetKey(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\nvar messaging = new Messaging(client);\n\nMessage result = await messaging.CreateEmail(\n messageId: \"<MESSAGE_ID>\",\n subject: \"<SUBJECT>\",\n content: \"<CONTENT>\" \n topics: new List<string> {} // optional \n users: new List<string> {} // optional \n targets: new List<string> {} // optional \n cc: new List<string> {} // optional \n bcc: new List<string> {} // optional \n draft: false // optional \n html: false // optional \n scheduledAt: \"\"); // optional\n```\n```dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\nimport 'package:dart_appwrite/enums.dart';\nimport 'package:dart_appwrite/models.dart';\n\nvoid main() { // Init SDK\n Client client = Client();\n Messaging messaging = Messaging(client);\n\n client\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n ;\n\n Future result = messaging.createEmail(\n messageId: '<MESSAGE_ID>',\n subject: '<SUBJECT>',\n content: '<CONTENT>',\n topics: [], // optional\n users: [], // optional\n targets: [], // optional\n cc: [], // optional\n bcc: [], // optional\n draft: false, // optional\n html: false, // optional\n scheduledAt: '', // optional\n );\n\n result\n .then((response) {\n print(response);\n }).catchError((error) {\n print(error.response);\n });\n}\n```\n```kotlin\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Messaging;\n\nClient client = new Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\nMessaging messaging = new Messaging(client);\n\nmessaging.createEmail(\n \"<MESSAGE_ID>\", // messageId\n \"<SUBJECT>\", // subject\n \"<CONTENT>\", // content\n listOf(), // topics (optional)\n listOf(), // users (optional)\n listOf(), // targets (optional)\n listOf(), // cc (optional)\n listOf(), // bcc (optional)\n false, // draft (optional)\n false, // html (optional)\n \"\" // scheduledAt (optional)\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(result);\n })\n);\n```\n```java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Messaging;\n\nClient client = new Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\nMessaging messaging = new Messaging(client);\n\nmessaging.createEmail(\n \"<MESSAGE_ID>\", // messageId\n \"<SUBJECT>\", // subject\n \"<CONTENT>\", // content\n listOf(), // topics (optional)\n listOf(), // users (optional)\n listOf(), // targets (optional)\n listOf(), // cc (optional)\n listOf(), // bcc (optional)\n false, // draft (optional)\n false, // html (optional)\n \"\" // scheduledAt (optional)\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(result);\n })\n);\n```\n```swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\") // Your secret API key\n\nlet messaging = Messaging(client)\n\nlet message = try await messaging.createEmail(\n messageId: \"<MESSAGE_ID>\",\n subject: \"<SUBJECT>\",\n content: \"<CONTENT>\",\n topics: [], // optional\n users: [], // optional\n targets: [], // optional\n cc: [], // optional\n bcc: [], // optional\n draft: false, // optional\n html: xfalse, // optional\n scheduledAt: \"\" // optional\n)\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::messaging::Messaging;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n let client = Client::new()\n .set_endpoint(\"https://<REGION>.cloud.appwrite.io/v1\")\n .set_project(\"<PROJECT_ID>\")\n .set_key(\"919c2d18fb5d4...a2ae413da83346ad2\");\n\n let messaging = Messaging::new(&client);\n\n let message = messaging.create_email(\n \"<MESSAGE_ID>\",\n \"<SUBJECT>\",\n \"<CONTENT>\",\n Some(vec![]), // topics (optional)\n Some(vec![]), // users (optional)\n Some(vec![]), // targets (optional)\n Some(vec![]), // cc (optional)\n Some(vec![]), // bcc (optional)\n None, // attachments (optional)\n Some(false), // draft (optional)\n Some(false), // html (optional)\n None, // scheduled_at (optional)\n ).await?;\n\n println!(\"{:?}\", message);\n Ok(())\n}\n```\n{% /multicode %}\n\n\n# Schedule emails {% #schedule-emails %}\nTo 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.\n{% multicode %}\n```server-nodejs\nconst sdk = require('node-appwrite');\n\n// Init SDK\nconst client = new sdk.Client();\n\nconst messaging = new sdk.Messaging(client);\n\nclient\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n;\n\nconst message = await messaging.createEmail(\n '<MESSAGE_ID>', // messageId\n '<SUBJECT>', // subject\n '<CONTENT>', // content\n [], // topics (optional)\n [], // users (optional)\n [], // targets (optional)\n [], // cc (optional)\n [], // bcc (optional)\n false, // draft (optional)\n false, // html (optional)\n '2025-02-13T22:01:00+0000' // scheduledAt (optional)\n );\n```\n```deno\nimport * as sdk from \"npm:node-appwrite\";\n\n// Init SDK\nlet client = new sdk.Client();\n\nlet messaging = new sdk.Messaging(client);\n\nclient\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n;\n\nconst message - await messaging.createEmail(\n '<MESSAGE_ID>', // messageId\n '<SUBJECT>', // subject\n '<CONTENT>', // content\n [], // topics (optional)\n [], // users (optional)\n [], // targets (optional)\n [], // cc (optional)\n [], // bcc (optional)\n false, // draft (optional)\n false, // html (optional)\n '2025-02-13T22:01:00+0000' // scheduledAt (optional)\n );\n```\n```php\n<?php\n\nuse Appwrite\\Client;\nuse Appwrite\\Services\\Messaging;\n\n$client = new Client();\n\n$client\n ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n ->setProject('<PROJECT_ID>') // Your project ID\n ->setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n;\n\n$messaging = new Messaging($client);\n\n$result = $messaging->createEmail( \n messageId: '<MESSAGE_ID>', \n subject: '<SUBJECT>', \n content: '<CONTENT>', \n topics: [], // optional\n users: [], // optional\n targets: [], // optional\n cc: [], // optional\n bcc: [], // optional\n draft: false, // optional\n html: false, // optional\n scheduledAt: '2025-02-13T22:01:00+0000'\n);\n```\n```python\nfrom appwrite.client import Client\nfrom appwrite.services.messaging import Messaging\n\nclient = Client()\n\n(client\n .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('<PROJECT_ID>') # Your project ID\n .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key\n)\n\nmessaging = Messaging(client)\n\nresult = messaging.create_email( \n message_id = '<MESSAGE_ID>', \n subject = '<SUBJECT>', \n content = '<CONTENT>', \n topics = [], # optional\n users = [], # optional\n targets = [], # optional\n cc = [], # optional\n bcc = [], # optional\n draft = False, # optional\n html = False, # optional\n scheduled_at = '2025-02-13T22:01:00+0000'\n)\n```\n```ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('<PROJECT_ID>') # Your project ID\n .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key\n\nmessaging = Messaging.new(client)\n\nresponse = messaging.create_email(\n message_id: '<MESSAGE_ID>', \n subject: '<SUBJECT>', \n content: '<CONTENT>', \n topics: [], # optional\n users: [], # optional\n targets: [], # optional\n cc: [], # optional\n bcc: [], # optional\n draft: false, # optional\n html: false, # optional\n scheduled_at: '2025-02-13T22:01:00+0000' \n)\n\nputs response.inspect\n```\n```csharp\nusing Appwrite;\nusing Appwrite.Services;\nusing Appwrite.Models;\n\nvar client = new Client()\n .SetEndPoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .SetProject(\"<PROJECT_ID>\") // Your project ID\n .SetKey(\"919c2d18fb5d4...a2ae413da83346ad2\");// Your secret API key\n\nvar messaging = new Messaging(client);\n\nMessage result = await messaging.CreateEmail(\n messageId: \"<MESSAGE_ID>\",\n subject: \"<SUBJECT>\",\n content: \"<CONTENT>\" \n topics: new List<string> {} // optional \n users: new List<string> {} // optional \n targets: new List<string> {} // optional \n cc: new List<string> {} // optional \n bcc: new List<string> {} // optional \n draft: false // optional \n html: false // optional \n scheduledAt: \"2025-02-13T22:01:00+0000\"); // optional\n```\n```dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\nimport 'package:dart_appwrite/enums.dart';\nimport 'package:dart_appwrite/models.dart';\n\nvoid main() { // Init SDK\n Client client = Client();\n Messaging messaging = Messaging(client);\n\n client\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n ;\n\n Future result = messaging.createEmail(\n messageId: '<MESSAGE_ID>',\n subject: '<SUBJECT>',\n content: '<CONTENT>',\n topics: [], // optional\n users: [], // optional\n targets: [], // optional\n cc: [], // optional\n bcc: [], // optional\n draft: false, // optional\n html: false, // optional\n scheduledAt: '2025-02-13T22:01:00+0000', \n );\n\n result\n .then((response) {\n print(response);\n }).catchError((error) {\n print(error.response);\n });\n}\n```\n```kotlin\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Messaging;\n\nClient client = new Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\nMessaging messaging = new Messaging(client);\n\nmessaging.createEmail(\n \"<MESSAGE_ID>\", // messageId\n \"<SUBJECT>\", // subject\n \"<CONTENT>\", // content\n listOf(), // topics (optional)\n listOf(), // users (optional)\n listOf(), // targets (optional)\n listOf(), // cc (optional)\n listOf(), // bcc (optional)\n false, // draft (optional)\n false, // html (optional)\n \"2025-02-13T22:01:00+0000\" // scheduledAt (optional)\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(result);\n })\n);\n```\n```java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Messaging;\n\nClient client = new Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\");// Your secret API key\n\nMessaging messaging = new Messaging(client);\n\nmessaging.createEmail(\n \"<MESSAGE_ID>\", // messageId\n \"<SUBJECT>\", // subject\n \"<CONTENT>\", // content\n listOf(), // topics (optional)\n listOf(), // users (optional)\n listOf(), // targets (optional)\n listOf(), // cc (optional)\n listOf(), // bcc (optional)\n false, // draft (optional)\n false, // html (optional)\n \"2025-02-13T22:01:00+0000\" \n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(result);\n })\n);\n```\n```swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\") // Your secret API key\n\nlet messaging = Messaging(client)\n\nlet message = try await messaging.createEmail(\n messageId: \"<MESSAGE_ID>\",\n subject: \"<SUBJECT>\",\n content: \"<CONTENT>\",\n topics: [], // optional\n users: [], // optional\n targets: [], // optional\n cc: [], // optional\n bcc: [], // optional\n draft: false, // optional\n html: xfalse, // optional\n scheduledAt: \"2025-02-13T22:01:00+0000\"\n)\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::messaging::Messaging;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n let client = Client::new()\n .set_endpoint(\"https://<REGION>.cloud.appwrite.io/v1\")\n .set_project(\"<PROJECT_ID>\")\n .set_key(\"919c2d18fb5d4...a2ae413da83346ad2\");\n\n let messaging = Messaging::new(&client);\n\n let message = messaging.create_email(\n \"<MESSAGE_ID>\",\n \"<SUBJECT>\",\n \"<CONTENT>\",\n Some(vec![]), // topics (optional)\n Some(vec![]), // users (optional)\n Some(vec![]), // targets (optional)\n Some(vec![]), // cc (optional)\n Some(vec![]), // bcc (optional)\n None, // attachments (optional)\n Some(false), // draft (optional)\n Some(false), // html (optional)\n Some(\"2025-02-13T22:01:00+0000\"), // scheduled_at (optional)\n ).await?;\n\n println!(\"{:?}\", message);\n Ok(())\n}\n```\n{% /multicode %}"}, {"path": "docs/products/messaging/send-push-notifications", "title": "Send push notification", "description": "Send push notification to your users using Appwrite Messaging.", "content": "You can send, schedule, and manage push notifications to your apps using Appwrite Messaging.\nPush notifications can be used to deliver new message notifications, app updates, promotional offers,\nand other messages straight to your user's devices.\n\n{% section #add-provider step=1 title=\"Add provider\" %}\nPush notifications must be sent through third-party providers, like Apple Push Notification service and Firebase Cloud Messaging.\nThe push notification APIs for Apple and Android devices can only be accessed through these services.\n\nYou must configure these services before you can send your first push notification.\n\n{% cards %}\n{% cards_item href=\"/docs/products/messaging/apns\" title=\"APNS\" icon=\"icon-apple\" %}\nConfigure APNs for push notification to Apple devices.\n{% /cards_item %}\n{% cards_item href=\"/docs/products/messaging/fcm\" title=\"FCM\" icon=\"web-icon-firebase\" %}\nConfigure FCM for push notification to Android and Apple devices.\n{% /cards_item %}\n{% /cards %}\n\n{% /section %}\n{% section #add-targets step=2 title=\"Add targets\" %}\nBefore sending your first push notification, your application must register itself for push notification,\nthen provide the device token to Appwrite.\n\n{% tabs %}\n{% tabsitem #apple-apns title=\"APNs for Apple\" %}\nFirst, enable push notification in your app.\nAdd push notification capability to your app by clicking your root-level app in XCode > **Signing & Capabilities** > {% icon icon=\"plus\" size=\"m\" /%} Capabilities > Search for **Push Notifications**.\n\n{% only_dark %} ![Authentication Key](/images/docs/messaging/providers/apns/dark/xcode-enable-pn.avif) {% /only_dark %} {% only_light %} ![Authentication Key](/images/docs/messaging/providers/apns/xcode-enable-pn.avif) {% /only_light %}\n\nFirst, register for remote notifications in your app delegate's `application(_:didFinishLaunchingWithOptions:)` method.\n\n```swift\nfunc application(\n _ application: UIApplication,\n didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil\n) -> Bool {\n UNUserNotificationCenter.current().delegate = self\n UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { granted, _ in\n if granted {\n DispatchQueue.main.async {\n application.registerForRemoteNotifications()\n }\n }\n }\n return true\n}\n```\n\nNext, create a handler for when the app receives the push notification device token.\n```swift\nfunc application(\n _ application: UIApplication,\n didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data\n) {\n /* store this `token` */\n let token = deviceToken.map { String(format: \"%.2hhx\", $0) }.joined()\n}\n```\n\nSince the token is saved in `UserDefaults`, you can access it from anywhere in your app.\nWith this saved `apnsToken`, you can create a push target with Appwrite when the user logs in.\nEach push target is associated with an account, heres an example with an email password login.\nThe same logic applies to all types of login methods.\n```swift\nfunc login() async {\n do {\n let session = try await account.createEmailPasswordSession(email: username, password: password)\n\n let token = /* Retrieve the stored push token */\n\n try await account.createPushTarget({\n targetId: ID.unique(),\n identifier: token\n })\n } catch {\n print(\"Login failed: \\(error.localizedDescription)\")\n }\n}\n```\n{% /tabsitem %}\n\n{% tabsitem #android-fcm title=\"FCM for Android\" %}\nBefore you can send push notifications using FCM, make sure you'd followed the steps to\n[Add Firebase to your Android project](https://firebase.google.com/docs/android/setup).\n\nAfter adding Firebase to your Android project and adding the `google-services.json` to your project,\ninitialize Firebase in your main activity and fetch the FCM registration token.\n\n```kotlin\nclass MainActivity : AppCompatActivity() {\n override fun onCreate() {\n // Initialize Firebase\n FirebaseApp.initializeApp(this)\n\n // Set the FCM token\n FirebaseMessaging.getInstance().token.addOnCompleteListener(OnCompleteListener { task ->\n if (task.isSuccessful) {\n /* store this `token` */\n val token = task.result\n }\n })\n }\n}\n```\n\nAppwrite's push targets are associated with accounts.\nTypically, you would create a push target when the user logs in.\n\nFor example, when the user logs in with email and password, your app\ncan register itself as a target after handling the login.\n\n```kotlin\nfun login(email: String, password: String) {\n viewModelScope.launch {\n try {\n val session = account.createEmailPasswordSession(email, password)\n\n let token = /* Retrieve the stored push token */\n\n /* store the `target.id` */\n val target = account.createPushTarget(\n targetId = ID.unique(),\n identifier = token\n )\n } catch (e: AppwriteException) {\n Log.e(\"Login\", \"Failed: ${e.message}\")\n }\n }\n}\n```\n\nLastly, because FCM push tokens can change, we need to add a service to handle FCM token\nrefreshes and update the target with Appwrite Messaging.\n\nCreate a new service that extends `FirebaseMessagingService` which handles the event where\nthe FCM token is updated.\n\n```kotlin\nclass MessagingService : FirebaseMessagingService() {\n override fun onNewToken(token: String) {\n super.onNewToken(token)\n\n /* store the `token` */\n /* If the user is logged in, update the push target */\n runBlocking {\n account?.updatePushTarget(/* retrieve saved `target.id` */, token)\n }\n }\n}\n```\n\nIn your `AndroidManifest.xml`, register this new service.\n```xml\n<service android:name=\"<YOUR_NOTIFICATION_HANDLER_SERVICE>\" android:exported=\"false\">\n <intent-filter>\n <action android:name=\"com.google.firebase.MESSAGING_EVENT\" />\n </intent-filter>\n</service>\n```\n{% /tabsitem %}\n\n{% tabsitem #apple-fcm title=\"FCM for Apple\" %}\nBefore you can send push notifications using FCM, make sure you'd followed the steps to\n[Add Firebase to your iOS project](https://firebase.google.com/docs/ios/setup).\n\nAfter adding Firebase to your iOS project and adding the `GoogleService-Info.plist` to the root of your project.\n\n\nNext, add your APNs key to Firebase.\n1. Head to **Apple Developer Member Center** > **Program resources** > **Certificates, Identifiers & Profiles** > **Keys**. The key needs **Apple Push Notification Service** enabled.\n1. Create a new key, note down the key ID and download your key.\n1. In Firebase console, go to *Settings** > **Cloud Messaging** > **APNs authentication key** > click **Upload**. Upload your key here.\n1. Add push notification capability to your app by clicking your root-level app in XCode > **Signing & Capabilities** > {% icon icon=\"plus\" size=\"m\" /%} Capabilities > Search for **Push Notifications**.\n1. If using SwiftUI, disable swizzling by setting `FirebaseAppDelegateProxyEnabled` to `NO` in your `Info.plist`.\n\nInitialize Firebase in your app delegate's `application(_:didFinishLaunchingWithOptions:)` method,\nimplement the messaging delegate protocol, and register for remote notifications.\n\n```swift\nfunc application(\n _ application: UIApplication,\n didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil\n) -> Bool {\n FirebaseApp.configure()\n Messaging.messaging().delegate = self\n UNUserNotificationCenter.current().delegate = self\n\n UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { granted, _ in\n if granted {\n DispatchQueue.main.async {\n application.registerForRemoteNotifications()\n }\n }\n }\n return true\n}\n```\nYour APNS token can change, so you need to handle the token refresh event and update the target with Appwrite Messaging.\nImplement `didReceiveRegistrationToken`, which is called when the FCM token is updated.\n```swift\nfunc messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) {\n /* store the fcmToken */\n guard let fcmToken = fcmToken else { return }\n\n Task {\n do {\n _ = try await account.get()\n try await account.createPushTarget(targetId: ID.unique(), identifier: fcmToken)\n } catch {\n print(\"Failed to create push target: \\(error.localizedDescription)\")\n }\n }\n}\n```\nSince the token is saved in `UserDefaults`, you can access it from anywhere in your app.\nWith this saved `fcmToken`, you can create a push target with Appwrite when the user logs in.\nEach push target is associated with an account, here's an example with an email password login.\nThe same logic applies to all types of login methods.\n```swift\nfunc login() async {\n do {\n let session = try await account.createEmailPasswordSession(email: username, password: password)\n\n let token = /* Retrieve stored push token */\n\n let target = try await account.createPushTarget(targetId: ID.unique(), identifier: token)\n } catch {\n print(\"Login failed: \\(error.localizedDescription)\")\n }\n}\n\n```\n\nIf you have disabled method swizzling, or you are building a SwiftUI app,\nyou'll need to explicitly map your APNs token to the FCM registration token.\nImplement the `didRegisterForRemoteNotificationsWithDeviceToken` method to get the device token and save it to FCM.\n```swift\nfunc application(\n _ application: UIApplication,\n didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data\n) {\n Messaging.messaging().apnsToken = deviceToken\n}\n```\n{% /tabsitem %}\n{% /tabs %}\n{% /section %}\n\n{% section #request-permissions step=3 title=\"Request permissions\" %}\nYour app must ask for permission to receive push notification from the user.\n\n{% tabs %}\n{% tabsitem #apple-apns title=\"Apple with APNs\" %}\nBefore your app can receive push notifications, you need to request the user for permissions.\nAppwrite provides a utility to help request permissions to display notificaitons.\n\nYou can learn more about requesting permissions from the\n[Apple Developer Documentation](https://developer.apple.com/documentation/usernotifications/asking-permission-to-use-notifications).\n{% /tabsitem %}\n\n{% tabsitem #android-fcm title=\"FCM for Android\" %}\nFirst, add `POST_NOTIFICATIONS` to your `AndroidManifest.xml`.\n\n```xml\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n package=\"YOUR_PACKAGE\">\n <uses-permission android:name=\"android.permission.POST_NOTIFICATIONS\"/>\n <!-- ... rest of your manifest -->\n```\n\nThen, you'll also need to request [runtime permissions](https://developer.android.com/training/permissions/requesting)\nfrom your users using the `android.permission.POST_NOTIFICATIONS` permission.\n{% /tabsitem %}\n\n{% tabsitem #apple-fcm title=\"FCM for Apple\" %}\nBefore your app can receive push notifications, you need to request the user for permissions.\nAppwrite provides a utility to help request permissions to display notificaitons.\n\nYou can learn more about requesting permissions from the\n[Apple Developer Documentation](https://developer.apple.com/documentation/usernotifications/asking-permission-to-use-notifications).\n\nWhen an FCM registration token is generated, the library uploads the identifier and configuration data to Firebase.\nIf you wish to give your users the ability to explicitly opt out of sending data to Firebase,\nyou can disable automatic initialization and manually initialize the library when the user grants permission.\n\nDisable auto-initialization by setting `FirebaseMessagingAutoInitEnabled` to `NO` in your `Info.plist`.\n```text\nFirebaseMessagingAutoInitEnabled = NO\n```\n\nThen, manually initialize the library when the user grants permission.\n``` swift\nMessaging.messaging().autoInitEnabled = true\n```\n{% /tabsitem %}\n{% /tabs %}\n\n{% /section %}\n\n{% section #send-message step=4 title=\"Send message\" %}\nYou can send messages in both the Appwrite Console and programmatically using the Appwrite Server SDK.\n\n{% info title=\"Sandbox\" %}\nIf you enabled **Sandbox** on your APNs provider, Appwrite will send push notifications to the development APNs environment.\nThis requires you to use a **Development profile** in XCode.\n\nIf XCode is not default to a development profile,\nclick your root-level app in XCode > **Signing & Capabilities** > {% icon icon=\"plus\" size=\"m\" /%} Capabilities > uncheck **Automatically manage signing**.\nThen manually select a **Provisioning profile** that is a **Distribution profile**.\n{% /info %}\n\n{% tabs %}\n{% tabsitem #console title=\"Console\" %}\nTo send a test message, navigate to **Messaging** > **Messages** > {% icon icon=\"plus\" size=\"m\" /%} **Create message** > **Push notification**.\n{% only_dark %}\n![Create email message](/images/docs/messaging/messages/dark/create-push-notification.avif)\n{% /only_dark %}\n{% only_light %}\n![Create email message](/images/docs/messaging/messages/create-push-notification.avif)\n{% /only_light %}\n\nAdd your message and in the targets step, select one of your test targets. Set the schedule to **Now** and click **Send**.\n\nVerify that you can receive the message in your inbox. If not, check for logs in the Appwrite Console or in your provider's logs.\n{% /tabsitem %}\n\n{% tabsitem #server-sdk title=\"Server SDK\" %}\nTo send a message programmatically, use an [Appwrite Server SDK](/docs/sdks#server).\n{% multicode %}\n```server-nodejs\nconst sdk = require('node-appwrite');\n\n// Init SDK\nconst client = new sdk.Client();\n\nconst messaging = new sdk.Messaging(client);\n\nclient\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n;\n\nconst message = await messaging.createPush({\n messageId: '<MESSAGE_ID>',\n title: '<TITLE>',\n body: '<BODY>',\n topics: [], // optional\n users: [], // optional\n targets: [], // optional\n data: {}, // optional\n action: '<ACTION>', // optional\n icon: '<ICON>', // optional\n sound: '<SOUND>', // optional\n color: '<COLOR>', // optional\n tag: '<TAG>', // optional\n badge: '<BADGE>', // optional\n draft: false, // optional\n scheduledAt: '' // optional\n });\n```\n```deno\nimport * as sdk from \"npm:node-appwrite\";\n\n// Init SDK\nlet client = new sdk.Client();\n\nlet messaging = new sdk.Messaging(client);\n\nclient\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n;\n\nconst message = await messaging.createPush({\n messageId: '<MESSAGE_ID>',\n title: '<TITLE>',\n body: '<BODY>',\n topics: [], // optional\n users: [], // optional\n targets: [], // optional\n data: {}, // optional\n action: '<ACTION>', // optional\n icon: '<ICON>', // optional\n sound: '<SOUND>', // optional\n color: '<COLOR>', // optional\n tag: '<TAG>', // optional\n badge: '<BADGE>', // optional\n draft: false, // optional\n scheduledAt: '' // optional\n });\n```\n```php\n<?php\n\nuse Appwrite\\Client;\nuse Appwrite\\Services\\Messaging;\n\n$client = new Client();\n\n$client\n ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n ->setProject('<PROJECT_ID>') // Your project ID\n ->setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n;\n\n$messaging = new Messaging($client);\n\n$result = $messaging->createPush(\n messageId: '<MESSAGE_ID>',\n title: '<TITLE>',\n body: '<BODY>',\n topics: [], // optional\n users: [], // optional\n targets: [], // optional\n data: [], // optional\n action: '<ACTION>', // optional\n icon: '<ICON>', // optional\n sound: '<SOUND>', // optional\n color: '<COLOR>', // optional\n tag: '<TAG>', // optional\n badge: '<BADGE>', // optional\n draft: false, // optional\n scheduledAt: '' // optional\n);\n```\n```python\nfrom appwrite.client import Client\nfrom appwrite.services.messaging import Messaging\n\nclient = Client()\n\n(client\n .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('<PROJECT_ID>') # Your project ID\n .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key\n)\n\nmessaging = Messaging(client)\n\nresult = messaging.create_push(\n message_id = '<MESSAGE_ID>',\n title = '<TITLE>',\n body = '<BODY>',\n topics = [], # optional\n users = [], # optional\n targets = [], # optional\n data = {}, # optional\n action = '<ACTION>',# optional\n icon = '<ICON>', # optional\n sound = '<SOUND>', # optional\n color = '<COLOR>', # optional\n tag = '<TAG>', # optional\n badge = '<BADGE>', # optional\n draft = False, # optional\n scheduled_at = '' # optional\n)\n```\n```ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('<PROJECT_ID>') # Your project ID\n .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key\n\nmessaging = Messaging.new(client)\n\nresponse = messaging.create_push(\n message_id: '<MESSAGE_ID>',\n title: '<TITLE>',\n body: '<BODY>',\n topics: [], # optional\n users: [], # optional\n targets: [], # optional\n data: {}, # optional\n action: '<ACTION>', # optional\n icon: '<ICON>', # optional\n sound: '<SOUND>', # optional\n color: '<COLOR>', # optional\n tag: '<TAG>', # optional\n badge: '<BADGE>', # optional\n draft: false, # optional\n scheduled_at: '' # optional\n)\n\nputs response.inspect\n```\n```csharp\nusing Appwrite;\nusing Appwrite.Services;\nusing Appwrite.Models;\n\nvar client = new Client()\n .SetEndPoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .SetProject(\"<PROJECT_ID>\") // Your project ID\n .SetKey(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\nvar messaging = new Messaging(client);\n\nMessage result = await messaging.CreatePush(\n messageId: \"[MESSAGE_ID]\",\n title: \"[TITLE]\",\n body: \"[BODY]\"\n topics: new List<string> {} // optional\n users: new List<string> {} // optional\n targets: new List<string> {} // optional\n data: [object] // optional\n action: \"[ACTION]\" // optional\n icon: \"[ICON]\" // optional\n sound: \"[SOUND]\" // optional\n color: \"[COLOR]\" // optional\n tag: \"[TAG]\" // optional\n badge: \"[BADGE]\" // optional\n draft: false // optional\n scheduledAt: \"\"); // optional\n```\n```dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\nimport 'package:dart_appwrite/enums.dart';\nimport 'package:dart_appwrite/models.dart';\n\nvoid main() async { // Init SDK\n Client client = Client();\n Messaging messaging = Messaging(client);\n\n client\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n ;\n\n Future result = await messaging.createPush(\n messageId: '<MESSAGE_ID>',\n title: '<TITLE>',\n body: '<BODY>',\n topics: [], // optional\n users: [], // optional\n targets: [], // optional\n data: {}, // optional\n action: '<ACTION>', // optional\n icon: '<ICON>', // optional\n sound: '<SOUND>', // optional\n color: '<COLOR>', // optional\n tag: '<TAG>', // optional\n badge: '<BADGE>', // optional\n draft: false, // optional\n scheduledAt: '', // optional\n );\n\n result\n .then((response) {\n print(response);\n }).catchError((error) {\n print(error.response);\n });\n}\n```\n```kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Messaging\n\nval client = Client(context)\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\") // Your secret API key\n\nval messaging = Messaging(client)\n\nval response = messaging.createPush(\n messageId = \"<MESSAGE_ID>\",\n title = \"<TITLE>\",\n body = \"<BODY>\",\n topics = listOf(), // optional\n users = listOf(), // optional\n targets = listOf(), // optional\n data = mapOf( \"a\" to \"b\" ), // optional\n action = \"<ACTION>\", // optional\n icon = \"<ICON>\", // optional\n sound = \"<SOUND>\", // optional\n color = \"<COLOR>\", // optional\n tag = \"<TAG>\", // optional\n badge = \"<BADGE>\", // optional\n draft = false, // optional\n scheduledAt = \"\" // optional\n)\n```\n```java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Messaging;\n\nClient client = new Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\nMessaging messaging = new Messaging(client);\n\nmessaging.createPush(\n \"<MESSAGE_ID>\", // messageId\n \"<TITLE>\", // title\n \"<BODY>\", // body\n listOf(), // topics (optional)\n listOf(), // users (optional)\n listOf(), // targets (optional)\n mapOf( \"a\" to \"b\" ), // data (optional)\n \"<ACTION>\", // action (optional)\n \"<ICON>\", // icon (optional)\n \"<SOUND>\", // sound (optional)\n \"<COLOR>\", // color (optional)\n \"<TAG>\", // tag (optional)\n \"<BADGE>\", // badge (optional)\n false, // draft (optional)\n \"\" // scheduledAt (optional)\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(result);\n })\n);\n```\n```swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\") // Your secret API key\n\nlet messaging = Messaging(client)\n\nlet message = try await messaging.createPush(\n messageId: \"<MESSAGE_ID>\",\n title: \"<TITLE>\",\n body: \"<BODY>\",\n topics: [], // optional\n users: [], // optional\n targets: [], // optional\n data: [:], // optional\n action: \"<ACTION>\", // optional\n icon: \"<ICON>\", // optional\n sound: \"<SOUND>\", // optional\n color: \"<COLOR>\", // optional\n tag: \"<TAG>\", // optional\n badge: \"<BADGE>\", // optional\n draft: false, // optional\n scheduledAt: \"\" // optional\n)\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::messaging::Messaging;\nuse serde_json::json;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n let client = Client::new()\n .set_endpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .set_project(\"<PROJECT_ID>\") // Your project ID\n .set_key(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\n let messaging = Messaging::new(&client);\n\n let message = messaging.create_push(\n \"<MESSAGE_ID>\",\n Some(\"<TITLE>\"), // title (optional)\n Some(\"<BODY>\"), // body (optional)\n Some(vec![]), // topics (optional)\n Some(vec![]), // users (optional)\n Some(vec![]), // targets (optional)\n Some(json!({})), // data (optional)\n Some(\"<ACTION>\"), // action (optional)\n Some(\"<IMAGE>\"), // image (optional)\n Some(\"<ICON>\"), // icon (optional)\n Some(\"<SOUND>\"), // sound (optional)\n Some(\"<COLOR>\"), // color (optional)\n Some(\"<TAG>\"), // tag (optional)\n Some(1), // badge (optional)\n Some(false), // draft (optional)\n None, // scheduled_at (optional)\n None, // content_available (optional)\n None, // critical (optional)\n None, // priority (optional)\n ).await?;\n\n println!(\"{:?}\", message);\n Ok(())\n}\n```\n{% /multicode %}\n{% /tabsitem %}\n{% /tabs %}\n{% /section %}"}, {"path": "docs/products/messaging/send-sms-messages", "title": "Send SMS messages", "description": "Send SMS messages to your users using Appwrite Messaging.", "content": "You can send custom SMS messages to your app's users using Appwrite Messaging and a connected SMTP service.\nThis guide takes you through the implementation path of adding SMS messaging to your app.\n\n# Add a provider {% #add-a-provider %}\nAppwrite supports [Twilio](/docs/products/messaging/twilio/),\n[MSG91](/docs/products/messaging/msg91/),\n[Telesign](/docs/products/messaging/telesign/),\n[Textmagic](/docs/products/messaging/textmagic/),\nand [Vonage](/docs/products/messaging/vonage/)\nas SMS providers. You must configure one of them as a provider.\n{% only_dark %}\n![Add a SMTP provider](/images/docs/messaging/providers/twilio/dark/provider.avif)\n{% /only_dark %}\n{% only_light %}\n![Add a SMTP provider](/images/docs/messaging/providers/twilio/provider.avif)\n{% /only_light %}\nTo add a new provider navigate to **Messaging** > **Providers** > {% icon icon=\"plus\" size=\"m\" /%} **Add provider** > **SMS**\nand follow the wizard. You can find more details about configuring in the provider guides for\n[Twilio](/docs/products/messaging/twilio/),\n[MSG91](/docs/products/messaging/msg91/),\n[Telesign](/docs/products/messaging/telesign/),\n[Textmagic](/docs/products/messaging/textmagic/),\nand [Vonage](/docs/products/messaging/vonage/).\n\n# Add targets {% #add-targets %}\nIn Appwrite Messaging, each user has **targets** like their email, phone number, and devices with your app installed.\nYou can deliver messages to users through their **targets**.\n\n{% only_dark %}\n![Target overview](/images/docs/messaging/targets/dark/target-overview.avif)\n{% /only_dark %}\n{% only_light %}\n![Target overview](/images/docs/messaging/targets/target-overview.avif)\n{% /only_light %}\n\nIf the user signed up with phone (SMS) authentication, their account would already have a phone number as a target.\nDuring development, you can add targets to existing accounts by navigating to **Authentication** > **Users** > **Select a user** > **Targets** > **Add a subscriber**.\n{% only_dark %}\n![Add a target](/images/docs/messaging/targets/dark/add-targets.avif)\n{% /only_dark %}\n{% only_light %}\n![Add a target](/images/docs/messaging/targets/add-targets.avif)\n{% /only_light %}\n\nYou 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.\n\n{% multicode %}\n```server-nodejs\nconst sdk = require('node-appwrite');\n\nconst client = new sdk.Client()\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2'); // Your secret API key\n\nconst users = new sdk.Users(client);\n\nconst target = await users.createTarget(\n '<USER_ID>', // userId\n '<TARGET_ID>', // targetId\n sdk.MessagingProviderType.Phone, // providerType\n '<IDENTIFIER>', // identifier\n '<PROVIDER_ID>', // providerId (optional)\n '<NAME>' // name (optional)\n);\n```\n```deno\nimport * as sdk from \"npm:node-appwrite\";\n\nconst client = new sdk.Client()\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setJWT('eyJhbVCJ9.eyJ...'); // Your secret JSON Web Token\n\nconst users = new sdk.Users(client);\n\nconst target = await users.createTarget(\n '<USER_ID>', // userId\n '<TARGET_ID>', // targetId\n sdk.MessagingProviderType.Phone, // providerType\n '<IDENTIFIER>', // identifier\n '<PROVIDER_ID>', // providerId (optional)\n '<NAME>' // name (optional)\n);\n```\n```php\n<?php\n\nuse Appwrite\\Client;\nuse Appwrite\\Services\\Users;\nuse Appwrite\\Enums\\MessagingProviderType;\n\n$client = (new Client())\n ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n ->setProject('<PROJECT_ID>') // Your project ID\n ->setKey('919c2d18fb5d4...a2ae413da83346ad2'); // Your secret API key\n\n$users = new Users($client);\n\n$target = $users->createTarget(\n userId: '<USER_ID>',\n targetId: '<TARGET_ID>',\n providerType: MessagingProviderType::EMAIL(),\n identifier: '<IDENTIFIER>',\n providerId: '<PROVIDER_ID>', // optional\n name: '<NAME>' // optional\n);\n```\n```python\nfrom appwrite.client import Client\nfrom appwrite.enums import MessagingProviderType\n\nclient = Client()\nclient.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\nclient.set_project('<PROJECT_ID>') # Your project ID\nclient.set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key\n\nusers = Users(client)\n\ntarget = users.create_target(\n user_id = '<USER_ID>',\n target_id = '<TARGET_ID>',\n provider_type = MessagingProviderType.PHONE,\n identifier = '<IDENTIFIER>',\n provider_id = '<PROVIDER_ID>', # optional\n name = '<NAME>' # optional\n)\n```\n```ruby\nrequire 'appwrite'\n\ninclude Appwrite\ninclude Appwrite::Enums\n\nclient = Client.new\n .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('<PROJECT_ID>') # Your project ID\n .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key\n\nusers = Users.new(client)\n\ntarget = users.create_target(\n user_id: '<USER_ID>',\n target_id: '<TARGET_ID>',\n provider_type: MessagingProviderType::EMAIL,\n identifier: '<IDENTIFIER>',\n provider_id: '<PROVIDER_ID>', # optional\n name: '<NAME>' # optional\n)\n\nputs target.inspect\n```\n```csharp\nusing Appwrite;\nusing Appwrite.Services;\nusing Appwrite.Models;\nusing Appwrite.Enums;\n\nClient client = new Client()\n .SetEndPoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .SetProject(\"<PROJECT_ID>\") // Your project ID\n .SetKey(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\nUsers users = new Users(client);\n\nTarget target = await users.CreateTarget(\n userId: \"<USER_ID>\",\n targetId: \"<TARGET_ID>\",\n providerType: MessagingProviderType.Phone,\n identifier: \"<IDENTIFIER>\",\n providerId: \"<PROVIDER_ID>\", // optional\n name: \"<NAME>\" // optional\n);\n```\n```dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\nimport 'package:dart_appwrite/enums.dart';\nimport 'package:dart_appwrite/models.dart';\n\nClient client = Client()\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2'); // Your secret API key\n\nUsers users = Users(client);\n\nTarget target = await users.createTarget(\n userId: '<USER_ID>',\n targetId: '<TARGET_ID>',\n providerType: MessagingProviderType.phone,\n identifier: '<IDENTIFIER>',\n providerId: '<PROVIDER_ID>', // (optional)\n name: '<NAME>', // (optional)\n);\n\n```\n```kotlin\nimport io.appwrite.Client\nimport io.appwrite.coroutines.CoroutineCallback\nimport io.appwrite.services.Users\nimport io.appwrite.enums.MessagingProviderType\n\nval client = Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\") // Your secret API key\n\nval users = Users(client)\n\nval target = users.createTarget(\n userId = \"<USER_ID>\",\n targetId = \"<TARGET_ID>\",\n providerType = MessagingProviderType.PHONE,\n identifier = \"<IDENTIFIER>\",\n providerId = \"<PROVIDER_ID>\", // optional\n name = \"<NAME>\" // optional\n)\n\n```\n```java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Users;\nimport io.appwrite.enums.MessagingProviderType;\n\nClient client = new Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\nUsers users = new Users(client);\n\nusers.createTarget(\n \"<USER_ID>\", // userId\n \"<TARGET_ID>\", // targetId\n MessagingProviderType.PHONE, // providerType\n \"<IDENTIFIER>\", // identifier\n \"<PROVIDER_ID>\", // providerId (optional)\n \"<NAME>\", // name (optional)\n new CoroutineCallback<>((target, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(target);\n })\n);\n```\n```swift\nimport Appwrite\nimport AppwriteEnums\n\nlet client = Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\") // Your secret API key\n\nlet users = Users(client)\n\nlet target = try await users.createTarget(\n userId: \"<USER_ID>\",\n targetId: \"<TARGET_ID>\",\n providerType: .phone,\n identifier: \"<IDENTIFIER>\",\n providerId: \"<PROVIDER_ID>\", // optional\n name: \"<NAME>\" // optional\n)\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::users::Users;\nuse appwrite::enums::messaging_provider_type::MessagingProviderType;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n let client = Client::new()\n .set_endpoint(\"https://<REGION>.cloud.appwrite.io/v1\")\n .set_project(\"<PROJECT_ID>\")\n .set_key(\"919c2d18fb5d4...a2ae413da83346ad2\");\n\n let users = Users::new(&client);\n\n let target = users.create_target(\n \"<USER_ID>\",\n \"<TARGET_ID>\",\n MessagingProviderType::Sms,\n \"<IDENTIFIER>\",\n Some(\"<PROVIDER_ID>\"), // optional\n Some(\"<NAME>\"), // optional\n ).await?;\n\n println!(\"{:?}\", target);\n Ok(())\n}\n```\n{% /multicode %}\n\n# Create topics (optional) {% #create-topics %}\nYou 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.\nThis step is optional if you plan to only send SMS messages to individual targets.\n\nTo create a topic in the Appwrite Console, navigate to **Messaging** > **Topics** > {% icon icon=\"plus\" size=\"m\" /%} **Create topic**.\n{% only_dark %}\n![Add a target](/images/docs/messaging/topics/dark/create-topics.avif)\n{% /only_dark %}\n{% only_light %}\n![Add a target](/images/docs/messaging/topics/create-topics.avif)\n{% /only_light %}\n\nYou can also create topics programmatically using an [Appwrite Server SDK](/docs/references/cloud/server-nodejs/messaging#createTopic).\n{% multicode %}\n```server-nodejs\nconst sdk = require('node-appwrite');\n\nconst client = new sdk.Client()\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2'); // Your secret API key\n\nconst messaging = new sdk.Messaging(client);\n\nconst topic = await messaging.createTopic(\n '<TOPIC_ID>', // topicId\n '<NAME>' // name\n);\n```\n```deno\nimport * as sdk from \"npm:node-appwrite\";\n\nconst client = new sdk.Client()\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2'); // Your secret API key\n\nconst messaging = new sdk.Messaging(client);\n\nconst topic = await messaging.createTopic(\n '<TOPIC_ID>', // topicId\n '<NAME>' // name\n);\n```\n```php\n<?php\n\nuse Appwrite\\Client;\nuse Appwrite\\Services\\Messaging;\n\n$client = (new Client())\n ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n ->setProject('<PROJECT_ID>') // Your project ID\n ->setKey('919c2d18fb5d4...a2ae413da83346ad2'); // Your secret API key\n\n$messaging = new Messaging($client);\n\n$result = $messaging->createTopic(\n topicId: '<TOPIC_ID>',\n name: '<NAME>'\n);\n```\n```python\nfrom appwrite.client import Client\nfrom appwrite.services.messaging import Messaging\n\nclient = Client()\nclient.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\nclient.set_project('<PROJECT_ID>') # Your project ID\nclient.set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key\n\nmessaging = Messaging(client)\n\ntopic = messaging.create_topic(\n topic_id = '<TOPIC_ID>',\n name = '<NAME>'\n)\n```\n```ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('<PROJECT_ID>') # Your project ID\n .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key\n\nmessaging = Messaging.new(client)\n\ntopic = messaging.create_topic(\n topic_id: '<TOPIC_ID>',\n name: '<NAME>'\n)\n```\n```csharp\nusing Appwrite;\nusing Appwrite.Services;\nusing Appwrite.Models;\n\nClient client = new Client()\n .SetEndPoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .SetProject(\"<PROJECT_ID>\") // Your project ID\n .SetKey(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\nMessaging messaging = new Messaging(client);\n\nTopic topic = await messaging.CreateTopic(\n topicId: \"<TOPIC_ID>\",\n name: \"<NAME>\");\n```\n```dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\nimport 'package:dart_appwrite/enums.dart';\nimport 'package:dart_appwrite/models.dart';\n\nClient client = Client()\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2'); // Your secret API key\n\nMessaging messaging = Messaging(client);\n\nTopic topic = await messaging.createTopic(\n topicId: '<TOPIC_ID>',\n name: '<NAME>',\n);\n```\n```kotlin\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Messaging;\n\nval client = new Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\") // Your secret API key\n\nval messaging = new Messaging(client)\n\nval topic = messaging.createTopic(\n topicId = \"<TOPIC_ID>\",\n name = \"<NAME>\"\n)\n```\n```java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Messaging;\n\nClient client = new Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\nMessaging messaging = new Messaging(client);\n\nmessaging.createTopic(\n \"<TOPIC_ID>\", // topicId\n \"<NAME>\", // name\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(result);\n })\n);\n```\n```swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\") // Your secret API key\n\nlet messaging = Messaging(client)\n\nlet topic = try await messaging.createTopic(\n topicId: \"<TOPIC_ID>\",\n name: \"<NAME>\"\n)\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::messaging::Messaging;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n let client = Client::new()\n .set_endpoint(\"https://<REGION>.cloud.appwrite.io/v1\")\n .set_project(\"<PROJECT_ID>\")\n .set_key(\"919c2d18fb5d4...a2ae413da83346ad2\");\n\n let messaging = Messaging::new(&client);\n\n let topic = messaging.create_topic(\n \"<TOPIC_ID>\",\n \"<NAME>\",\n None, // subscribe (optional)\n ).await?;\n\n println!(\"{:?}\", topic);\n Ok(())\n}\n```\n{% /multicode %}\n\n# Send SMS messages {% #send-sms %}\nYou can send SMS messages using a Server SDK.\nTo send an SMS messages immediately, you can call the `createSms` endpoint without passing either the `draft` or `scheduledAt` parameters.\n\n{% multicode %}\n```server-nodejs\nconst sdk = require('node-appwrite');\n\n// Init SDK\nconst client = new sdk.Client()\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2'); // Your secret API key\n\nconst messaging = new sdk.Messaging(client);\n\nconst message = await messaging.createSms(\n '<MESSAGE_ID>', // messageId\n '<CONTENT>', // content\n [], // topics (optional)\n [], // users (optional)\n [], // targets (optional)\n false, // draft (optional)\n '' // scheduledAt (optional)\n);\n```\n```deno\nimport * as sdk from \"npm:node-appwrite\";\n\nconst client = new sdk.Client()\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2'); // Your secret API key\n\nconst messaging = new sdk.Messaging(client);\n\nconst message = await messaging.createSms(\n '<MESSAGE_ID>', // messageId\n '<CONTENT>', // content\n [], // topics (optional)\n [], // users (optional)\n [], // targets (optional)\n false, // draft (optional)\n '' // scheduledAt (optional)\n);\n```\n```php\n<?php\n\nuse Appwrite\\Client;\nuse Appwrite\\Services\\Messaging;\n\n$client = new Client();\n\n$client\n ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n ->setProject('<PROJECT_ID>') // Your project ID\n ->setKey('919c2d18fb5d4...a2ae413da83346ad2'); // Your secret API key\n\n$messaging = new Messaging($client);\n\n$result = $messaging->createSms(\n messageId: '<MESSAGE_ID>',\n content: '<CONTENT>',\n topics: [], // optional\n users: [], // optional\n targets: [], // optional\n draft: false, // optional\n scheduledAt: '' // optional\n);\n```\n```python\nfrom appwrite.client import Client\nfrom appwrite.services.messaging import Messaging\n\nclient = Client()\nclient.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\nclient.set_project('<PROJECT_ID>') # Your project ID\nclient.set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key\n\nmessaging = Messaging(client)\n\nresult = messaging.create_sms(\n message_id = '<MESSAGE_ID>',\n content = '<CONTENT>',\n topics = [], # optional\n users = [], # optional\n targets = [], # optional\n draft = false, # optional\n scheduled_at = '' # optional\n)\n```\n```ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('<PROJECT_ID>') # Your project ID\n .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key\n\nmessaging = Messaging.new(client)\n\nresponse = messaging.create_sms(\n message_id: '<MESSAGE_ID>',\n content: '<CONTENT>',\n topics: [], # optional\n users: [], # optional\n targets: [], # optional\n draft: false, # optional\n scheduled_at: '' # optional\n)\n```\n```csharp\nusing Appwrite;\nusing Appwrite.Services;\nusing Appwrite.Models;\n\nClient client = new Client()\n .SetEndPoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .SetProject(\"<PROJECT_ID>\") // Your project ID\n .SetKey(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\nMessaging messaging = new Messaging(client);\n\nMessage message = await messaging.CreateSMS(\n messageId: \"<MESSAGE_ID>\",\n content: \"<CONTENT>\"\n topics: new List<string> {} // optional\n users: new List<string> {} // optional\n targets: new List<string> {} // optional\n draft: false, // optional\n scheduledAt: \"\"); // optional\n```\n```dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\nimport 'package:dart_appwrite/enums.dart';\nimport 'package:dart_appwrite/models.dart';\n\nClient client = Client();\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2'); // Your secret API key\n\nMessaging messaging = Messaging(client);\n\nMessage message result = await messaging.createSms(\n messageId: '<MESSAGE_ID>',\n content: '<CONTENT>',\n topics: [], // optional\n users: [], // optional\n targets: [], // optional\n draft: false, // optional\n scheduledAt: '' // optional\n);\n```\n```kotlin\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Messaging;\n\nval client = Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\") // Your secret API key\n\nval messaging = Messaging(client)\n\nval message - await messaging.createSms(\n messageId = \"<MESSAGE_ID>\",\n content = \"<CONTENT>\",\n topics = listOf(),\n users = listOf(),\n targets = listOf(),\n draft = false,\n scheduledAt = \"\"\n)\n```\n```java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Messaging;\n\nClient client = new Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\nMessaging messaging = new Messaging(client);\n\nmessaging.createSms(\n \"<MESSAGE_ID>\", // messageId\n \"<CONTENT>\", // content\n listOf(), // topics (optional)\n listOf(), // users (optional)\n listOf(), // targets (optional)\n false, // draft (optional)\n \"\", // scheduledAt (optional)\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(result);\n })\n);\n```\n```swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\") // Your secret API key\n\nlet messaging = Messaging(client)\n\nlet message = try await messaging.createSms(\n messageId: \"<MESSAGE_ID>\",\n content: \"<CONTENT>\",\n topics: [], // optional\n users: [], // optional\n targets: [], // optional\n draft: false, // optional\n scheduledAt: \"\" // optional\n)\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::messaging::Messaging;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n let client = Client::new()\n .set_endpoint(\"https://<REGION>.cloud.appwrite.io/v1\")\n .set_project(\"<PROJECT_ID>\")\n .set_key(\"919c2d18fb5d4...a2ae413da83346ad2\");\n\n let messaging = Messaging::new(&client);\n\n let message = messaging.create_sms(\n \"<MESSAGE_ID>\",\n \"<CONTENT>\",\n Some(vec![]), // topics (optional)\n Some(vec![]), // users (optional)\n Some(vec![]), // targets (optional)\n Some(false), // draft (optional)\n None, // scheduled_at (optional)\n ).await?;\n\n println!(\"{:?}\", message);\n Ok(())\n}\n```\n{% /multicode %}\n\n# Schedule SMS message {% #schedule-sms %}\nTo send an scheduled SMS message, you can call the `createSms` endpoint with `scheduledAt` as a ISO 8601 date time string for the scheduled time.\n{% multicode %}\n```server-nodejs\nconst sdk = require('node-appwrite');\n\n// Init SDK\nconst client = new sdk.Client()\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2'); // Your secret API key\n\nconst messaging = new sdk.Messaging(client);\n\nconst message = await messaging.createSms(\n '<MESSAGE_ID>', // messageId\n '<CONTENT>', // content\n [], // topics (optional)\n [], // users (optional)\n [], // targets (optional)\n false, // draft (optional)\n '2025-02-13T22:01:00+0000' // scheduledAt (optional)\n);\n```\n```deno\nimport * as sdk from \"npm:node-appwrite\";\n\nconst client = new sdk.Client()\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2'); // Your secret API key\n\nconst messaging = new sdk.Messaging(client);\n\nconst message = await messaging.createSms(\n '<MESSAGE_ID>', // messageId\n '<CONTENT>', // content\n [], // topics (optional)\n [], // users (optional)\n [], // targets (optional)\n false, // draft (optional)\n '2025-02-13T22:01:00+0000' // scheduledAt (optional)\n);\n```\n```php\n<?php\n\nuse Appwrite\\Client;\nuse Appwrite\\Services\\Messaging;\n\n$client = new Client();\n\n$client\n ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n ->setProject('<PROJECT_ID>') // Your project ID\n ->setKey('919c2d18fb5d4...a2ae413da83346ad2'); // Your secret API key\n\n$messaging = new Messaging($client);\n\n$result = $messaging->createSms(\n messageId: '<MESSAGE_ID>',\n content: '<CONTENT>',\n topics: [], // optional\n users: [], // optional\n targets: [], // optional\n draft: false, // optional\n scheduledAt: '2025-02-13T22:01:00+0000' // optional\n);\n```\n```python\nfrom appwrite.client import Client\nfrom appwrite.services.messaging import Messaging\n\nclient = Client()\nclient.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\nclient.set_project('<PROJECT_ID>') # Your project ID\nclient.set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key\n\nmessaging = Messaging(client)\n\nresult = messaging.create_sms(\n message_id = '<MESSAGE_ID>',\n content = '<CONTENT>',\n topics = [], # optional\n users = [], # optional\n targets = [], # optional\n draft = false, # optional\n scheduled_at = '2025-02-13T22:01:00+0000' # optional\n)\n```\n```ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('<PROJECT_ID>') # Your project ID\n .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key\n\nmessaging = Messaging.new(client)\n\nresponse = messaging.create_sms(\n message_id: '<MESSAGE_ID>',\n content: '<CONTENT>',\n topics: [], # optional\n users: [], # optional\n targets: [], # optional\n draft: false, # optional\n scheduled_at: '2025-02-13T22:01:00+0000' # optional\n)\n```\n```csharp\nusing Appwrite;\nusing Appwrite.Services;\nusing Appwrite.Models;\n\nClient client = new Client()\n .SetEndPoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .SetProject(\"<PROJECT_ID>\") // Your project ID\n .SetKey(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\nMessaging messaging = new Messaging(client);\n\nMessage message = await messaging.CreateSMS(\n messageId: \"<MESSAGE_ID>\",\n content: \"<CONTENT>\"\n topics: new List<string> {} // optional\n users: new List<string> {} // optional\n targets: new List<string> {} // optional\n draft: false, // optional\n scheduledAt: \"2025-02-13T22:01:00+0000\"); // optional\n```\n```dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\nimport 'package:dart_appwrite/enums.dart';\nimport 'package:dart_appwrite/models.dart';\n\nClient client = Client();\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2'); // Your secret API key\n\nMessaging messaging = Messaging(client);\n\nMessage message result = await messaging.createSms(\n messageId: '<MESSAGE_ID>',\n content: '<CONTENT>',\n topics: [], // optional\n users: [], // optional\n targets: [], // optional\n draft: false, // optional\n scheduledAt: '2025-02-13T22:01:00+0000' // optional\n);\n```\n```kotlin\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Messaging;\n\nval client = Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\") // Your secret API key\n\nval messaging = Messaging(client)\n\nval message - await messaging.createSms(\n messageId = \"<MESSAGE_ID>\",\n content = \"<CONTENT>\",\n topics = listOf(),\n users = listOf(),\n targets = listOf(),\n draft = false,\n scheduledAt = \"2025-02-13T22:01:00+0000\"\n)\n```\n```java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Messaging;\n\nClient client = new Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\nMessaging messaging = new Messaging(client);\n\nmessaging.createSms(\n \"<MESSAGE_ID>\", // messageId\n \"<CONTENT>\", // content\n listOf(), // topics (optional)\n listOf(), // users (optional)\n listOf(), // targets (optional)\n false, // draft (optional)\n \"2025-02-13T22:01:00+0000\", // scheduledAt (optional)\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(result);\n })\n);\n```\n```swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\") // Your secret API key\n\nlet messaging = Messaging(client)\n\nlet message = try await messaging.createSms(\n messageId: \"<MESSAGE_ID>\",\n content: \"<CONTENT>\",\n topics: [], // optional\n users: [], // optional\n targets: [], // optional\n draft: false, // optional\n scheduledAt: \"2025-02-13T22:01:00+0000\" // optional\n)\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::messaging::Messaging;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n let client = Client::new()\n .set_endpoint(\"https://<REGION>.cloud.appwrite.io/v1\")\n .set_project(\"<PROJECT_ID>\")\n .set_key(\"919c2d18fb5d4...a2ae413da83346ad2\");\n\n let messaging = Messaging::new(&client);\n\n let message = messaging.create_sms(\n \"<MESSAGE_ID>\",\n \"<CONTENT>\",\n Some(vec![]), // topics (optional)\n Some(vec![]), // users (optional)\n Some(vec![]), // targets (optional)\n Some(false), // draft (optional)\n Some(\"2025-02-13T22:01:00+0000\"), // scheduled_at (optional)\n ).await?;\n\n println!(\"{:?}\", message);\n Ok(())\n}\n```\n{% /multicode %}"}, {"path": "docs/products/messaging/sendgrid", "title": "SendGrid", "description": "Send emails to your Appwrite users using SendGrid and Appwrite Messaging.", "content": "SendGrid lets you send customized email messages to your users.\nThese emails can be sent immediately or scheduled.\nYou can send emails for purposes like reminders, promotions, announcements, and even custom authentication flows.\n\n{% section #add-provider step=1 title=\"Add provider\" %}\n\nTo add SendGrid as a provider, navigate to **Messaging** > **Providers** > {% icon icon=\"plus\" size=\"m\" /%} **Add provider** > **Email**.\n{% only_dark %}\n![Add a SMTP provider](/images/docs/messaging/providers/sendgrid/dark/add-sendgrid.avif)\n{% /only_dark %}\n{% only_light %}\n![Add a SMTP provider](/images/docs/messaging/providers/sendgrid/add-sendgrid.avif)\n{% /only_light %}\n\nGive your provider a name > choose **SendGrid** > click **Save and continue**.\nThe provider will be saved to your project, but not enabled until you complete its configuration.\n{% /section %}\n{% section #configure-provider step=2 title=\"Configure provider\" %}\n\nIn the **Configure** step, you will need to provide details from your SendGrid dashboard to connect your Appwrite project.\n\n{% only_dark %}\n![Configure SMTP provider](/images/docs/messaging/providers/sendgrid/dark/configure-sendgrid.avif)\n{% /only_dark %}\n{% only_light %}\n![Configure SMTP provider](/images/docs/messaging/providers/sendgrid/configure-sendgrid.avif)\n{% /only_light %}\nYou will need to provide the following information from your **SendGrid dashboard**.\n\n{% table %}\n* Field name\n*\n---\n* API key\n* Head to Settings -> API Keys -> Create API Key.\n---\n* Sender email\n* 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).\n---\n* Sender name\n* The sender name that appears in the emails sent from this provider.\n---\n* Reply-to email\n* 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.\n---\n* Reply-to name\n* The reply-to name that appears in the emails sent from this provider.\n{% /table %}\n\nAfter adding the following details, click **Save and continue** to enable the provider.\n{% /section %}\n\n{% section #test-provider step=3 title=\"Test provider\" %}\nBefore sending your first message,\nmake sure you've configured [a topic](/docs/products/messaging/topics) and [a target](/docs/products/messaging/targets) to send messages to.\n{% tabs %}\n{% tabsitem #console title=\"Console\" %}\nTo send a test message, navigate to **Messaging** > **Messages** > {% icon icon=\"plus\" size=\"m\" /%} **Create message** > **Email**.\n{% only_dark %}\n![Create email message](/images/docs/messaging/messages/dark/create-email-message.avif)\n{% /only_dark %}\n{% only_light %}\n![Create email message](/images/docs/messaging/messages/create-email-message.avif)\n{% /only_light %}\n\nAdd your message and in the targets step, select one of your test targets. Set the schedule to **Now** and click **Send**.\n\nVerify that you can receive the message in your inbox. If not, check for logs in the Appwrite Console or in your provider's logs.\n{% /tabsitem %}\n\n{% tabsitem #server-sdk title=\"Server SDK\" %}\nTo send a message programmatically, use an [Appwrite Server SDK](/docs/sdks#server).\n{% multicode %}\n```server-nodejs\nconst sdk = require('node-appwrite');\n\n// Init SDK\nconst client = new sdk.Client();\n\nconst messaging = new sdk.Messaging(client);\n\nclient\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('<API_KEY>') // Your secret API key\n;\n\nconst message = await messaging.createEmail({\n messageId: '<MESSAGE_ID>',\n subject: '<SUBJECT>',\n content: '<CONTENT>'\n});\n```\n```deno\nimport * as sdk from \"npm:node-appwrite\";\n\n// Init SDK\nlet client = new sdk.Client();\n\nlet messaging = new sdk.Messaging(client);\n\nclient\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('<API_KEY>') // Your secret API key\n;\n\n\nconst message = await messaging.createEmail({\n messageId: '<MESSAGE_ID>',\n subject: '<SUBJECT>',\n content: '<CONTENT>'\n});\n```\n```php\n<?php\n\nuse Appwrite\\Client;\nuse Appwrite\\Services\\Messaging;\n\n$client = new Client();\n\n$client\n ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n ->setProject('<PROJECT_ID>') // Your project ID\n ->setKey('<API_KEY>') // Your secret API key\n;\n\n$messaging = new Messaging($client);\n\n$result = $messaging->createEmail('<MESSAGE_ID>', '<SUBJECT>', '<CONTENT>');\n```\n```python\nfrom appwrite.client import Client\n\nclient = Client()\n\n(client\n .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('<PROJECT_ID>') # Your project ID\n .set_key('<API_KEY>') # Your secret API key\n)\n\nmessaging = Messaging(client)\n\nresult = messaging.create_email('<MESSAGE_ID>', '<SUBJECT>', '<CONTENT>')\n```\n```ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('<PROJECT_ID>') # Your project ID\n .set_key('<API_KEY>') # Your secret API key\n\nmessaging = Messaging.new(client)\n\nresponse = messaging.create_email(message_id: '<MESSAGE_ID>', subject: '<SUBJECT>', content: '<CONTENT>')\n\nputs response.inspect\n```\n```csharp\nusing Appwrite;\nusing Appwrite.Services;\nusing Appwrite.Models;\nusing Appwrite.Enums;\nusing Appwrite.Enums;\nusing Appwrite.Enums;\n\nvar client = new Client()\n .SetEndPoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .SetProject(\"<PROJECT_ID>\") // Your project ID\n .SetKey(\"<API_KEY>\"); // Your secret API key\n\nvar messaging = new Messaging(client);\n\nMessage result = await messaging.CreateEmail(\n messageId: \"<MESSAGE_ID>\",\n subject: \"<SUBJECT>\",\n content: \"<CONTENT>\");\n```\n```dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\nimport 'package:dart_appwrite/enums.dart';\nimport 'package:dart_appwrite/models.dart';\n\nvoid main() async { // Init SDK\n Client client = Client();\n Messaging messaging = Messaging(client);\n\n client\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('<API_KEY>') // Your secret API key\n ;\n\n Future result = await messaging.createEmail(\n messageId:'<MESSAGE_ID>' ,\n subject:'<SUBJECT>' ,\n content:'<CONTENT>' ,\n );\n\n result\n .then((response) {\n print(response);\n }).catchError((error) {\n print(error.response);\n });\n}\n```\n```kotlin\nimport io.appwrite.Client\nimport io.appwrite.coroutines.CoroutineCallback\nimport io.appwrite.services.Messaging\n\nval client = Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"<API_KEY>\") // Your secret API key\n\nval messaging = Messaging(client)\n\nval response = messaging.createEmail(\n messageId = \"<MESSAGE_ID>\",\n subject = \"<SUBJECT>\",\n content = \"<CONTENT>\",\n)\n```\n```java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Messaging;\n\nClient client = new Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"<API_KEY>\"); // Your secret API key\n\nMessaging messaging = new Messaging(client);\n\nmessaging.createEmail(\n \"<MESSAGE_ID>\",\n \"<SUBJECT>\",\n \"<CONTENT>\",\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(result);\n })\n);\n```\n```swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"<API_KEY>\") // Your secret API key\n\nlet messaging = Messaging(client)\n\nlet message = try await messaging.createEmail(\n messageId: \"<MESSAGE_ID>\",\n subject: \"<SUBJECT>\",\n content: \"<CONTENT>\"\n)\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::messaging::Messaging;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n let client = Client::new()\n .set_endpoint(\"https://<REGION>.cloud.appwrite.io/v1\")\n .set_project(\"<PROJECT_ID>\")\n .set_key(\"<API_KEY>\");\n\n let messaging = Messaging::new(&client);\n\n let message = messaging.create_email(\n \"<MESSAGE_ID>\", // messageId\n \"<SUBJECT>\", // subject\n \"<CONTENT>\", // content\n None, // topics (optional)\n None, // users (optional)\n None, // targets (optional)\n None, // cc (optional)\n None, // bcc (optional)\n None, // attachments (optional)\n None, // draft (optional)\n None, // html (optional)\n None, // scheduledAt (optional)\n ).await?;\n\n println!(\"{:?}\", message);\n Ok(())\n}\n```\n{% /multicode %}\n{% /tabsitem %}\n{% /tabs %}\n\nYou can follow the [Send email messages](/docs/products/messaging/send-email-messages) journey to send your first push notification and test your provider.\n{% /section %}\n\n{% section #manage-provider step=4 title=\"Manage provider\" %}\n{% tabs %}\n{% tabsitem #console title=\"Console\" %}\nYou can update or delete a provider in the Appwrite Console.\n\nNavigate to **Messaging** > **Providers** > click your provider.\nIn the settings, you can update a provider's configuration or delete the provider.\n{% /tabsitem %}\n\n{% tabsitem #server-sdk title=\"Server SDK\" %}\nTo update or delete providers programmatically, use an [Appwrite Server SDK](/docs/sdks#server).\n{% multicode %}\n```server-nodejs\nconst sdk = require('node-appwrite');\n\n// Init SDK\nconst client = new sdk.Client();\n\nconst messaging = new sdk.Messaging(client);\n\nclient\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('<YOUR_API_KEY>') // Your secret API key\n;\n\nconst provider = await messaging.updateSendgridProvider(\n '<PROVIDER_ID>', // providerId\n '<NAME>', // name (optional)\n '<API_KEY>', // apiKey (optional)\n false, // enabled (optional)\n '<FROM_NAME>', // fromName (optional)\n 'email@example.com', // fromEmail (optional)\n '<REPLY_TO_NAME>', // replyToName (optional)\n '<REPLY_TO_EMAIL>' // replyToEmail (optional)\n );\n```\n```deno\nimport * as sdk from \"npm:node-appwrite\";\n\n// Init SDK\nlet client = new sdk.Client();\n\nlet messaging = new sdk.Messaging(client);\n\nclient\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('<YOUR_API_KEY>') // Your secret API key\n;\n\nconst provider = await messaging.updateSendgridProvider(\n '<PROVIDER_ID>', // providerId\n '<NAME>', // name (optional)\n '<API_KEY>', // apiKey (optional)\n false, // enabled (optional)\n '<FROM_NAME>', // fromName (optional)\n 'email@example.com', // fromEmail (optional)\n '<REPLY_TO_NAME>', // replyToName (optional)\n '<REPLY_TO_EMAIL>' // replyToEmail (optional)\n );\n```\n```php\n<?php\n\nuse Appwrite\\Client;\nuse Appwrite\\Services\\Messaging;\n\n$client = new Client();\n\n$client\n ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n ->setProject('<PROJECT_ID>') // Your project ID\n ->setKey('<YOUR_API_KEY>') // Your secret API key\n;\n\n$messaging = new Messaging($client);\n\n$result = $messaging->updateSendgridProvider(\n providerId: '<PROVIDER_ID>',\n name: '<NAME>', // optional\n apiKey: '<API_KEY>', // optional\n enabled: false, // optional\n fromName: '<FROM_NAME>', // optional\n fromEmail: 'email@example.com', // optional\n replyToName: '<REPLY_TO_NAME>', // optional\n replyToEmail: '<REPLY_TO_EMAIL>' // optional\n);\n```\n```python\nfrom appwrite.client import Client\nfrom appwrite.services.messaging import Messaging\n\nclient = Client()\n\n(client\n .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('<PROJECT_ID>') # Your project ID\n .set_key('<YOUR_API_KEY>') # Your secret API key\n)\n\nmessaging = Messaging(client)\n\nresult = messaging.update_sendgrid_provider(\n provider_id = '<PROVIDER_ID>',\n name = '<NAME>', # optional\n api_key = '<API_KEY>', # optional\n enabled = False, # optional\n from_name = '<FROM_NAME>', # optional\n from_email = 'email@example.com', # optional\n reply_to_name = '<REPLY_TO_NAME>', # optional\n reply_to_email = '<REPLY_TO_EMAIL>' # optional\n)\n```\n```ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('<PROJECT_ID>') # Your project ID\n .set_key('<YOUR_API_KEY>') # Your secret API key\n\nmessaging = Messaging.new(client)\n\nresponse = messaging.update_sendgrid_provider(\n provider_id: '<PROVIDER_ID>',\n name: '<NAME>', # optional\n api_key: '<API_KEY>', # optional\n enabled: false, # optional\n from_name: '<FROM_NAME>', # optional\n from_email: 'email@example.com', # optional\n reply_to_name: '<REPLY_TO_NAME>', # optional\n reply_to_email: '<REPLY_TO_EMAIL>' # optional\n)\n\nputs response.inspect\n```\n```csharp\nusing Appwrite;\nusing Appwrite.Services;\nusing Appwrite.Models;\n\nvar client = new Client()\n .SetEndPoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .SetProject(\"<PROJECT_ID>\") // Your project ID\n .SetKey(\"<YOUR_API_KEY>\"); // Your secret API key\n\nvar messaging = new Messaging(client);\n\nProvider result = await messaging.UpdateSendgridProvider(\n providerId: \"<PROVIDER_ID>\"\n name: \"<NAME>\" // optional\n apiKey: \"<API_KEY>\" // optional\n enabled: false // optional\n fromName: \"<FROM_NAME>\" // optional\n fromEmail: \"email@example.com\" // optional\n replyToName: \"<REPLY_TO_NAME>\" // optional\n replyToEmail: \"<REPLY_TO_EMAIL>\"); // optional\n```\n```dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\nimport 'package:dart_appwrite/enums.dart';\nimport 'package:dart_appwrite/models.dart';\n\nvoid main() { // Init SDK\n Client client = Client();\n Messaging messaging = Messaging(client);\n\n client\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('<YOUR_API_KEY>') // Your secret API key\n ;\n\n Future result = messaging.updateSendgridProvider(\n providerId: '<PROVIDER_ID>',\n name: '<NAME>', // optional\n apiKey: '<API_KEY>', // optional\n enabled: false, // optional\n fromName: '<FROM_NAME>', // optional\n fromEmail: 'email@example.com', // optional\n replyToName: '<REPLY_TO_NAME>', // optional\n replyToEmail: '<REPLY_TO_EMAIL>', // optional\n );\n\n result\n .then((response) {\n print(response);\n }).catchError((error) {\n print(error.response);\n });\n}\n```\n```kotlin\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Messaging;\n\nClient client = new Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"<YOUR_API_KEY>\"); // Your secret API key\n\nMessaging messaging = new Messaging(client);\n\nmessaging.updateSendgridProvider(\n \"<PROVIDER_ID>\", // providerId\n \"<NAME>\", // name (optional)\n \"<API_KEY>\", // apiKey (optional)\n false, // enabled (optional)\n \"<FROM_NAME>\", // fromName (optional)\n \"email@example.com\", // fromEmail (optional)\n \"<REPLY_TO_NAME>\", // replyToName (optional)\n \"<REPLY_TO_EMAIL>\" // replyToEmail (optional)\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(result);\n })\n);\n```\n```java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Messaging;\n\nClient client = new Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"<YOUR_API_KEY>\"); // Your secret API key\n\nMessaging messaging = new Messaging(client);\n\nmessaging.updateSendgridProvider(\n \"<PROVIDER_ID>\", // providerId\n \"<NAME>\", // name (optional)\n \"<API_KEY>\", // apiKey (optional)\n false, // enabled (optional)\n \"<FROM_NAME>\", // fromName (optional)\n \"email@example.com\", // fromEmail (optional)\n \"<REPLY_TO_NAME>\", // replyToName (optional)\n \"<REPLY_TO_EMAIL>\" // replyToEmail (optional)\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(result);\n })\n);\n```\n```swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"<YOUR_API_KEY>\") // Your secret API key\n\nlet messaging = Messaging(client)\n\nlet provider = try await messaging.updateSendgridProvider(\n providerId: \"<PROVIDER_ID>\",\n name: \"<NAME>\", // optional\n apiKey: \"<API_KEY>\", // optional\n enabled: xfalse, // optional\n fromName: \"<FROM_NAME>\", // optional\n fromEmail: \"email@example.com\", // optional\n replyToName: \"<REPLY_TO_NAME>\", // optional\n replyToEmail: \"<REPLY_TO_EMAIL>\" // optional\n)\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::messaging::Messaging;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n let client = Client::new()\n .set_endpoint(\"https://<REGION>.cloud.appwrite.io/v1\")\n .set_project(\"<PROJECT_ID>\")\n .set_key(\"<YOUR_API_KEY>\");\n\n let messaging = Messaging::new(&client);\n\n let provider = messaging.update_sendgrid_provider(\n \"<PROVIDER_ID>\", // providerId\n Some(\"<NAME>\"), // name (optional)\n Some(false), // enabled (optional)\n Some(\"<API_KEY>\"), // apiKey (optional)\n Some(\"<FROM_NAME>\"), // fromName (optional)\n Some(\"email@example.com\"), // fromEmail (optional)\n Some(\"<REPLY_TO_NAME>\"), // replyToName (optional)\n Some(\"<REPLY_TO_EMAIL>\"), // replyToEmail (optional)\n ).await?;\n\n println!(\"{:?}\", provider);\n Ok(())\n}\n```\n{% /multicode %}\n{% /tabsitem %}\n{% /tabs %}\n{% /section %}"}, {"path": "docs/products/messaging/smtp", "title": "SMTP", "description": "Send emails to your Appwrite users using SMTP and Appwrite Messaging.", "content": "If you wish to use a third-party SMTP provider that Appwrite doesn't yet support or host your own SMTP\nserver, you can setup a custom SMTP provider for your project.\n\n{% section #add-provider step=1 title=\"Add provider\" %}\nTo add a custom SMTP server as a provider, navigate to **Messaging** > **Providers** > {% icon icon=\"plus\" size=\"m\" /%} **Add provider** > **Email**.\n{% only_dark %}\n![Add a SMTP provider](/images/docs/messaging/providers/smtp/dark/add-smtp.avif)\n{% /only_dark %}\n{% only_light %}\n![Add a SMTP provider](/images/docs/messaging/providers/smtp/add-smtp.avif)\n{% /only_light %}\n\nGive your provider a name > choose **SMTP** > click **Save and continue**.\nThe provider will be saved to your project, but not enabled until you complete its configuration.\n{% /section %}\n{% section #configure-provider step=2 title=\"Configure provider\" %}\n\nIn the **Configure** step, you will need to provide details from your SMTP dashboard to connect your Appwrite project.\n\nYou will need to provide the following information from your **SMTP dashboard**.\n\n{% table %}\n* Field name\n* Description\n---\n* Host\n* The server address of the SMTP provider.\n---\n* Port\n* The port used for SMTP connections.\n---\n* Username\n* Your SMTP provider account username.\n---\n* Password\n* Your SMTP provider account password.\n---\n* Encryption\n* The type of encryption used. One of SSL or TLS.\n---\n* Auto TLS\n* Automatically uses TLS encryption if available.\n---\n* Mailer\n* The SMTP server or provider.\n---\n* Sender email\n* The provider sends emails from this sender email. The sender email needs to be an email under the configured domain.\n---\n* Sender name\n* The sender name that appears in the emails sent from this provider.\n---\n* Reply-to email\n* 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.\n---\n* Reply-to name\n* The reply-to name that appears in the emails sent from this provider.\n{% /table %}\n\nAfter adding the following details, click **Save and continue** to enable the provider.\n{% /section %}\n\n{% section #test-provider step=3 title=\"Test provider\" %}\nBefore sending your first message,\nmake sure you've configured [a topic](/docs/products/messaging/topics) and [a target](/docs/products/messaging/targets) to send messages to.\n{% tabs %}\n{% tabsitem #console title=\"Console\" %}\nTo send a test message, navigate to **Messaging** > **Messages** > {% icon icon=\"plus\" size=\"m\" /%} **Create message** > **Email**.\n{% only_dark %}\n![Create email message](/images/docs/messaging/messages/dark/create-email-message.avif)\n{% /only_dark %}\n{% only_light %}\n![Create email message](/images/docs/messaging/messages/create-email-message.avif)\n{% /only_light %}\n\nAdd your message and in the targets step, select one of your test targets. Set the schedule to **Now** and click **Send**.\n\nVerify that you can receive the message in your inbox. If not, check for logs in the Appwrite Console or in your provider's logs.\n{% /tabsitem %}\n\n{% tabsitem #server-sdk title=\"Server SDK\" %}\nTo send a message programmatically, use an [Appwrite Server SDK](/docs/sdks#server).\n{% multicode %}\n```server-nodejs\nconst sdk = require('node-appwrite');\n\n// Init SDK\nconst client = new sdk.Client();\n\nconst messaging = new sdk.Messaging(client);\n\nclient\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('<API_KEY>') // Your secret API key\n;\n\nconst message = await messaging.createEmail({\n messageId: '<MESSAGE_ID>',\n subject: '<SUBJECT>',\n content: '<CONTENT>'\n});\n```\n```deno\nimport * as sdk from \"npm:node-appwrite\";\n\n// Init SDK\nlet client = new sdk.Client();\n\nlet messaging = new sdk.Messaging(client);\n\nclient\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('<API_KEY>') // Your secret API key\n;\n\nconst message = await messaging.createEmail({\n messageId: '<MESSAGE_ID>',\n subject: '<SUBJECT>',\n content: '<CONTENT>'\n});\n```\n```php\n<?php\n\nuse Appwrite\\Client;\nuse Appwrite\\Services\\Messaging;\n\n$client = new Client();\n\n$client\n ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n ->setProject('<PROJECT_ID>') // Your project ID\n ->setKey('<API_KEY>') // Your secret API key\n;\n\n$messaging = new Messaging($client);\n\n$result = $messaging->createEmail('<MESSAGE_ID>', '<SUBJECT>', '<CONTENT>');\n```\n```python\nfrom appwrite.client import Client\n\nclient = Client()\n\n(client\n .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('<PROJECT_ID>') # Your project ID\n .set_key('<API_KEY>') # Your secret API key\n)\n\nmessaging = Messaging(client)\n\nresult = messaging.create_email('<MESSAGE_ID>', '<SUBJECT>', '<CONTENT>')\n```\n```ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('<PROJECT_ID>') # Your project ID\n .set_key('<API_KEY>') # Your secret API key\n\nmessaging = Messaging.new(client)\n\nresponse = messaging.create_email(message_id: '<MESSAGE_ID>', subject: '<SUBJECT>', content: '<CONTENT>')\n\nputs response.inspect\n```\n```csharp\nusing Appwrite;\nusing Appwrite.Services;\nusing Appwrite.Models;\nusing Appwrite.Enums;\nusing Appwrite.Enums;\nusing Appwrite.Enums;\n\nvar client = new Client()\n .SetEndPoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .SetProject(\"<PROJECT_ID>\") // Your project ID\n .SetKey(\"<API_KEY>\"); // Your secret API key\n\nvar messaging = new Messaging(client);\n\nMessage result = await messaging.CreateEmail(\n messageId: \"<MESSAGE_ID>\",\n subject: \"<SUBJECT>\",\n content: \"<CONTENT>\");\n```\n```dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\nimport 'package:dart_appwrite/enums.dart';\nimport 'package:dart_appwrite/models.dart';\n\nvoid main() async { // Init SDK\n Client client = Client();\n Messaging messaging = Messaging(client);\n\n client\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('<API_KEY>') // Your secret API key\n ;\n\n Future result = await messaging.createEmail(\n messageId:'<MESSAGE_ID>' ,\n subject:'<SUBJECT>' ,\n content:'<CONTENT>' ,\n );\n\n result\n .then((response) {\n print(response);\n }).catchError((error) {\n print(error.response);\n });\n}\n```\n```kotlin\nimport io.appwrite.Client\nimport io.appwrite.coroutines.CoroutineCallback\nimport io.appwrite.services.Messaging\n\nval client = Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"<API_KEY>\") // Your secret API key\n\nval messaging = Messaging(client)\n\nval response = messaging.createEmail(\n messageId = \"<MESSAGE_ID>\",\n subject = \"<SUBJECT>\",\n content = \"<CONTENT>\",\n)\n```\n```java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Messaging;\n\nClient client = new Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"<API_KEY>\"); // Your secret API key\n\nMessaging messaging = new Messaging(client);\n\nmessaging.createEmail(\n \"<MESSAGE_ID>\",\n \"<SUBJECT>\",\n \"<CONTENT>\",\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(result);\n })\n);\n```\n```swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"<API_KEY>\") // Your secret API key\n\nlet messaging = Messaging(client)\n\nlet message = try await messaging.createEmail(\n messageId: \"<MESSAGE_ID>\",\n subject: \"<SUBJECT>\",\n content: \"<CONTENT>\"\n)\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::messaging::Messaging;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n let client = Client::new()\n .set_endpoint(\"https://<REGION>.cloud.appwrite.io/v1\")\n .set_project(\"<PROJECT_ID>\")\n .set_key(\"<API_KEY>\");\n\n let messaging = Messaging::new(&client);\n\n let message = messaging.create_email(\n \"<MESSAGE_ID>\", // messageId\n \"<SUBJECT>\", // subject\n \"<CONTENT>\", // content\n None, // topics (optional)\n None, // users (optional)\n None, // targets (optional)\n None, // cc (optional)\n None, // bcc (optional)\n None, // attachments (optional)\n None, // draft (optional)\n None, // html (optional)\n None, // scheduledAt (optional)\n ).await?;\n\n println!(\"{:?}\", message);\n Ok(())\n}\n```\n{% /multicode %}\n{% /tabsitem %}\n{% /tabs %}\n\nYou can follow the [Send email messages](/docs/products/messaging/send-push-notifications) journey to send your first push notification and test your provider.\n{% /section %}\n\n{% section #manage-provider step=4 title=\"Manage provider\" %}\n{% tabs %}\n{% tabsitem #console title=\"Console\" %}\nYou can update or delete a provider in the Appwrite Console.\n\nNavigate to **Messaging** > **Providers** > click your provider.\nIn the settings, you can update a provider's configuration or delete the provider.\n{% /tabsitem %}\n\n{% tabsitem #server-sdk title=\"Server SDK\" %}\nTo update or delete providers programmatically, use an [Appwrite Server SDK](/docs/sdks#server).\n{% multicode %}\n```server-nodejs\nconst sdk = require('node-appwrite');\n\n// Init SDK\nconst client = new sdk.Client();\n\nconst messaging = new sdk.Messaging(client);\n\nclient\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('<API_KEY>') // Your secret API key\n;\n\n// update provider\nmessaging.updateSendgridProvider(\n '<PROVIDER_ID>',\n '<PROVIDER_NAME>',\n '<API_KEY>',\n '<DOMAIN>',\n '<IS_EU_REGION?>',\n '<SENDER_NAME>',\n '<SENDER_EMAIL>',\n '<REPLY_TO_NAME>',\n '<REPLY_TO_EMAIL>',\n '<ENABLED?>',\n).then(function (response) {\n console.log(response);\n}, function (error) {\n console.log(error);\n});\n\n// delete provider\nmessaging.deleteProvider('<PROVIDER_ID>')\n.then(function (response) {\n console.log(response);\n}, function (error) {\n console.log(error);\n});\n```\n```deno\nimport * as sdk from \"npm:node-appwrite\";\n\n// Init SDK\nlet client = new sdk.Client();\n\nlet messaging = new sdk.Messaging(client);\n\n// update provider\nmessaging.updateSendgridProvider(\n '<PROVIDER_ID>',\n '<PROVIDER_NAME>',\n '<API_KEY>',\n '<DOMAIN>',\n '<IS_EU_REGION?>',\n '<SENDER_NAME>',\n '<SENDER_EMAIL>',\n '<REPLY_TO_NAME>',\n '<REPLY_TO_EMAIL>',\n '<ENABLED?>',\n).then(function (response) {\n console.log(response);\n}, function (error) {\n console.log(error);\n});\n\n// delete provider\nmessaging.deleteProvider('<PROVIDER_ID>')\n.then(function (response) {\n console.log(response);\n}, function (error) {\n console.log(error);\n});\n```\n```php\n<?php\n\nuse Appwrite\\Client;\nuse Appwrite\\Services\\Messaging;\n\n$client = new Client();\n\n$client\n ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n ->setProject('<PROJECT_ID>') // Your project ID\n ->setKey('<API_KEY>') // Your secret API key\n;\n\n$messaging = new Messaging($client);\n\n$result = $messaging->updateSendgridProvider(\n '<PROVIDER_ID>',\n '<PROVIDER_NAME>',\n '<API_KEY>',\n '<DOMAIN>',\n '<IS_EU_REGION?>',\n '<SENDER_NAME>',\n '<SENDER_EMAIL>',\n '<REPLY_TO_NAME>',\n '<REPLY_TO_EMAIL>',\n '<ENABLED?>',\n);\n```\n```python\nfrom appwrite.client import Client\n\nclient = Client()\n\n(client\n .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('<PROJECT_ID>') # Your project ID\n .set_key('<API_KEY>') # Your secret API key\n)\n\nmessaging = Messaging(client)\n\nresult = messaging.update_sendgrid_provider(\n '<PROVIDER_ID>',\n '<PROVIDER_NAME>',\n '<API_KEY>',\n '<DOMAIN>',\n '<IS_EU_REGION?>',\n '<SENDER_NAME>',\n '<SENDER_EMAIL>',\n '<REPLY_TO_NAME>',\n '<REPLY_TO_EMAIL>',\n '<ENABLED?>',\n)\n```\n```ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('<PROJECT_ID>') # Your project ID\n .set_key('<API_KEY>') # Your secret API key\n\nmessaging = Messaging.new(client)\n\nresponse = messaging.update_sendgrid_provider(\n provider_id: \"<PROVIDER_ID>\",\n name: \"<PROVIDER_NAME>\",\n api_key: \"<API_KEY>\",\n domain: \"<DOMAIN>\",\n isEuRegion: \"<IS_EU_REGION?>\",\n from_name: \"<SENDER_NAME>\",\n from_email: \"<SENDER_EMAIL>\",\n reply_to_name: \"<REPLY_TO_NAME>\",\n reply_to_email: \"<REPLY_TO_EMAIL>\",\n enabled: \"<ENABLED?>\",\n)\n\nputs response.inspect\n```\n```csharp\nusing Appwrite;\nusing Appwrite.Services;\nusing Appwrite.Models;\nusing Appwrite.Enums;\n\nvar client = new Client()\n .SetEndPoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .SetProject(\"<PROJECT_ID>\") // Your project ID\n .SetKey(\"<API_KEY>\"); // Your secret API key\n\nvar messaging = new Messaging(client);\n\nProvider result = await messaging.UpdateSendgridProvider(\n providerId: \"<PROVIDER_ID>\",\n name: \"<PROVIDER_NAME>\",\n apiKey: \"<API_KEY>\",\n domain: \"<DOMAIN>\",\n isEuRegion: \"<IS_EU_REGION?>\",\n fromName: \"<SENDER_NAME>\",\n fromEmail: \"<SENDER_EMAIL>\",\n replyToName: \"<REPLY_TO_NAME>\",\n replyToEmail: \"<REPLY_TO_EMAIL>\",\n enabled: \"<ENABLED?>\",\n);\n```\n```dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\nimport 'package:dart_appwrite/enums.dart';\nimport 'package:dart_appwrite/models.dart';\n\nvoid main() { // Init SDK\n Client client = Client();\n Messaging messaging = Messaging(client);\n\n client\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('<API_KEY>') // Your secret API key\n ;\n\n Future result = messaging.updateSendgridProvider(\n providerId: \"<PROVIDER_ID>\",\n name: \"<PROVIDER_NAME>\",\n apiKey: \"<API_KEY>\",\n domain: \"<DOMAIN>\",\n isEuRegion: \"<IS_EU_REGION?>\",\n fromName: \"<SENDER_NAME>\",\n fromEmail: \"<SENDER_EMAIL>\",\n replyToName: \"<REPLY_TO_NAME>\",\n replyToEmail: \"<REPLY_TO_EMAIL>\",\n enabled: \"<ENABLED?>\",\n );\n\n result\n .then((response) {\n print(response);\n }).catchError((error) {\n print(error.response);\n });\n}\n```\n```kotlin\nimport io.appwrite.Client\nimport io.appwrite.coroutines.CoroutineCallback\nimport io.appwrite.services.Messaging\n\nval client = Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"<API_KEY>\") // Your secret API key\n\nval messaging = Messaging(client)\n\nval response = messaging.updateSendgridProvider(\n providerId = \"<PROVIDER_ID>\",\n name = \"<PROVIDER_NAME>\",\n apiKey = \"<API_KEY>\",\n domain = \"<DOMAIN>\",\n isEuRegion = \"<IS_EU_REGION?>\",\n fromName = \"<SENDER_NAME>\",\n fromEmail = \"<SENDER_EMAIL>\",\n replyToName = \"<REPLY_TO_NAME>\",\n replyToEmail = \"<REPLY_TO_EMAIL>\",\n enabled = \"<ENABLED?>\",\n)\n\n```\n```java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Messaging;\n\nClient client = new Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"<API_KEY>\"); // Your secret API key\n\nMessaging messaging = new Messaging(client);\n\nmessaging.updateSendgridProvider(\n \"<PROVIDER_ID>\",\n \"<PROVIDER_NAME>\",\n \"<API_KEY>\",\n \"<DOMAIN>\",\n \"<IS_EU_REGION?>\",\n \"<SENDER_NAME>\",\n \"<SENDER_EMAIL>\",\n \"<REPLY_TO_NAME>\",\n \"<REPLY_TO_EMAIL>\",\n \"<ENABLED?>\",\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(result);\n })\n);\n```\n```swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"<API_KEY>\") // Your secret API key\n\nlet messaging = Messaging(client)\n\nlet provider = try await messaging.updateSendgridProvider(\n providerId: \"<PROVIDER_ID>\",\n name: \"<PROVIDER_NAME>\",\n apiKey: \"<API_KEY>\",\n domain: \"<DOMAIN>\",\n isEuRegion: \"<IS_EU_REGION?>\",\n fromName: \"<SENDER_NAME>\",\n fromEmail: \"<SENDER_EMAIL>\",\n replyToName: \"<REPLY_TO_NAME>\",\n replyToEmail: \"<REPLY_TO_EMAIL>\",\n enabled: \"<ENABLED?>\",\n)\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::messaging::Messaging;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n let client = Client::new()\n .set_endpoint(\"https://<REGION>.cloud.appwrite.io/v1\")\n .set_project(\"<PROJECT_ID>\")\n .set_key(\"<API_KEY>\");\n\n let messaging = Messaging::new(&client);\n\n // update provider\n let provider = messaging.update_smtp_provider(\n \"<PROVIDER_ID>\", // providerId\n Some(\"<PROVIDER_NAME>\"), // name (optional)\n Some(\"<HOST>\"), // host (optional)\n Some(587), // port (optional)\n Some(\"<USERNAME>\"), // username (optional)\n Some(\"<PASSWORD>\"), // password (optional)\n None, // encryption (optional)\n Some(true), // autoTLS (optional)\n Some(\"<MAILER>\"), // mailer (optional)\n Some(\"<SENDER_NAME>\"), // fromName (optional)\n Some(\"<SENDER_EMAIL>\"), // fromEmail (optional)\n Some(\"<REPLY_TO_NAME>\"), // replyToName (optional)\n Some(\"<REPLY_TO_EMAIL>\"), // replyToEmail (optional)\n Some(true), // enabled (optional)\n ).await?;\n\n // delete provider\n messaging.delete_provider(\"<PROVIDER_ID>\").await?;\n\n println!(\"{:?}\", provider);\n Ok(())\n}\n```\n{% /multicode %}\n{% /tabsitem %}\n{% /tabs %}\n{% /section %}"}, {"path": "docs/products/messaging/targets", "title": "Targets", "description": "Manage avenues of communication by targetting user's device, email, or phone number in your notification and messages.", "content": "Targets are different ways a user can be reached.\nFor example, a user might have two emails, a phone number as well as a phone and a tablet with your app installed.\nThis means, the user has five different targets that you can deliver messages to.\n\n{% only_dark %}\n![Target overview](/images/docs/messaging/targets/dark/target-overview.avif)\n{% /only_dark %}\n{% only_light %}\n![Target overview](/images/docs/messaging/targets/target-overview.avif)\n{% /only_light %}\n\n# Topics and targets {% #topics-and-targets %}\nA user can have multiple targets, such as emails, phone numbers, and devices with your app installed. \nThese targets can subscribe to a topic, so when messages are published to a topic, all subscribed targets receive the message.\n\n{% arrow_link href=\"/docs/products/messaging/topics\" %}\nLearn more about topics\n{% /arrow_link %}\n\n# Types of targets {% #types-of-targets %}\nThere are three types of targets you can use to reach your targets.\n{% table %}\n* Target Type\n* Description\n---\n* **Email**\n* Allows you to send emails to the user's email.\n---\n* **SMS**\n* Allows you to send SMS messages to the user's phone.\n---\n* **Push notification**\n* Allows you to send push notifications to the user's device.\n{% /table %}\n\n\n# Add a target {% #add-a-target %}\nBefore you can send messages, make sure you have the appropriate targets added for your user.\n## Add email target {% #add-email-target%}\nVerified emails for users that signed up with [email password](/docs/products/auth/email-password), \n[magic URL](/docs/products/auth/magic-url), and [email OTP](/docs/products/auth/email-otp) login will already have an email target.\n\n## Add SMS target {% #add-sms-target%}\nVerified phone numbers for users that signed up with [Phone OTP](/docs/products/auth/phone-sms) login will already have a phone target.\n\n## Add push notification target {% #add-push-notification-target%}\nPush notifications require configuration on both the Appwrite platform and your client app's code.\n\n\n{% tabs %}\n{% tabsitem #fcm-ios title=\"iOS with FCM\" %}\n1. In your Firebase console, navigate to **Settings** > **General** > **Your apps** > add an **iOS** app.\n1. Register and download your `google-services.json` config file.\n1. Head to **Apple Developer Member Center** > **Program resources** > **Certificates, Identifiers & Profiles** > **Keys**. The key needs **Apple Push Notification Service** enabled.\n1. Create a new key, note down the key ID and download your key.\n1. In Firebase console, go to *Settings** > **Cloud Messaging** > **APNs authentication key** > click **Upload**. Upload your key here.\n1. Add push notification capability to your app by clicking your root-level app in XCode > **Signing & Capabilities** > {% icon icon=\"plus\" size=\"m\" /%} Capabilities > Search for **Push Notifications**.\n1. If using SwiftUI, disable swizzling by setting `FirebaseAppDelegateProxyEnabled` to `NO` in your `Info.plist`.\n\n{% only_dark %}![Enable Push Notification in XCode](/images/docs/messaging/targets/dark/xcode-enable-pn.avif){% /only_dark %}{% only_light %}![Enable Push Notification in XCode](/images/docs/messaging/targets/xcode-enable-pn.avif){% /only_light %}\n{% /tabsitem %}\n\n{% tabsitem #fcm-android title=\"Android with FCM\" %}\n1. In your Firebase console, navigate to **Settings** > **General** > **Your apps** > add an **Android** app.\n1. Register and download your `google-services.json` config file.\n1. Add `google-services.json` at the root of your project.\n1. Add Google Services class path to your app-level Gradle dependencies block `\"com.google.gms:google-services:4.4.0\"`.\n1. Add Google Services plugin to your app-level Gradle in the plugins block as `\"com.google.gms.google-services\"`.\n1. Add notification handler service to `AndroidManifest.xml` inside the application tag, alongside other activities. Find an example of this service in the [Send push notification](/docs/products/messaging/send-push-notifications#add-targets) journey.\n```xml\n<service android:name=\"<YOUR_NOTIFICATION_HANDLER_SERVICE>\" android:exported=\"false\">\n <intent-filter>\n <action android:name=\"com.google.firebase.MESSAGING_EVENT\" />\n </intent-filter>\n</service>\n```\n{% /tabsitem %}\n\n{% tabsitem #APNs-ios title=\"iOS with APNs\" %}\n1. Head to **Apple Developer Member Center** > **Program resources** > **Certificates, Identifiers & Profiles** > **Keys**. The key needs **Apple Push Notification Service** enabled.\n1. Create a new key, note down the key ID and download your key.\n1. Add push notification capability to your app by clicking your root-level app in XCode > **Signing & Capabilities** > {% icon icon=\"plus\" size=\"m\" /%} Capabilities > Search for **Push Notifications**.\n{% only_dark %}\n![Enable Push Notification in XCode](/images/docs/messaging/targets/dark/xcode-enable-pn.avif)\n{% /only_dark %}\n{% only_light %}\n![Enable Push Notification in XCode](/images/docs/messaging/targets/xcode-enable-pn.avif)\n{% /only_light %}\n\n{% /tabsitem %}\n{% /tabs %}"}, {"path": "docs/products/messaging/telesign", "title": "Telesign", "description": "Send SMS messages to your Appwrite users using Telesign and Appwrite Messaging.", "content": "Telesign lets you send customized SMS messages to your users.\nThese SMS messages can be sent immediately or scheduled.\nYou can send SMS messages for purposes like reminders, promotions, announcements, and even custom authentication flows.\n\n{% section #add-provider step=1 title=\"Add provider\" %}\n\nTo add Telesign as a provider, navigate to **Messaging** > **Providers** > {% icon icon=\"plus\" size=\"m\" /%} **Add provider** > **SMS**.\n{% only_dark %}\n![Add a Telesign provider](/images/docs/messaging/providers/telesign/dark/provider.avif)\n{% /only_dark %}\n{% only_light %}\n![Add a Telesign provider](/images/docs/messaging/providers/telesign/provider.avif)\n{% /only_light %}\n\nGive your provider a name > choose **Telesign** > click **Save and continue**.\nThe provider will be saved to your project, but not enabled until you complete its configuration.\n{% /section %}\n{% section #configure-provider step=2 title=\"Configure provider\" %}\n\nIn the **Configure** step, you will need to provide details from your Telesign dashboard to connect your Appwrite project.\n\nYou will need to provide the following information from your **Telesign dashboard**.\n\n{% table %}\n* Field name\n*\n---\n* Customer ID\n* Head to **Telesign portal** > **Profile** > **Customer ID**.\n---\n* API Key\n* Head to **Telesign portal** > **Profile** > **API Keys**.\n---\n* Sender number\n* The number from which the SMS will be sent. You may need to first purchase a number from Telesign.\n{% /table %}\n\nAfter adding the following details, click **Save and continue** to enable the provider.\n{% /section %}\n\n{% section #test-provider step=3 title=\"Test provider\" %}\nBefore sending your first message,\nmake sure you've configured [a topic](/docs/products/messaging/topics) and [a target](/docs/products/messaging/targets) to send messages to.\n{% tabs %}\n{% tabsitem #console title=\"Console\" %}\nTo send a test message, navigate to **Messaging** > **Messages** > {% icon icon=\"plus\" size=\"m\" /%} **Create message** > **SMS**.\n{% only_dark %}\n![Create SMS message](/images/docs/messaging/messages/dark/create-sms-message.avif)\n{% /only_dark %}\n{% only_light %}\n![Create SMS message](/images/docs/messaging/messages/create-sms-message.avif)\n{% /only_light %}\n\nAdd your message and in the targets step, select one of your test targets. Set the schedule to **Now** and click **Send**.\n\nVerify that you can receive the message in your inbox. If not, check for logs in the Appwrite Console or in your provider's logs.\n{% /tabsitem %}\n\n{% tabsitem #server-sdk title=\"Server SDK\" %}\nTo send a message programmatically, use an [Appwrite Server SDK](/docs/sdks#server).\n{% multicode %}\n```server-nodejs\nconst sdk = require('node-appwrite');\n\n// Init SDK\nconst client = new sdk.Client();\n\nconst messaging = new sdk.Messaging(client);\n\nclient\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n;\n\nconst messaging = await messaging.createSms(\n '<MESSAGE_ID>', // messageId\n '<CONTENT>', // content\n [], // topics (optional)\n [], // users (optional)\n [], // targets (optional)\n true, // draft (optional)\n '' // scheduledAt (optional)\n );\n```\n```deno\nimport * as sdk from \"npm:node-appwrite\";\n\n// Init SDK\nlet client = new sdk.Client();\n\nlet messaging = new sdk.Messaging(client);\n\nclient\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n;\n\nconst messaging = await messaging.createSms(\n '<MESSAGE_ID>', // messageId\n '<CONTENT>', // content\n [], // topics (optional)\n [], // users (optional)\n [], // targets (optional)\n true, // draft (optional)\n '' // scheduledAt (optional)\n );\n```\n```php\n<?php\n\nuse Appwrite\\Client;\nuse Appwrite\\Services\\Messaging;\n\n$client = new Client();\n\n$client\n ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n ->setProject('<PROJECT_ID>') // Your project ID\n ->setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n;\n\n$messaging = new Messaging($client);\n\n$result = $messaging->createSms(\n messageId: '<MESSAGE_ID>',\n content: '<CONTENT>',\n topics: [], // optional\n users: [], // optional\n targets: [], // optional\n draft: true, // optional\n scheduledAt: '' // optional\n);\n```\n```python\nfrom appwrite.client import Client\nfrom appwrite.services.messaging import Messaging\n\nclient = Client()\n\n(client\n .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('<PROJECT_ID>') # Your project ID\n .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key\n)\n\nmessaging = Messaging(client)\n\nresult = messaging.create_sms(\n message_id = '<MESSAGE_ID>',\n content = '<CONTENT>',\n topics = [], # optional\n users = [], # optional\n targets = [], # optional\n draft = True, # optional\n scheduled_at = '' # optional\n)\n```\n```ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('<PROJECT_ID>') # Your project ID\n .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key\n\nmessaging = Messaging.new(client)\n\nresponse = messaging.create_sms(\n message_id: '<MESSAGE_ID>',\n content: '<CONTENT>',\n topics: [], # optional\n users: [], # optional\n targets: [], # optional\n draft: true, # optional\n scheduled_at: '' # optional\n)\n\nputs response.inspect\n```\n```csharp\nusing Appwrite;\nusing Appwrite.Services;\nusing Appwrite.Models;\n\nvar client = new Client()\n .SetEndPoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .SetProject(\"<PROJECT_ID>\") // Your project ID\n .SetKey(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\nvar messaging = new Messaging(client);\n\nMessage result = await messaging.CreateSMS(\n messageId: \"<MESSAGE_ID>\",\n content: \"<CONTENT>\" \n topics: new List<string> {} // optional \n users: new List<string> {} // optional \n targets: new List<string> {} // optional \n draft: true // optional \n scheduledAt: \"\"); // optional\n```\n```dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\nimport 'package:dart_appwrite/enums.dart';\nimport 'package:dart_appwrite/models.dart';\n\nvoid main() async { // Init SDK\n Client client = Client();\n Messaging messaging = Messaging(client);\n\n client\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n ;\n\n Future result = await messaging.createSms(\n messageId: '<MESSAGE_ID>',\n content: '<CONTENT>',\n topics: [], // optional\n users: [], // optional\n targets: [], // optional\n draft: true, // optional\n scheduledAt: '', // optional\n );\n\n result\n .then((response) {\n print(response);\n }).catchError((error) {\n print(error.response);\n });\n}\n```\n```kotlin\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Messaging;\n\nClient client = new Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\nMessaging messaging = new Messaging(client);\n\nmessaging.createSms(\n \"<MESSAGE_ID>\", // messageId\n \"<CONTENT>\", // content\n listOf(), // topics (optional)\n listOf(), // users (optional)\n listOf(), // targets (optional)\n true, // draft (optional)\n \"\" // scheduledAt (optional)\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(result);\n })\n);\n```\n```java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Messaging;\n\nClient client = new Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\nMessaging messaging = new Messaging(client);\n\nmessaging.createSms(\n \"<MESSAGE_ID>\", // messageId\n \"<CONTENT>\", // content\n listOf(), // topics (optional)\n listOf(), // users (optional)\n listOf(), // targets (optional)\n true, // draft (optional)\n \"\" // scheduledAt (optional)\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(result);\n })\n);\n```\n```swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\") // Your secret API key\n\nlet messaging = Messaging(client)\n\nlet message = try await messaging.createSms(\n messageId: \"<MESSAGE_ID>\",\n content: \"<CONTENT>\",\n topics: [], // optional\n users: [], // optional\n targets: [], // optional\n draft: true, // optional\n scheduledAt: \"\" // optional\n)\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::messaging::Messaging;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n let client = Client::new()\n .set_endpoint(\"https://<REGION>.cloud.appwrite.io/v1\")\n .set_project(\"<PROJECT_ID>\")\n .set_key(\"919c2d18fb5d4...a2ae413da83346ad2\");\n\n let messaging = Messaging::new(&client);\n\n let message = messaging.create_sms(\n \"<MESSAGE_ID>\", // messageId\n \"<CONTENT>\", // content\n Some(vec![]), // topics (optional)\n Some(vec![]), // users (optional)\n Some(vec![]), // targets (optional)\n Some(true), // draft (optional)\n None, // scheduledAt (optional)\n ).await?;\n\n println!(\"{:?}\", message);\n Ok(())\n}\n```\n{% /multicode %}\n{% /tabsitem %}\n{% /tabs %}\n\nYou can follow the [Send SMS messages](/docs/products/messaging/send-sms-messages) journey to send your first push notification and test your provider.\n{% /section %}\n\n{% section #manage-provider step=4 title=\"Manage provider\" %}\n{% tabs %}\n{% tabsitem #console title=\"Console\" %}\nYou can update or delete a provider in the Appwrite Console.\n\nNavigate to **Messaging** > **Providers** > click your provider.\nIn the settings, you can update a provider's configuration or delete the provider.\n{% /tabsitem %}\n\n{% tabsitem #server-sdk title=\"Server SDK\" %}\nTo update or delete providers programmatically, use an [Appwrite Server SDK](/docs/sdks#server).\n{% multicode %}\n```server-nodejs\nconst sdk = require('node-appwrite');\n\n// Init SDK\nconst client = new sdk.Client();\n\nconst messaging = new sdk.Messaging(client);\n\nclient\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n;\n\nconst provider = await messaging.updateTelesignProvider(\n '<PROVIDER_ID>', // providerId\n '<NAME>', // name (optional)\n false, // enabled (optional)\n '<USERNAME>', // username (optional)\n '<PASSWORD>', // password (optional)\n '<FROM>' // from (optional)\n );\n```\n```deno\nimport * as sdk from \"npm:node-appwrite\";\n\n// Init SDK\nlet client = new sdk.Client();\n\nlet messaging = new sdk.Messaging(client);\n\nclient\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n;\n\nconst provider = await messaging.updateTelesignProvider(\n '<PROVIDER_ID>', // providerId\n '<NAME>', // name (optional)\n false, // enabled (optional)\n '<USERNAME>', // username (optional)\n '<PASSWORD>', // password (optional)\n '<FROM>' // from (optional)\n );\n```\n```php\n<?php\n\nuse Appwrite\\Client;\nuse Appwrite\\Services\\Messaging;\n\n$client = new Client();\n\n$client\n ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n ->setProject('<PROJECT_ID>') // Your project ID\n ->setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n;\n\n$messaging = new Messaging($client);\n\n$result = $messaging->updateTelesignProvider(\n providerId: '<PROVIDER_ID>',\n name: '<NAME>', // optional\n enabled: false, // optional\n username: '<USERNAME>', // optional\n password: '<PASSWORD>', // optional\n from: '<FROM>' // optional\n);\n```\n```python\nfrom appwrite.client import Client\nfrom appwrite.services.messaging import Messaging\n\nclient = Client()\n\n(client\n .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('<PROJECT_ID>') # Your project ID\n .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key\n)\n\nmessaging = Messaging(client)\n\nresult = messaging.update_telesign_provider(\n provider_id = '<PROVIDER_ID>',\n name = '<NAME>', # optional\n enabled = False, # optional\n username = '<USERNAME>', # optional\n password = '<PASSWORD>', # optional\n from = '<FROM>' # optional\n)\n```\n```ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('<PROJECT_ID>') # Your project ID\n .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key\n\nmessaging = Messaging.new(client)\n\nresponse = messaging.update_telesign_provider(\n provider_id: '<PROVIDER_ID>',\n name: '<NAME>', # optional\n enabled: false, # optional\n username: '<USERNAME>', # optional\n password: '<PASSWORD>', # optional\n from: '<FROM>' # optional\n)\n\nputs response.inspect\n```\n```csharp\nusing Appwrite;\nusing Appwrite.Services;\nusing Appwrite.Models;\n\nvar client = new Client()\n .SetEndPoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .SetProject(\"<PROJECT_ID>\") // Your project ID\n .SetKey(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\nvar messaging = new Messaging(client);\n\nProvider result = await messaging.UpdateTelesignProvider(\n providerId: \"<PROVIDER_ID>\"\n name: \"<NAME>\" // optional\n enabled: false // optional\n username: \"<USERNAME>\" // optional\n password: \"<PASSWORD>\" // optional\n from: \"<FROM>\"); // optional\n```\n```dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\nimport 'package:dart_appwrite/enums.dart';\nimport 'package:dart_appwrite/models.dart';\n\nvoid main() { // Init SDK\n Client client = Client();\n Messaging messaging = Messaging(client);\n\n client\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n ;\n\n Future result = messaging.updateTelesignProvider(\n providerId: '<PROVIDER_ID>',\n name: '<NAME>', // optional\n enabled: false, // optional\n username: '<USERNAME>', // optional\n password: '<PASSWORD>', // optional\n from: '<FROM>', // optional\n );\n\n result\n .then((response) {\n print(response);\n }).catchError((error) {\n print(error.response);\n });\n}\n```\n```kotlin\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Messaging;\n\nClient client = new Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\nMessaging messaging = new Messaging(client);\n\nmessaging.updateTelesignProvider(\n \"<PROVIDER_ID>\", // providerId\n \"<NAME>\", // name (optional)\n false, // enabled (optional)\n \"<USERNAME>\", // username (optional)\n \"<PASSWORD>\", // password (optional)\n \"<FROM>\" // from (optional)\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(result);\n })\n);\n```\n```java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Messaging;\n\nClient client = new Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\nMessaging messaging = new Messaging(client);\n\nmessaging.updateTelesignProvider(\n \"<PROVIDER_ID>\", // providerId\n \"<NAME>\", // name (optional)\n false, // enabled (optional)\n \"<USERNAME>\", // username (optional)\n \"<PASSWORD>\", // password (optional)\n \"<FROM>\" // from (optional)\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(result);\n })\n);\n```\n```swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\") // Your secret API key\n\nlet messaging = Messaging(client)\n\nlet provider = try await messaging.updateTelesignProvider(\n providerId: \"<PROVIDER_ID>\",\n name: \"<NAME>\", // optional\n enabled: xfalse, // optional\n username: \"<USERNAME>\", // optional\n password: \"<PASSWORD>\", // optional\n from: \"<FROM>\" // optional\n)\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::messaging::Messaging;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n let client = Client::new()\n .set_endpoint(\"https://<REGION>.cloud.appwrite.io/v1\")\n .set_project(\"<PROJECT_ID>\")\n .set_key(\"919c2d18fb5d4...a2ae413da83346ad2\");\n\n let messaging = Messaging::new(&client);\n\n let provider = messaging.update_telesign_provider(\n \"<PROVIDER_ID>\", // providerId\n Some(\"<NAME>\"), // name (optional)\n Some(false), // enabled (optional)\n Some(\"<CUSTOMER_ID>\"), // customerId (optional)\n Some(\"<API_KEY>\"), // apiKey (optional)\n Some(\"<FROM>\"), // from (optional)\n ).await?;\n\n println!(\"{:?}\", provider);\n Ok(())\n}\n```\n{% /multicode %}\n{% /tabsitem %}\n{% /tabs %}\n{% /section %}"}, {"path": "docs/products/messaging/textmagic", "title": "Textmagic", "description": "Send SMS messages to your Appwrite users using Textmagic and Appwrite Messaging.", "content": "Textmagic lets you send customized SMS messages to your users.\nThese SMS messages can be sent immediately or scheduled.\nYou can send SMS messages for purposes like reminders, promotions, announcements, and even custom authentication flows.\n\n{% section #add-provider step=1 title=\"Add provider\" %}\n\nTo add Textmagic as a provider, navigate to **Messaging** > **Providers** > {% icon icon=\"plus\" size=\"m\" /%} **Add provider** > **SMS**.\n{% only_dark %}\n![Add a Textmagic provider](/images/docs/messaging/providers/textmagic/dark/provider.avif)\n{% /only_dark %}\n{% only_light %}\n![Add a Textmagic provider](/images/docs/messaging/providers/textmagic/provider.avif)\n{% /only_light %}\n\nGive your provider a name > choose **Textmagic** > click **Save and continue**.\nThe provider will be saved to your project, but not enabled until you complete its configuration.\n{% /section %}\n{% section #configure-provider step=2 title=\"Configure provider\" %}\n\nIn the **Configure** step, you will need to provide details from your Textmagic dashboard to connect your Appwrite project.\n\nYou will need to provide the following information from your **Textmagic dashboard**.\n\n{% table %}\n* Field name\n*\n---\n* API key\n* Head to Textmagic dashboard > **Services** > **API** > **Add new API key**.\n---\n* Username\n* Head to Textmagic dashboard > **My account** > **Username**.\n---\n* Sender number\n* Head to Textmagic dashboard > **Services** > **Sender settings**.\n{% /table %}\n\nAfter adding the following details, click **Save and continue** to enable the provider.\n{% /section %}\n\n{% section #test-provider step=3 title=\"Test provider\" %}\nBefore sending your first message,\nmake sure you've configured [a topic](/docs/products/messaging/topics) and [a target](/docs/products/messaging/targets) to send messages to.\n{% tabs %}\n{% tabsitem #console title=\"Console\" %}\nTo send a test message, navigate to **Messaging** > **Messages** > {% icon icon=\"plus\" size=\"m\" /%} **Create message** > **SMS**.\n{% only_dark %}\n![Create an SMS message](/images/docs/messaging/messages/dark/create-sms-message.avif)\n{% /only_dark %}\n{% only_light %}\n![Create an SMS message](/images/docs/messaging/messages/create-sms-message.avif)\n{% /only_light %}\n\nAdd your message and in the targets step, select one of your test targets. Set the schedule to **Now** and click **Send**.\n\nVerify that you can receive the message in your inbox. If not, check for logs in the Appwrite Console or in your provider's logs.\n{% /tabsitem %}\n\n{% tabsitem #server-sdk title=\"Server SDK\" %}\nTo send a message programmatically, use an [Appwrite Server SDK](/docs/sdks#server).\n{% multicode %}\n```server-nodejs\nconst sdk = require('node-appwrite');\n\n// Init SDK\nconst client = new sdk.Client();\n\nconst messaging = new sdk.Messaging(client);\n\nclient\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n;\n\nconst messaging = await messaging.createSms(\n '<MESSAGE_ID>', // messageId\n '<CONTENT>', // content\n [], // topics (optional)\n [], // users (optional)\n [], // targets (optional)\n true, // draft (optional)\n '' // scheduledAt (optional)\n );\n```\n```deno\nimport * as sdk from \"npm:node-appwrite\";\n\n// Init SDK\nlet client = new sdk.Client();\n\nlet messaging = new sdk.Messaging(client);\n\nclient\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n;\n\nconst messaging = await messaging.createSms(\n '<MESSAGE_ID>', // messageId\n '<CONTENT>', // content\n [], // topics (optional)\n [], // users (optional)\n [], // targets (optional)\n true, // draft (optional)\n '' // scheduledAt (optional)\n );\n```\n```php\n<?php\n\nuse Appwrite\\Client;\nuse Appwrite\\Services\\Messaging;\n\n$client = new Client();\n\n$client\n ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n ->setProject('<PROJECT_ID>') // Your project ID\n ->setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n;\n\n$messaging = new Messaging($client);\n\n$result = $messaging->createSms(\n messageId: '<MESSAGE_ID>',\n content: '<CONTENT>',\n topics: [], // optional\n users: [], // optional\n targets: [], // optional\n draft: true, // optional\n scheduledAt: '' // optional\n);\n```\n```python\nfrom appwrite.client import Client\nfrom appwrite.services.messaging import Messaging\n\nclient = Client()\n\n(client\n .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('<PROJECT_ID>') # Your project ID\n .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key\n)\n\nmessaging = Messaging(client)\n\nresult = messaging.create_sms(\n message_id = '<MESSAGE_ID>',\n content = '<CONTENT>',\n topics = [], # optional\n users = [], # optional\n targets = [], # optional\n draft = True, # optional\n scheduled_at = '' # optional\n)\n```\n```ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('<PROJECT_ID>') # Your project ID\n .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key\n\nmessaging = Messaging.new(client)\n\nresponse = messaging.create_sms(\n message_id: '<MESSAGE_ID>',\n content: '<CONTENT>',\n topics: [], # optional\n users: [], # optional\n targets: [], # optional\n draft: true, # optional\n scheduled_at: '' # optional\n)\n\nputs response.inspect\n```\n```csharp\nusing Appwrite;\nusing Appwrite.Services;\nusing Appwrite.Models;\n\nvar client = new Client()\n .SetEndPoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .SetProject(\"<PROJECT_ID>\") // Your project ID\n .SetKey(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\nvar messaging = new Messaging(client);\n\nMessage result = await messaging.CreateSMS(\n messageId: \"<MESSAGE_ID>\",\n content: \"<CONTENT>\" \n topics: new List<string> {} // optional \n users: new List<string> {} // optional \n targets: new List<string> {} // optional \n draft: true // optional \n scheduledAt: \"\"); // optional\n```\n```dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\nimport 'package:dart_appwrite/enums.dart';\nimport 'package:dart_appwrite/models.dart';\n\nvoid main() async { // Init SDK\n Client client = Client();\n Messaging messaging = Messaging(client);\n\n client\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n ;\n\n Future result = await messaging.createSms(\n messageId: '<MESSAGE_ID>',\n content: '<CONTENT>',\n topics: [], // optional\n users: [], // optional\n targets: [], // optional\n draft: true, // optional\n scheduledAt: '', // optional\n );\n\n result\n .then((response) {\n print(response);\n }).catchError((error) {\n print(error.response);\n });\n}\n```\n```kotlin\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Messaging;\n\nClient client = new Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\nMessaging messaging = new Messaging(client);\n\nmessaging.createSms(\n \"<MESSAGE_ID>\", // messageId\n \"<CONTENT>\", // content\n listOf(), // topics (optional)\n listOf(), // users (optional)\n listOf(), // targets (optional)\n true, // draft (optional)\n \"\" // scheduledAt (optional)\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(result);\n })\n);\n```\n```java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Messaging;\n\nClient client = new Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\nMessaging messaging = new Messaging(client);\n\nmessaging.createSms(\n \"<MESSAGE_ID>\", // messageId\n \"<CONTENT>\", // content\n listOf(), // topics (optional)\n listOf(), // users (optional)\n listOf(), // targets (optional)\n true, // draft (optional)\n \"\" // scheduledAt (optional)\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(result);\n })\n);\n```\n```swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\") // Your secret API key\n\nlet messaging = Messaging(client)\n\nlet message = try await messaging.createSms(\n messageId: \"<MESSAGE_ID>\",\n content: \"<CONTENT>\",\n topics: [], // optional\n users: [], // optional\n targets: [], // optional\n draft: true, // optional\n scheduledAt: \"\" // optional\n)\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::messaging::Messaging;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n let client = Client::new()\n .set_endpoint(\"https://<REGION>.cloud.appwrite.io/v1\")\n .set_project(\"<PROJECT_ID>\")\n .set_key(\"919c2d18fb5d4...a2ae413da83346ad2\");\n\n let messaging = Messaging::new(&client);\n\n let message = messaging.create_sms(\n \"<MESSAGE_ID>\", // messageId\n \"<CONTENT>\", // content\n Some(vec![]), // topics (optional)\n Some(vec![]), // users (optional)\n Some(vec![]), // targets (optional)\n Some(true), // draft (optional)\n None, // scheduledAt (optional)\n ).await?;\n\n println!(\"{:?}\", message);\n Ok(())\n}\n```\n{% /multicode %}\n{% /tabsitem %}\n{% /tabs %}\n\nYou can follow the [Send SMS messages](/docs/products/messaging/send-sms-messages) journey to send your first push notification and test your provider.\n{% /section %}\n\n{% section #manage-provider step=4 title=\"Manage provider\" %}\n{% tabs %}\n{% tabsitem #console title=\"Console\" %}\nYou can update or delete a provider in the Appwrite Console.\n\nNavigate to **Messaging** > **Providers** > click your provider.\nIn the settings, you can update a provider's configuration or delete the provider.\n{% /tabsitem %}\n\n{% tabsitem #server-sdk title=\"Server SDK\" %}\nTo update or delete providers programmatically, use an [Appwrite Server SDK](/docs/sdks#server).\n{% multicode %}\n```server-nodejs\nconst sdk = require('node-appwrite');\n\n// Init SDK\nconst client = new sdk.Client();\n\nconst messaging = new sdk.Messaging(client);\n\nclient\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n;\n\nconst provider = await messaging.updateTextmagicProvider(\n '<PROVIDER_ID>', // providerId\n '<NAME>', // name (optional)\n false, // enabled (optional)\n '<USERNAME>', // username (optional)\n '<API_KEY>', // apiKey (optional)\n '<FROM>' // from (optional)\n );\n```\n```deno\nimport * as sdk from \"npm:node-appwrite\";\n\n// Init SDK\nlet client = new sdk.Client();\n\nlet messaging = new sdk.Messaging(client);\n\nclient\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n;\n\nconst provider = await messaging.updateTextmagicProvider(\n '<PROVIDER_ID>', // providerId\n '<NAME>', // name (optional)\n false, // enabled (optional)\n '<USERNAME>', // username (optional)\n '<API_KEY>', // apiKey (optional)\n '<FROM>' // from (optional)\n );\n```\n```php\n<?php\n\nuse Appwrite\\Client;\nuse Appwrite\\Services\\Messaging;\n\n$client = new Client();\n\n$client\n ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n ->setProject('<PROJECT_ID>') // Your project ID\n ->setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n;\n\n$messaging = new Messaging($client);\n\n$result = $messaging->updateTextmagicProvider(\n providerId: '<PROVIDER_ID>',\n name: '<NAME>', // optional\n enabled: false, // optional\n username: '<USERNAME>', // optional\n apiKey: '<API_KEY>', // optional\n from: '<FROM>' // optional\n);\n```\n```python\nfrom appwrite.client import Client\nfrom appwrite.services.messaging import Messaging\n\nclient = Client()\n\n(client\n .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('<PROJECT_ID>') # Your project ID\n .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key\n)\n\nmessaging = Messaging(client)\n\nresult = messaging.update_textmagic_provider(\n provider_id = '<PROVIDER_ID>',\n name = '<NAME>', # optional\n enabled = False, # optional\n username = '<USERNAME>', # optional\n api_key = '<API_KEY>', # optional\n from = '<FROM>' # optional\n)\n```\n```ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('<PROJECT_ID>') # Your project ID\n .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key\n\nmessaging = Messaging.new(client)\n\nresponse = messaging.update_textmagic_provider(\n provider_id: '<PROVIDER_ID>',\n name: '<NAME>', # optional\n enabled: false, # optional\n username: '<USERNAME>', # optional\n api_key: '<API_KEY>', # optional\n from: '<FROM>' # optional\n)\n\nputs response.inspect\n```\n```csharp\nusing Appwrite;\nusing Appwrite.Services;\nusing Appwrite.Models;\n\nvar client = new Client()\n .SetEndPoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .SetProject(\"<PROJECT_ID>\") // Your project ID\n .SetKey(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\nvar messaging = new Messaging(client);\n\nProvider result = await messaging.UpdateTextmagicProvider(\n providerId: \"<PROVIDER_ID>\"\n name: \"<NAME>\" // optional\n enabled: false // optional\n username: \"<USERNAME>\" // optional\n apiKey: \"<API_KEY>\" // optional\n from: \"<FROM>\"); // optional\n```\n```dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\nimport 'package:dart_appwrite/enums.dart';\nimport 'package:dart_appwrite/models.dart';\n\nvoid main() { // Init SDK\n Client client = Client();\n Messaging messaging = Messaging(client);\n\n client\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n ;\n\n Future result = messaging.updateTextmagicProvider(\n providerId: '<PROVIDER_ID>',\n name: '<NAME>', // optional\n enabled: false, // optional\n username: '<USERNAME>', // optional\n apiKey: '<API_KEY>', // optional\n from: '<FROM>', // optional\n );\n\n result\n .then((response) {\n print(response);\n }).catchError((error) {\n print(error.response);\n });\n}\n```\n```kotlin\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Messaging;\n\nClient client = new Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\nMessaging messaging = new Messaging(client);\n\nmessaging.updateTextmagicProvider(\n \"<PROVIDER_ID>\", // providerId\n \"<NAME>\", // name (optional)\n false, // enabled (optional)\n \"<USERNAME>\", // username (optional)\n \"<API_KEY>\", // apiKey (optional)\n \"<FROM>\" // from (optional)\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(result);\n })\n);\n```\n```java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Messaging;\n\nClient client = new Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\nMessaging messaging = new Messaging(client);\n\nmessaging.updateTextmagicProvider(\n \"<PROVIDER_ID>\", // providerId\n \"<NAME>\", // name (optional)\n false, // enabled (optional)\n \"<USERNAME>\", // username (optional)\n \"<API_KEY>\", // apiKey (optional)\n \"<FROM>\" // from (optional)\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(result);\n })\n);\n```\n```swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\") // Your secret API key\n\nlet messaging = Messaging(client)\n\nlet provider = try await messaging.updateTextmagicProvider(\n providerId: \"<PROVIDER_ID>\",\n name: \"<NAME>\", // optional\n enabled: xfalse, // optional\n username: \"<USERNAME>\", // optional\n apiKey: \"<API_KEY>\", // optional\n from: \"<FROM>\" // optional\n)\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::messaging::Messaging;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n let client = Client::new()\n .set_endpoint(\"https://<REGION>.cloud.appwrite.io/v1\")\n .set_project(\"<PROJECT_ID>\")\n .set_key(\"919c2d18fb5d4...a2ae413da83346ad2\");\n\n let messaging = Messaging::new(&client);\n\n let provider = messaging.update_textmagic_provider(\n \"<PROVIDER_ID>\", // providerId\n Some(\"<NAME>\"), // name (optional)\n Some(false), // enabled (optional)\n Some(\"<USERNAME>\"), // username (optional)\n Some(\"<API_KEY>\"), // apiKey (optional)\n Some(\"<FROM>\"), // from (optional)\n ).await?;\n\n println!(\"{:?}\", provider);\n Ok(())\n}\n```\n{% /multicode %}\n{% /tabsitem %}\n{% /tabs %}\n{% /section %}"}, {"path": "docs/products/messaging/topics", "title": "Topics", "description": "Allow groups of users to subscribe to a common topic and receive the same notifications.", "content": "In Appwrite Messaging, you can use topics to deliver messages to groups of users at once.\n\n{% only_dark %}\n![Add a target](/images/docs/messaging/topics/dark/topics.avif)\n{% /only_dark %}\n{% only_light %}\n![Add a target](/images/docs/messaging/topics/topics.avif)\n{% /only_light %}\n\n# Topics and targets {% #topics-and-targets %}\nA user can have multiple targets, such as emails, phone numbers, and devices with your app installed.\nThese targets can subscribe to a topic, so when messages are published to a topic, all subscribed targets receive the message.\n\n{% arrow_link href=\"/docs/products/messaging/targets\" %}\nLearn more about targets\n{% /arrow_link %}\n\n# Organizing topics {% #organizing-topics %}\nA topic should have semantic meaning.\nFor example, a topic can represent a group of customers that receiving a common announcemennt or publishing public updates.\nIt's important to keep privacy in mind when using topics.\nPrefer sending private information like chat messages by addressing individual targets attached to a user.\n\nTopics are optimized for delivering the same message to large groups of users.\nIf you need to deliver messages to **all devices of the same user**, you can find a user's targets by calling `account.get()`.\n\n# Create a topic {% #create-a-topic %}\nYou can create topics\n{% tabs %}\n{% tabsitem #console title=\"Console\" %}\n{% only_dark %}\n![Add a topic](/images/docs/messaging/topics/dark/create-topics.avif)\n{% /only_dark %}\n{% only_light %}\n![Add a topic](/images/docs/messaging/topics/create-topics.avif)\n{% /only_light %}\nNavigate to your Appwrite Console > **Messaging** > **Topics** > **Create topic**.\n{% /tabsitem %}\n\n{% tabsitem #server-sdk title=\"Server SDK\" %}\n{% multicode %}\n```server-nodejs\nconst sdk = require('node-appwrite');\n\n// Init SDK\nconst client = new sdk.Client();\n\nconst messaging = new sdk.Messaging(client);\n\nclient\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n;\n\nconst topic = messaging.createTopic(\n '<TOPIC_ID>', // topicId\n '<NAME>', // name\n '<ROLES>' // permission roles for who can subscribe\n );\n\n```\n```deno\nimport * as sdk from \"npm:node-appwrite\";\n\n// Init SDK\nlet client = new sdk.Client();\n\nlet messaging = new sdk.Messaging(client);\n\nclient\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n;\n\nconst topic = messaging.createTopic({\n topicId: '<TOPIC_ID>',\n name: '<NAME>',\n subscribe: '<ROLES>' // permission roles for who can subscribe\n });\nconst topic = messaging.createTopic(\n '<TOPIC_ID>', // topicId\n '<NAME>', // name\n '<ROLES>' // permission roles for who can subscribe\n );\n```\n```php\n<?php\n\nuse Appwrite\\Client;\nuse Appwrite\\Services\\Messaging;\n\n$client = new Client();\n\n$client\n ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n ->setProject('<PROJECT_ID>') // Your project ID\n ->setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n;\n\n$messaging = new Messaging($client);\n\n$topic = $messaging->createTopic(\n topicId: '<TOPIC_ID>',\n name: '<NAME>',\n subscribe: '<ROLES>' // permission roles for who can subscribe\n\n);\n```\n```python\nfrom appwrite.client import Client\nfrom appwrite.services.messaging import Messaging\n\nclient = Client()\n\n(client\n .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('<PROJECT_ID>') # Your project ID\n .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key\n)\n\nmessaging = Messaging(client)\n\ntopic = messaging.create_topic(\n topic_id = '<TOPIC_ID>',\n name = '<NAME>',\n subscribe = '<ROLES>' # permission roles for who can subscribe\n)\n```\n```ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('<PROJECT_ID>') # Your project ID\n .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key\n\nmessaging = Messaging.new(client)\n\ntopic = messaging.create_topic(\n topic_id: '<TOPIC_ID>',\n name: '<NAME>',\n subscribe: '<ROLES>' # permission roles for who can subscribe\n)\n\nputs topic.inspect\n```\n```csharp\nusing Appwrite;\nusing Appwrite.Services;\nusing Appwrite.Models;\n\nvar client = new Client()\n .SetEndPoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .SetProject(\"<PROJECT_ID>\") // Your project ID\n .SetKey(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\nvar messaging = new Messaging(client);\n\nTopic topic = await messaging.CreateTopic(\n topicId: \"<TOPIC_ID>\",\n name: \"<NAME>\",\n subscribe: \"<ROLES>\") // permission roles for who can subscribe\n```\n```dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\nimport 'package:dart_appwrite/enums.dart';\nimport 'package:dart_appwrite/models.dart';\n\nvoid main() async { // Init SDK\n Client client = Client();\n Messaging messaging = Messaging(client);\n\n client\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n ;\n\n Future result = await messaging.createTopic(\n topicId: '<TOPIC_ID>',\n name: '<NAME>',\n subscribe: '<ROLES>' // permission roles for who can subscribe\n );\n\n result\n .then((response) {\n print(response);\n }).catchError((error) {\n print(error.response);\n });\n}\n```\n```kotlin\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Messaging;\n\nClient client = new Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\nMessaging messaging = new Messaging(client);\n\nmessaging.createTopic(\n \"<TOPIC_ID>\", // topicId\n \"<NAME>\" // name\n \"<ROLES>\" // permission roles for who can subscribe\n new CoroutineCallback<>((topic, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(topic);\n })\n);\n```\n```java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Messaging;\n\nClient client = new Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\nMessaging messaging = new Messaging(client);\n\nmessaging.createTopic(\n \"<TOPIC_ID>\", // topicId\n \"<NAME>\" // name\n \"<ROLES>\" // permission roles for who can subscribe\n new CoroutineCallback<>((topic, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(topic);\n })\n);\n```\n```swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\") // Your secret API key\n\nlet messaging = Messaging(client)\n\nlet topic = try await messaging.createTopic(\n topicId: \"<TOPIC_ID>\",\n name: \"<NAME>\",\n subscribe: \"<ROLES>\" // permission roles for who can subscribe\n)\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::messaging::Messaging;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n let client = Client::new()\n .set_endpoint(\"https://<REGION>.cloud.appwrite.io/v1\")\n .set_project(\"<PROJECT_ID>\")\n .set_key(\"919c2d18fb5d4...a2ae413da83346ad2\");\n\n let messaging = Messaging::new(&client);\n\n let topic = messaging.create_topic(\n \"<TOPIC_ID>\", // topicId\n \"<NAME>\", // name\n Some(vec![\"<ROLES>\".to_string()]), // subscribe (optional)\n ).await?;\n\n println!(\"{:?}\", topic);\n Ok(())\n}\n```\n{% /multicode %}\n{% /tabsitem %}\n\n{% tabsitem #cli title=\"CLI\" %}\n\n{% partial file=\"cli-disclaimer.md\" /%}\n\nYou can create a topic using the CLI command `appwrite init topics` to initialize a topic.\n\n```sh\nappwrite init topics\n```\n\nYou can now push your topics with the following command:\n\n```sh\nappwrite push topics\n```\n\nThis will create your topic in the Console with all of your `appwrite.config.json` configurations.\n\n{% arrow_link href=\"/docs/tooling/command-line/topics#commands\" %}\nLearn more about the CLI topics commands\n{% /arrow_link %}\n\n{% /tabsitem %}\n{% /tabs %}\n\n# Permissions {% #permissions %}\nBefore you can subscribe to a topic, a user needs the appropriate permission.\nYou can set permission by navigating to **Messaging** > **Topics** > select a topic to configure > **Subscription access**.\n\n{% arrow_link href=\"/docs/advanced/security/permissions#permission-roles\" %}\nLearn more about permission roles\n{% /arrow_link %}\n\n# Subscribe targets to a topic {% #subscribe-targets-to-topics %}\n{% tabs %}\n{% tabsitem #console title=\"Console\" %}\nDuring development, you can subscribe targets to a topic for testing right in the Appwrite console.\n{% only_dark %}\n![Add a topic](/images/docs/messaging/topics/dark/add-subscriber.avif)\n{% /only_dark %}\n{% only_light %}\n![Add a topic](/images/docs/messaging/topics/add-subscriber.avif)\n{% /only_light %}\nNavigate to your Appwrite Console > **Messaging** > **Topics** > click on your topic > **Subscribers** > **Create topic** > **Add subscriber**.\n\nIf you can't find the targets you'd like to add, see the [targets page](/docs/products/messaging/targets).\n{% /tabsitem %}\n\n{% tabsitem #server-sdk title=\"Server SDK\" %}\n{% multicode %}\n```server-nodejs\nconst sdk = require('node-appwrite');\n\nconst client = new sdk.Client()\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setJWT('eyJhbVCJ9.eyJ...'); // Your secret JSON Web Token\n\nconst messaging = new sdk.Messaging(client);\n\nconst subscriber = await messaging.createSubscriber(\n '<TOPIC_ID>', // topicId\n '<SUBSCRIBER_ID>', // subscriberId\n '<TARGET_ID>' // targetId\n);\n```\n```deno\nimport * as sdk from \"npm:node-appwrite\";\n\nconst client = new sdk.Client()\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setJWT('eyJhbVCJ9.eyJ...'); // Your secret JSON Web Token\n\nconst messaging = new sdk.Messaging(client);\n\nconst subscriber = await messaging.createSubscriber({\n topicId: '<TOPIC_ID>',\n subscriberId: '<SUBSCRIBER_ID>',\n targetId: '<TARGET_ID>'\n});\n```\n```php\n<?php\n\nuse Appwrite\\Client;\nuse Appwrite\\Services\\Messaging;\n\n$client = new Client();\n\n$client\n ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n ->setProject('<PROJECT_ID>') // Your project ID\n ->setJWT('eyJhbVCJ9.eyJ...'); // Your secret JSON Web Token\n\n$messaging = new Messaging($client);\n\n$subscriber = $messaging->createSubscriber(\n topicId: '[TOPIC_ID]',\n subscriberId: '[SUBSCRIBER_ID]',\n targetId: '[TARGET_ID]'\n topicId: '<TOPIC_ID>',\n subscriberId: '<SUBSCRIBER_ID>',\n targetId: '<TARGET_ID>'\n);\n```\n```python\nfrom appwrite.client import Client\nfrom appwrite.services.messaging import Messaging\n\nclient = Client()\nclient.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\nclient.set_project('<PROJECT_ID>') # Your project ID\nclient.set_jwt('eyJhbVCJ9.eyJ...') # Your secret JSON Web Token\n\nmessaging = Messaging(client)\n\nsubscriber = messaging.create_subscriber(\n topic_id = '<TOPIC_ID>',\n subscriber_id = '<SUBSCRIBER_ID>',\n target_id = '<TARGET_ID>'\n)\n```\n```ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('<PROJECT_ID>') # Your project ID\n .set_jwt('eyJhbVCJ9.eyJ...') # Your secret JSON Web Token\n\nmessaging = Messaging.new(client)\n\nsubscriber = messaging.create_subscriber(\n topic_id: '<TOPIC_ID>',\n subscriber_id: '<SUBSCRIBER_ID>',\n target_id: '<TARGET_ID>'\n)\n```\n```csharp\nusing Appwrite;\nusing Appwrite.Services;\nusing Appwrite.Models;\n\nClient client = new Client()\n .SetEndPoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .SetProject(\"<PROJECT_ID>\") // Your project ID\n .SetJWT(\"eyJhbVCJ9.eyJ...\"); // Your secret JSON Web Token\n\nMessaging messaging = new Messaging(client);\n\nSubscriber result = await messaging.CreateSubscriber(\n topicId: \"<TOPIC_ID>\",\n subscriberId: \"<SUBSCRIBER_ID>\",\n targetId: \"<TARGET_ID>\");\n```\n```dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\nimport 'package:dart_appwrite/enums.dart';\nimport 'package:dart_appwrite/models.dart';\n\nClient client = Client()\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setJWT('eyJhbVCJ9.eyJ...'); // Your secret JSON Web Token\n\nMessaging messaging = Messaging(client);\n\nSubscriber subscriber result = await messaging.createSubscriber(\n topicId: '<TOPIC_ID>',\n subscriberId: '<SUBSCRIBER_ID>',\n targetId: '<TARGET_ID>',\n);\n```\n```kotlin\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Messaging;\n\nval client = Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setJWT(\"eyJhbVCJ9.eyJ...\") // Your secret JSON Web Token\n\nval messaging = new Messaging(client)\n\nval subscriber = messaging.createSubscriber(\n topicId = \"<TOPIC_ID>\",\n subscriberId = \"<SUBSCRIBER_ID>\",\n targetId = \"<TARGET_ID>\"\n)\n```\n```java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Messaging;\n\nClient client = new Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setJWT(\"eyJhbVCJ9.eyJ...\"); // Your secret JSON Web Token\n\nMessaging messaging = new Messaging(client);\n\nmessaging.createSubscriber(\n \"<TOPIC_ID>\", // topicId\n \"<SUBSCRIBER_ID>\", // subscriberId\n \"<TARGET_ID>\" // targetId\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(result);\n })\n);\n```\n```swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setJWT(\"eyJhbVCJ9.eyJ...\") // Your secret JSON Web Token\n\nlet messaging = Messaging(client)\n\nlet subscriber = try await messaging.createSubscriber(\n topicId: \"<TOPIC_ID>\",\n subscriberId: \"<SUBSCRIBER_ID>\",\n targetId: \"<TARGET_ID>\"\n)\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::messaging::Messaging;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n let client = Client::new()\n .set_endpoint(\"https://<REGION>.cloud.appwrite.io/v1\")\n .set_project(\"<PROJECT_ID>\")\n .set_jwt(\"eyJhbVCJ9.eyJ...\");\n\n let messaging = Messaging::new(&client);\n\n let subscriber = messaging.create_subscriber(\n \"<TOPIC_ID>\", // topicId\n \"<SUBSCRIBER_ID>\", // subscriberId\n \"<TARGET_ID>\", // targetId\n ).await?;\n\n println!(\"{:?}\", subscriber);\n Ok(())\n}\n```\n{% /multicode %}\n{% /tabsitem %}\n{% /tabs %}"}, {"path": "docs/products/messaging/twilio", "title": "Twilio", "description": "Send SMS messages to your Appwrite users using Twilio and Appwrite Messaging.", "content": "Twilio lets you send customized SMS messages to your users.\nThese SMS messages can be sent immediately or scheduled.\nYou can send SMS messages for purposes like reminders, promotions, announcements, and even custom authentication flows.\n\n{% section #add-provider step=1 title=\"Add provider\" %}\n\nTo add Twilio as a provider, navigate to **Messaging** > **Providers** > {% icon icon=\"plus\" size=\"m\" /%} **Add provider** > **SMS**.\n{% only_dark %}\n![Add a Twilio provider](/images/docs/messaging/providers/twilio/dark/provider.avif)\n{% /only_dark %}\n{% only_light %}\n![Add a Twilio provider](/images/docs/messaging/providers/twilio/provider.avif)\n{% /only_light %}\n\nGive your provider a name > choose **Twilio** > click **Save and continue**.\nThe provider will be saved to your project, but not enabled until you complete its configuration.\n{% /section %}\n{% section #configure-provider step=2 title=\"Configure provider\" %}\n\nIn the **Configure** step, you will need to provide details from your Twilio dashboard to connect your Appwrite project.\n\nYou will need to provide the following information from your **Twilio dashboard**.\n\n{% table %}\n* Field name\n*\n---\n* Account SID\n* Head to Twilio console > **Account info** > **Account SID**.\n---\n* Auth token\n* Head to Twilio console > **Account info** > **Auth Token**.\n---\n* Sender number\n* You can access numbers by navigating to your Twilio console > **Develop** > **Phone Numbers** > **Manage** > **Active Numbers**.\n{% /table %}\n\nAfter adding the following details, click **Save and continue** to enable the provider.\n{% /section %}\n\n{% section #test-provider step=3 title=\"Test provider\" %}\nBefore sending your first message,\nmake sure you've configured [a topic](/docs/products/messaging/topics) and [a target](/docs/products/messaging/targets) to send messages to.\n{% tabs %}\n{% tabsitem #console title=\"Console\" %}\nTo send a test message, navigate to **Messaging** > **Messages** > {% icon icon=\"plus\" size=\"m\" /%} **Create message** > **SMS**.\n{% only_dark %}\n![Create an SMS message](/images/docs/messaging/messages/dark/create-sms-message.avif)\n{% /only_dark %}\n{% only_light %}\n![Create an SMS message](/images/docs/messaging/messages/create-sms-message.avif)\n{% /only_light %}\n\nAdd your message and in the targets step, select one of your test targets. Set the schedule to **Now** and click **Send**.\n\nVerify that you can receive the message in your inbox. If not, check for logs in the Appwrite Console or in your provider's logs.\n{% /tabsitem %}\n\n{% tabsitem #server-sdk title=\"Server SDK\" %}\nTo send a message programmatically, use an [Appwrite Server SDK](/docs/sdks#server).\n{% multicode %}\n\n```server-nodejs\nconst sdk = require('node-appwrite');\n\n// Init SDK\nconst client = new sdk.Client();\n\nconst messaging = new sdk.Messaging(client);\n\nclient\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n;\n\nconst message = await messaging.createSms(\n '<MESSAGE_ID>', // messageId\n '<CONTENT>', // content\n [], // topics (optional)\n [], // users (optional)\n [], // targets (optional)\n true, // draft (optional)\n '' // scheduledAt (optional)\n );\n```\n```deno\nimport * as sdk from \"npm:node-appwrite\";\n\n// Init SDK\nlet client = new sdk.Client();\n\nlet messaging = new sdk.Messaging(client);\n\nclient\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n;\n\nconst message = await messaging.createSms(\n '<MESSAGE_ID>', // messageId\n '<CONTENT>', // content\n [], // topics (optional)\n [], // users (optional)\n [], // targets (optional)\n true, // draft (optional)\n '' // scheduledAt (optional)\n );\n```\n```php\n<?php\n\nuse Appwrite\\Client;\nuse Appwrite\\Services\\Messaging;\n\n$client = new Client();\n\n$client\n ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n ->setProject('<PROJECT_ID>') // Your project ID\n ->setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n;\n\n$messaging = new Messaging($client);\n\n$result = $messaging->createSms(\n messageId: '<MESSAGE_ID>',\n content: '<CONTENT>',\n topics: [], // optional\n users: [], // optional\n targets: [], // optional\n draft: true, // optional\n scheduledAt: '' // optional\n);\n```\n```python\nfrom appwrite.client import Client\nfrom appwrite.services.messaging import Messaging\n\nclient = Client()\n\n(client\n .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('<PROJECT_ID>') # Your project ID\n .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key\n)\n\nmessaging = Messaging(client)\n\nresult = messaging.create_sms(\n message_id = '<MESSAGE_ID>',\n content = '<CONTENT>',\n topics = [], # optional\n users = [], # optional\n targets = [], # optional\n draft = True, # optional\n scheduled_at = '' # optional\n)\n```\n```ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('<PROJECT_ID>') # Your project ID\n .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key\n\nmessaging = Messaging.new(client)\n\nresponse = messaging.create_sms(\n message_id: '<MESSAGE_ID>',\n content: '<CONTENT>',\n topics: [], # optional\n users: [], # optional\n targets: [], # optional\n draft: true, # optional\n scheduled_at: '' # optional\n)\n\nputs response.inspect\n```\n```csharp\nusing Appwrite;\nusing Appwrite.Services;\nusing Appwrite.Models;\n\nvar client = new Client()\n .SetEndPoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .SetProject(\"<PROJECT_ID>\") // Your project ID\n .SetKey(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\nvar messaging = new Messaging(client);\n\nMessage result = await messaging.CreateSMS(\n messageId: \"<MESSAGE_ID>\",\n content: \"<CONTENT>\" \n topics: new List<string> {} // optional \n users: new List<string> {} // optional \n targets: new List<string> {} // optional \n draft: true // optional \n scheduledAt: \"\"); // optional\n```\n```dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\nimport 'package:dart_appwrite/enums.dart';\nimport 'package:dart_appwrite/models.dart';\n\nvoid main() async { // Init SDK\n Client client = Client();\n Messaging messaging = Messaging(client);\n\n client\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n ;\n\n Future result = await messaging.createSms(\n messageId: '<MESSAGE_ID>',\n content: '<CONTENT>',\n topics: [], // optional\n users: [], // optional\n targets: [], // optional\n draft: true, // optional\n scheduledAt: '', // optional\n );\n\n result\n .then((response) {\n print(response);\n }).catchError((error) {\n print(error.response);\n });\n}\n```\n```kotlin\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Messaging;\n\nClient client = new Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\nMessaging messaging = new Messaging(client);\n\nmessaging.createSms(\n \"<MESSAGE_ID>\", // messageId\n \"<CONTENT>\", // content\n listOf(), // topics (optional)\n listOf(), // users (optional)\n listOf(), // targets (optional)\n true, // draft (optional)\n \"\" // scheduledAt (optional)\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(result);\n })\n);\n```\n```java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Messaging;\n\nClient client = new Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\nMessaging messaging = new Messaging(client);\n\nmessaging.createSms(\n \"<MESSAGE_ID>\", // messageId\n \"<CONTENT>\", // content\n listOf(), // topics (optional)\n listOf(), // users (optional)\n listOf(), // targets (optional)\n true, // draft (optional)\n \"\" // scheduledAt (optional)\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(result);\n })\n);\n```\n```swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\") // Your secret API key\n\nlet messaging = Messaging(client)\n\nlet message = try await messaging.createSms(\n messageId: \"<MESSAGE_ID>\",\n content: \"<CONTENT>\",\n topics: [], // optional\n users: [], // optional\n targets: [], // optional\n draft: true, // optional\n scheduledAt: \"\" // optional\n)\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::messaging::Messaging;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n let client = Client::new()\n .set_endpoint(\"https://<REGION>.cloud.appwrite.io/v1\")\n .set_project(\"<PROJECT_ID>\")\n .set_key(\"919c2d18fb5d4...a2ae413da83346ad2\");\n\n let messaging = Messaging::new(&client);\n\n let message = messaging.create_sms(\n \"<MESSAGE_ID>\", // messageId\n \"<CONTENT>\", // content\n Some(vec![]), // topics (optional)\n Some(vec![]), // users (optional)\n Some(vec![]), // targets (optional)\n Some(true), // draft (optional)\n None, // scheduledAt (optional)\n ).await?;\n\n println!(\"{:?}\", message);\n Ok(())\n}\n```\n{% /multicode %}\n{% /tabsitem %}\n{% /tabs %}\n{% /section %}\n\n{% section #manage-provider step=4 title=\"Manage provider\" %}\n{% tabs %}\n{% tabsitem #console title=\"Console\" %}\nYou can update or delete a provider in the Appwrite Console.\n\nNavigate to **Messaging** > **Providers** > click your provider.\nIn the settings, you can update a provider's configuration or delete the provider.\n{% /tabsitem %}\n\n{% tabsitem #server-sdk title=\"Server SDK\" %}\nTo update or delete providers programmatically, use an [Appwrite Server SDK](/docs/sdks#server).\n{% multicode %}\n```server-nodejs\nconst sdk = require('node-appwrite');\n\n// Init SDK\nconst client = new sdk.Client();\n\nconst messaging = new sdk.Messaging(client);\n\nclient\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n;\n\nconst provider = await messaging.updateTwilioProvider(\n '<PROVIDER_ID>', // providerId\n '<NAME>', // name (optional)\n false, // enabled (optional)\n '<ACCOUNT_SID>', // accountSid (optional)\n '<AUTH_TOKEN>', // authToken (optional)\n '<FROM>' // from (optional)\n );\n```\n```deno\nimport * as sdk from \"npm:node-appwrite\";\n\n// Init SDK\nlet client = new sdk.Client();\n\nlet messaging = new sdk.Messaging(client);\n\nclient\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n;\n\nconst provider = await messaging.updateTwilioProvider(\n '<PROVIDER_ID>', // providerId\n '<NAME>', // name (optional)\n false, // enabled (optional)\n '<ACCOUNT_SID>', // accountSid (optional)\n '<AUTH_TOKEN>', // authToken (optional)\n '<FROM>' // from (optional)\n );\n```\n```php\n<?php\n\nuse Appwrite\\Client;\nuse Appwrite\\Services\\Messaging;\n\n$client = new Client();\n\n$client\n ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n ->setProject('<PROJECT_ID>') // Your project ID\n ->setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n;\n\n$messaging = new Messaging($client);\n\n$result = $messaging->updateTwilioProvider(\n providerId: '<PROVIDER_ID>',\n name: '<NAME>', // optional\n enabled: false, // optional\n accountSid: '<ACCOUNT_SID>', // optional\n authToken: '<AUTH_TOKEN>', // optional\n from: '<FROM>' // optional\n);\n```\n```python\nfrom appwrite.client import Client\nfrom appwrite.services.messaging import Messaging\n\nclient = Client()\n\n(client\n .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('<PROJECT_ID>') # Your project ID\n .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key\n)\n\nmessaging = Messaging(client)\n\nresult = messaging.update_twilio_provider(\n provider_id = '<PROVIDER_ID>',\n name = '<NAME>', # optional\n enabled = False, # optional\n account_sid = '<ACCOUNT_SID>', # optional\n auth_token = '<AUTH_TOKEN>', # optional\n from = '<FROM>' # optional\n)\n```\n```ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('<PROJECT_ID>') # Your project ID\n .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key\n\nmessaging = Messaging.new(client)\n\nresponse = messaging.update_twilio_provider(\n provider_id: '<PROVIDER_ID>',\n name: '<NAME>', # optional\n enabled: false, # optional\n account_sid: '<ACCOUNT_SID>', # optional\n auth_token: '<AUTH_TOKEN>', # optional\n from: '<FROM>' # optional\n)\n\nputs response.inspect\n```\n```csharp\nusing Appwrite;\nusing Appwrite.Services;\nusing Appwrite.Models;\n\nvar client = new Client()\n .SetEndPoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .SetProject(\"<PROJECT_ID>\") // Your project ID\n .SetKey(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\nvar messaging = new Messaging(client);\n\nProvider result = await messaging.UpdateTwilioProvider(\n providerId: \"<PROVIDER_ID>\"\n name: \"<NAME>\" // optional\n enabled: false // optional\n accountSid: \"<ACCOUNT_SID>\" // optional\n authToken: \"<AUTH_TOKEN>\" // optional\n from: \"<FROM>\"); // optional\n```\n```dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\nimport 'package:dart_appwrite/enums.dart';\nimport 'package:dart_appwrite/models.dart';\n\nvoid main() { // Init SDK\n Client client = Client();\n Messaging messaging = Messaging(client);\n\n client\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n ;\n\n Future result = messaging.updateTwilioProvider(\n providerId: '<PROVIDER_ID>',\n name: '<NAME>', // optional\n enabled: false, // optional\n accountSid: '<ACCOUNT_SID>', // optional\n authToken: '<AUTH_TOKEN>', // optional\n from: '<FROM>', // optional\n );\n\n result\n .then((response) {\n print(response);\n }).catchError((error) {\n print(error.response);\n });\n}\n```\n```kotlin\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Messaging;\n\nClient client = new Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\nMessaging messaging = new Messaging(client);\n\nmessaging.updateTwilioProvider(\n \"<PROVIDER_ID>\", // providerId\n \"<NAME>\", // name (optional)\n false, // enabled (optional)\n \"<ACCOUNT_SID>\", // accountSid (optional)\n \"<AUTH_TOKEN>\", // authToken (optional)\n \"<FROM>\" // from (optional)\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(result);\n })\n);\n```\n```java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Messaging;\n\nClient client = new Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\nMessaging messaging = new Messaging(client);\n\nmessaging.updateTwilioProvider(\n \"<PROVIDER_ID>\", // providerId\n \"<NAME>\", // name (optional)\n false, // enabled (optional)\n \"<ACCOUNT_SID>\", // accountSid (optional)\n \"<AUTH_TOKEN>\", // authToken (optional)\n \"<FROM>\" // from (optional)\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(result);\n })\n);\n```\n```swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\") // Your secret API key\n\nlet messaging = Messaging(client)\n\nlet provider = try await messaging.updateTwilioProvider(\n providerId: \"<PROVIDER_ID>\",\n name: \"<NAME>\", // optional\n enabled: xfalse, // optional\n accountSid: \"<ACCOUNT_SID>\", // optional\n authToken: \"<AUTH_TOKEN>\", // optional\n from: \"<FROM>\" // optional\n)\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::messaging::Messaging;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n let client = Client::new()\n .set_endpoint(\"https://<REGION>.cloud.appwrite.io/v1\")\n .set_project(\"<PROJECT_ID>\")\n .set_key(\"919c2d18fb5d4...a2ae413da83346ad2\");\n\n let messaging = Messaging::new(&client);\n\n let provider = messaging.update_twilio_provider(\n \"<PROVIDER_ID>\", // providerId\n Some(\"<NAME>\"), // name (optional)\n Some(false), // enabled (optional)\n Some(\"<ACCOUNT_SID>\"), // accountSid (optional)\n Some(\"<AUTH_TOKEN>\"), // authToken (optional)\n Some(\"<FROM>\"), // from (optional)\n ).await?;\n\n println!(\"{:?}\", provider);\n Ok(())\n}\n```\n{% /multicode %}\n{% /tabsitem %}\n{% /tabs %}\n{% /section %}"}, {"path": "docs/products/messaging/vonage", "title": "Vonage", "description": "Send SMS messages to your Appwrite users using Vonage and Appwrite Messaging.", "content": "Vonage lets you send customized SMS messages to your users.\nThese SMS messages can be sent immediately or scheduled.\nYou can send SMS messages for purposes like reminders, promotions, announcements, and even custom authentication flows.\n\n{% section #add-provider step=1 title=\"Add provider\" %}\n\nTo add Vonage as a provider, navigate to **Messaging** > **Providers** > {% icon icon=\"plus\" size=\"m\" /%} **Add provider** > **SMS**.\n{% only_dark %}\n![Add a Vonage provider](/images/docs/messaging/providers/vonage/dark/provider.avif)\n{% /only_dark %}\n{% only_light %}\n![Add a Vonage provider](/images/docs/messaging/providers/vonage/provider.avif)\n{% /only_light %}\n\nGive your provider a name > choose **Vonage** > click **Save and continue**.\nThe provider will be saved to your project, but not enabled until you complete its configuration.\n{% /section %}\n{% section #configure-provider step=2 title=\"Configure provider\" %}\n\nIn the **Configure** step, you will need to provide details from your Vonage dashboard to connect your Appwrite project.\n\nYou will need to provide the following information from your **Vonage dashboard**.\n\n{% table %}\n* Field name\n*\n---\n* API key\n* Head to Vonage dashboard > **Build & manage** > **API settings** and copy the API key.\n---\n* API secret\n* Head to Vonage dashboard > **Build & manage** > **API settings** and copy the API secret.\n---\n* Sender number\n* You can access your numbers by navigating to Vonage dashboard > **Build & manage** > **Numbers** > **Your numbers**.\n---\n{% /table %}\n\nAfter adding the following details, click **Save and continue** to enable the provider.\n{% /section %}\n\n{% section #test-provider step=3 title=\"Test provider\" %}\nBefore sending your first message,\nmake sure you've configured [a topic](/docs/products/messaging/topics) and [a target](/docs/products/messaging/targets) to send messages to.\n{% tabs %}\n{% tabsitem #console title=\"Console\" %}\nTo send a test message, navigate to **Messaging** > **Messages** > {% icon icon=\"plus\" size=\"m\" /%} **Create message** > **SMS**.\n{% only_dark %}\n![Create an SMS message](/images/docs/messaging/messages/dark/create-sms-message.avif)\n{% /only_dark %}\n{% only_light %}\n![Create an SMS message](/images/docs/messaging/messages/create-sms-message.avif)\n{% /only_light %}\n\nAdd your message and in the targets step, select one of your test targets. Set the schedule to **Now** and click **Send**.\n\nVerify that you can receive the message in your inbox. If not, check for logs in the Appwrite Console or in your provider's logs.\n{% /tabsitem %}\n\n{% tabsitem #server-sdk title=\"Server SDK\" %}\nTo send a message programmatically, use an [Appwrite Server SDK](/docs/sdks#server).\n{% multicode %}\n```server-nodejs\nconst sdk = require('node-appwrite');\n\n// Init SDK\nconst client = new sdk.Client();\n\nconst messaging = new sdk.Messaging(client);\n\nclient\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n;\n\nconst message = await messaging.createSms(\n '<MESSAGE_ID>', // messageId\n '<CONTENT>', // content\n [], // topics (optional)\n [], // users (optional)\n [], // targets (optional)\n true, // draft (optional)\n '' // scheduledAt (optional)\n );\n```\n```deno\nimport * as sdk from \"npm:node-appwrite\";\n\n// Init SDK\nlet client = new sdk.Client();\n\nlet messaging = new sdk.Messaging(client);\n\nclient\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n;\n\nconst message = await messaging.createSms(\n '<MESSAGE_ID>', // messageId\n '<CONTENT>', // content\n [], // topics (optional)\n [], // users (optional)\n [], // targets (optional)\n true, // draft (optional)\n '' // scheduledAt (optional)\n );\n```\n```php\n<?php\n\nuse Appwrite\\Client;\nuse Appwrite\\Services\\Messaging;\n\n$client = new Client();\n\n$client\n ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n ->setProject('<PROJECT_ID>') // Your project ID\n ->setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n;\n\n$messaging = new Messaging($client);\n\n$result = $messaging->createSms(\n messageId: '<MESSAGE_ID>',\n content: '<CONTENT>',\n topics: [], // optional\n users: [], // optional\n targets: [], // optional\n draft: true, // optional\n scheduledAt: '' // optional\n);\n```\n```python\nfrom appwrite.client import Client\nfrom appwrite.services.messaging import Messaging\n\nclient = Client()\n\n(client\n .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('<PROJECT_ID>') # Your project ID\n .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key\n)\n\nmessaging = Messaging(client)\n\nresult = messaging.create_sms(\n message_id = '<MESSAGE_ID>',\n content = '<CONTENT>',\n topics = [], # optional\n users = [], # optional\n targets = [], # optional\n draft = True, # optional\n scheduled_at = '' # optional\n)\n```\n```ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('<PROJECT_ID>') # Your project ID\n .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key\n\nmessaging = Messaging.new(client)\n\nresponse = messaging.create_sms(\n message_id: '<MESSAGE_ID>',\n content: '<CONTENT>',\n topics: [], # optional\n users: [], # optional\n targets: [], # optional\n draft: true, # optional\n scheduled_at: '' # optional\n)\n\nputs response.inspect\n```\n```csharp\nusing Appwrite;\nusing Appwrite.Services;\nusing Appwrite.Models;\n\nvar client = new Client()\n .SetEndPoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .SetProject(\"<PROJECT_ID>\") // Your project ID\n .SetKey(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\nvar messaging = new Messaging(client);\n\nMessage result = await messaging.CreateSMS(\n messageId: \"<MESSAGE_ID>\",\n content: \"<CONTENT>\" \n topics: new List<string> {} // optional \n users: new List<string> {} // optional \n targets: new List<string> {} // optional \n draft: true // optional \n scheduledAt: \"\"); // optional\n```\n```dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\nimport 'package:dart_appwrite/enums.dart';\nimport 'package:dart_appwrite/models.dart';\n\nvoid main() { // Init SDK\n Client client = Client();\n Messaging messaging = Messaging(client);\n\n client\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n ;\n\n Future result = messaging.createSms(\n messageId: '<MESSAGE_ID>',\n content: '<CONTENT>',\n topics: [], // optional\n users: [], // optional\n targets: [], // optional\n draft: true, // optional\n scheduledAt: '', // optional\n );\n\n result\n .then((response) {\n print(response);\n }).catchError((error) {\n print(error.response);\n });\n}\n```\n```kotlin\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Messaging;\n\nClient client = new Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\nMessaging messaging = new Messaging(client);\n\nmessaging.createSms(\n \"<MESSAGE_ID>\", // messageId\n \"<CONTENT>\", // content\n listOf(), // topics (optional)\n listOf(), // users (optional)\n listOf(), // targets (optional)\n true, // draft (optional)\n \"\" // scheduledAt (optional)\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(result);\n })\n);\n```\n```java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Messaging;\n\nClient client = new Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\nMessaging messaging = new Messaging(client);\n\nmessaging.createSms(\n \"<MESSAGE_ID>\", // messageId\n \"<CONTENT>\", // content\n listOf(), // topics (optional)\n listOf(), // users (optional)\n listOf(), // targets (optional)\n true, // draft (optional)\n \"\" // scheduledAt (optional)\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(result);\n })\n);\n```\n```swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\") // Your secret API key\n\nlet messaging = Messaging(client)\n\nlet message = try await messaging.createSms(\n messageId: \"<MESSAGE_ID>\",\n content: \"<CONTENT>\",\n topics: [], // optional\n users: [], // optional\n targets: [], // optional\n draft: true, // optional\n scheduledAt: \"\" // optional\n)\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::messaging::Messaging;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n let client = Client::new()\n .set_endpoint(\"https://<REGION>.cloud.appwrite.io/v1\")\n .set_project(\"<PROJECT_ID>\")\n .set_key(\"919c2d18fb5d4...a2ae413da83346ad2\");\n\n let messaging = Messaging::new(&client);\n\n let message = messaging.create_sms(\n \"<MESSAGE_ID>\", // messageId\n \"<CONTENT>\", // content\n Some(vec![]), // topics (optional)\n Some(vec![]), // users (optional)\n Some(vec![]), // targets (optional)\n Some(true), // draft (optional)\n None, // scheduledAt (optional)\n ).await?;\n\n println!(\"{:?}\", message);\n Ok(())\n}\n```\n{% /multicode %}\n{% /tabsitem %}\n{% /tabs %}\n\nYou can follow the [Send SMS messages](/docs/products/messaging/send-sms-messages) journey to send your first push notification and test your provider.\n{% /section %}\n\n{% section #manage-provider step=4 title=\"Manage provider\" %}\n{% tabs %}\n{% tabsitem #console title=\"Console\" %}\nYou can update or delete a provider in the Appwrite Console.\n\nNavigate to **Messaging** > **Providers** > click your provider.\nIn the settings, you can update a provider's configuration or delete the provider.\n{% /tabsitem %}\n\n{% tabsitem #server-sdk title=\"Server SDK\" %}\nTo update or delete providers programmatically, use an [Appwrite Server SDK](/docs/sdks#server).\n{% multicode %}\n```server-nodejs\nconst sdk = require('node-appwrite');\n\n// Init SDK\nconst client = new sdk.Client();\n\nconst messaging = new sdk.Messaging(client);\n\nclient\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n;\n\nconst provider = await messaging.updateVonageProvider(\n '<PROVIDER_ID>', // providerId\n '<NAME>', // name (optional)\n false, // enabled (optional)\n '<API_KEY>', // apiKey (optional)\n '<API_SECRET>', // apiSecret (optional)\n '<FROM>' // from (optional)\n );\n```\n```deno\nimport * as sdk from \"npm:node-appwrite\";\n\n// Init SDK\nlet client = new sdk.Client();\n\nlet messaging = new sdk.Messaging(client);\n\nclient\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n;\n\nconst provider = await messaging.updateVonageProvider(\n '<PROVIDER_ID>', // providerId\n '<NAME>', // name (optional)\n false, // enabled (optional)\n '<API_KEY>', // apiKey (optional)\n '<API_SECRET>', // apiSecret (optional)\n '<FROM>' // from (optional)\n );\n```\n```php\n<?php\n\nuse Appwrite\\Client;\nuse Appwrite\\Services\\Messaging;\n\n$client = new Client();\n\n$client\n ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n ->setProject('<PROJECT_ID>') // Your project ID\n ->setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n;\n\n$messaging = new Messaging($client);\n\n$result = $messaging->updateVonageProvider(\n providerId: '<PROVIDER_ID>',\n name: '<NAME>', // optional\n enabled: false, // optional\n apiKey: '<API_KEY>', // optional\n apiSecret: '<API_SECRET>', // optional\n from: '<FROM>' // optional\n);\n```\n```python\nfrom appwrite.client import Client\nfrom appwrite.services.messaging import Messaging\n\nclient = Client()\n\n(client\n .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('<PROJECT_ID>') # Your project ID\n .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key\n)\n\nmessaging = Messaging(client)\n\nresult = messaging.update_vonage_provider(\n provider_id = '<PROVIDER_ID>',\n name = '<NAME>', # optional\n enabled = False, # optional\n api_key = '<API_KEY>', # optional\n api_secret = '<API_SECRET>', # optional\n from = '<FROM>' # optional\n)\n```\n```ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('<PROJECT_ID>') # Your project ID\n .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key\n\nmessaging = Messaging.new(client)\n\nresponse = messaging.update_vonage_provider(\n provider_id: '<PROVIDER_ID>',\n name: '<NAME>', # optional\n enabled: false, # optional\n api_key: '<API_KEY>', # optional\n api_secret: '<API_SECRET>', # optional\n from: '<FROM>' # optional\n)\n\nputs response.inspect\n```\n```csharp\nusing Appwrite;\nusing Appwrite.Services;\nusing Appwrite.Models;\n\nvar client = new Client()\n .SetEndPoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .SetProject(\"<PROJECT_ID>\") // Your project ID\n .SetKey(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\nvar messaging = new Messaging(client);\n\nProvider result = await messaging.UpdateVonageProvider(\n providerId: \"<PROVIDER_ID>\"\n name: \"<NAME>\" // optional\n enabled: false // optional\n apiKey: \"<API_KEY>\" // optional\n apiSecret: \"<API_SECRET>\" // optional\n from: \"<FROM>\"); // optional\n```\n```dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\nimport 'package:dart_appwrite/enums.dart';\nimport 'package:dart_appwrite/models.dart';\n\nvoid main() { // Init SDK\n Client client = Client();\n Messaging messaging = Messaging(client);\n\n client\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n ;\n\n Future result = messaging.updateVonageProvider(\n providerId: '<PROVIDER_ID>',\n name: '<NAME>', // optional\n enabled: false, // optional\n apiKey: '<API_KEY>', // optional\n apiSecret: '<API_SECRET>', // optional\n from: '<FROM>', // optional\n );\n\n result\n .then((response) {\n print(response);\n }).catchError((error) {\n print(error.response);\n });\n}\n```\n```kotlin\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Messaging;\n\nClient client = new Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\nMessaging messaging = new Messaging(client);\n\nmessaging.updateVonageProvider(\n \"<PROVIDER_ID>\", // providerId\n \"<NAME>\", // name (optional)\n false, // enabled (optional)\n \"<API_KEY>\", // apiKey (optional)\n \"<API_SECRET>\", // apiSecret (optional)\n \"<FROM>\" // from (optional)\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(result);\n })\n);\n```\n```java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Messaging;\n\nClient client = new Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\nMessaging messaging = new Messaging(client);\n\nmessaging.updateVonageProvider(\n \"<PROVIDER_ID>\", // providerId\n \"<NAME>\", // name (optional)\n false, // enabled (optional)\n \"<API_KEY>\", // apiKey (optional)\n \"<API_SECRET>\", // apiSecret (optional)\n \"<FROM>\" // from (optional)\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(result);\n })\n);\n```\n```swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\") // Your secret API key\n\nlet messaging = Messaging(client)\n\nlet provider = try await messaging.updateVonageProvider(\n providerId: \"<PROVIDER_ID>\",\n name: \"<NAME>\", // optional\n enabled: xfalse, // optional\n apiKey: \"<API_KEY>\", // optional\n apiSecret: \"<API_SECRET>\", // optional\n from: \"<FROM>\" // optional\n)\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::messaging::Messaging;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n let client = Client::new()\n .set_endpoint(\"https://<REGION>.cloud.appwrite.io/v1\")\n .set_project(\"<PROJECT_ID>\")\n .set_key(\"919c2d18fb5d4...a2ae413da83346ad2\");\n\n let messaging = Messaging::new(&client);\n\n let provider = messaging.update_vonage_provider(\n \"<PROVIDER_ID>\", // providerId\n Some(\"<NAME>\"), // name (optional)\n Some(false), // enabled (optional)\n Some(\"<API_KEY>\"), // apiKey (optional)\n Some(\"<API_SECRET>\"), // apiSecret (optional)\n Some(\"<FROM>\"), // from (optional)\n ).await?;\n\n println!(\"{:?}\", provider);\n Ok(())\n}\n```\n{% /multicode %}\n{% /tabsitem %}\n{% /tabs %}\n{% /section %}"}, {"path": "docs/products/network", "title": "Network", "description": "Discover Appwrite's network architecture with global regions, edge nodes, and optimized routing. Explore how it ensures low latency, reliable performance, and scalable infrastructure for modern applications.", "content": "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.\n\nWith 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.\n\n{% only_dark %}\n![Network map](/images/docs/network/dark/all-maps.avif)\n{% /only_dark %}\n{% only_light %}\n![Network map](/images/docs/network/all-maps.avif)\n{% /only_light %}\n\n# Components {% #components %}\nThe Appwrite Network is composed of multiple components that work together to deliver a seamless experience for developers and end-users. These components include:\n\n{% cards %}\n{% cards_item href=\"/docs/products/network/regions\" title=\"Regions\" %}\nMulti-region deployments for geo-redundancy, compliance and low-latency.\n{% /cards_item %}\n{% cards_item href=\"/docs/products/network/edges\" title=\"Edges\" %}\nEdge nodes for fast request processing and reduced round-trip times.\n{% /cards_item %}\n{% cards_item href=\"/docs/products/network/cdn\" title=\"CDN\" %}\nGlobal infrastructure for optimized routing, enabling faster and more consistent data delivery.\n{% /cards_item %}\n{% cards_item href=\"/docs/products/network/endpoints\" title=\"Endpoints\" %}\nDedicated endpoints for region-specific, edge, and compute workloads.\n{% /cards_item %}\n{% /cards %}\n\n## Region vs Edge {% #region-vs-edge %}\n\nIn 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.\n\nEdges 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.\n\n{% info title=\"Where to execute?\" %}\nThe 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.\n{% /info %}\n\n# Architecture {% #architecture %}\n\nAppwrite's network is designed to provide a balance between centralized compute and distributed delivery:\n- 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.\n- 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.\n- Private Routing: Data moves between edge nodes and regions through optimized, low-latency connections to ensure fast and reliable communication.\n\nThis 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.\n\n{% info title=\"Self-Hosting?\" %}\nAppwrite's self-hosted setup is designed and optimized for straightforward, single-region environments - making it an ideal choice for small to medium-scale workloads.\nAll 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).\n{% /info %}\n\n# Features {% #features %}\n\n\n{% cards %}\n{% cards_item href=\"/docs/products/network/dns\" title=\"Domain Name System\" %}\nProvides dedicated nameservers and DNS management for apex domains with SSL certification.\n{% /cards_item %}\n{% cards_item href=\"/docs/products/network/ddos\" title=\"DDoS mitigation\" %}\nProtects against distributed denial-of-service attacks, ensuring uninterrupted access.\n{% /cards_item %}\n{% cards_item href=\"/docs/products/network/tls\" title=\"Transport Layer Security (TLS)\" %}\nEncrypts data in transit for secure and private communication.\n{% /cards_item %}\n{% cards_item href=\"/docs/products/network/waf\" title=\"Web Application Firewall (WAF)\" %}\nShields applications from common web vulnerabilities and attacks on the application layer.\n{% /cards_item %}\n{% cards_item href=\"/docs/products/network/compression\" title=\"Compression\" %}\nReduces data size in transit to improve transfer speed and efficiency.\n{% /cards_item %}\n{% cards_item href=\"/docs/products/network/caching\" title=\"Caching\" %}\nStores frequently accessed data for faster retrieval and lower latency.\n{% /cards_item %}\n{% /cards %}"}, {"path": "docs/products/network/caa-records", "title": "Certification Authority Authorization (CAA) records", "description": "Learn what DNS Certification Authority Authorization (CAA) records are, when they are required to use a custom domain with Appwrite, and how to configure one or more of them at your DNS provider.", "content": "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).\n\nWhen 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.\n\n{% info title=\"CAA records are additive, not exclusive\" %}\nAdding 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).\n{% /info %}\n\n# Certificate authority used by Appwrite {% #certificate-authority %}\n\nAppwrite 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`.\n\nFor more on how Appwrite manages certificates, see the [TLS documentation](/docs/advanced/security/tls).\n\n# Do you need a CAA record? {% #do-you-need-one %}\n\nBy 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.\n\nYou **do** need to add a CAA record that authorizes Appwrite's CA in either of these situations.\n\n- 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.\n- Your DNS provider, registrar, or organization adds CAA records automatically for new domains.\n\nIf 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.\n\n{% info title=\"What the Appwrite Console shows\" %}\nWhen 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.\n{% /info %}\n\n## Apex domains and subdomains {% #scope %}\n\nCAA records are scoped to where they sit in DNS, so adding one for Appwrite does not have to touch your main domain.\n\n- 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.\n- 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.\n\nIf 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.\n\n1. 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.\n2. Add Appwrite's CAA record at the apex alongside your existing CAA records. Both CAs remain authorized everywhere. See [Setting multiple CAA records](#multiple).\n\n# How to add a CAA record {% #add %}\n\nThe exact UI differs between DNS providers, but the values are the same.\n\n1. Open your DNS provider's DNS management dashboard.\n2. Create a new record and select **CAA** as the record type.\n3. 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.\n4. Set the **flags** to `0`.\n5. Set the **tag** to `issue`.\n6. Set the **value** to the CA shown in the Appwrite Console (`certainly.com` for Appwrite Cloud).\n7. Save the record and wait for DNS propagation. This can take up to 48 hours.\n\nA complete CAA record in zone file format looks like this.\n\n```text\nexample.com. IN CAA 0 issue \"certainly.com\"\n```\n\n# Setting multiple CAA records {% #multiple %}\n\nCAA 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.\n\nFor example, to allow both Appwrite Cloud's CA and Let's Encrypt on the same domain, add two records.\n\n```text\nexample.com. IN CAA 0 issue \"certainly.com\"\nexample.com. IN CAA 0 issue \"letsencrypt.org\"\n```\n\nMost 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.\n\n{% info title=\"Using Appwrite DNS as your nameserver?\" %}\nIf 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.\n{% /info %}\n\n## Wildcard certificates {% #wildcard %}\n\nThe `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.\n\nIf you have set restrictive `issuewild` records for other CAs, add an `issuewild` record for Appwrite's CA as well.\n\n```text\nexample.com. IN CAA 0 issuewild \"certainly.com\"\n```\n\n## Reporting violations {% #iodef %}\n\nThe `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.\n\n```text\nexample.com. IN CAA 0 iodef \"mailto:security@example.com\"\n```\n\n# Troubleshooting {% #troubleshooting %}\n\nIf a custom domain stays unverified or its certificate fails to issue, check the following.\n\n- Confirm the CAA record uses the exact value shown in the Appwrite Console, with no trailing spaces, quotes, or `https://` prefix.\n- 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.\n- Wait for DNS propagation. CAA changes can take up to 48 hours to be visible to the CA.\n- Check the current state of your CAA records with [DNS Checker](https://dnschecker.org/) or `dig CAA example.com`.\n\nIf issues persist, [contact us](/contact-us) and we will help debug your DNS setup."}, {"path": "docs/products/network/caching", "title": "Caching", "description": "Learn how Appwrite uses smart caching strategies at the region, edge, and CDN levels to optimize performance and protect dynamic APIs, with advanced options for enterprise customers.", "content": "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.\n\n# Region-level {% #region-level %}\n\nAt the region level, Appwrite provides smart in-memory caching for various resources: \n\n- **Documents**: Frequently accessed rows are cached in memory and automatically purged when updated, ensuring data consistency without manual intervention. \n- **Storage files**: Frequently accessed files are cached in memory to reduce disk reads and improve performance. \n- **Image transformations**: Processed images (e.g., resized or converted) are cached in memory for faster repeated requests, reducing processing overhead. \n\nRegion-level caching is tightly integrated with Appwrite's APIs, optimizing performance while preserving data integrity.\n\n# Edge-level {% #edge-level %}\n\nAt the edge, Appwrite employs smart caching for specific use cases: \n\n- **Compute builds**: Caches build artifacts for faster deployments and reduced latency during function executions. \n- **Cold starts**: Pre-loads frequently accessed resources, reducing latency for new requests and improving application responsiveness. \n\nEdge-level caching complements region-level caching, ensuring optimal performance for globally distributed applications.\n\n# Private caching {% #cdn-caching %}\nAppwrite'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.\n\n**What is private caching?** \n\nIn 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: \n\n- `Cache-Control: private, max-age=3600` \n Indicates that the response can be cached, but only in a private cache (e.g., the user's browser). \n\n- `Cache-Control: no-store` \n Ensures that no part of the response is cached, useful for highly sensitive or frequently changing data. \n\n- `Vary: Authorization` \n Signals that the cached response varies based on the `Authorization` header, ensuring permission-specific responses are cached and served appropriately.\n\n**Why use private caching?**\n\nAppwrite's APIs often deliver personalized or restricted content based on user roles and permissions. Private caching ensures: \n\n- **Security**: Sensitive resources are securely cached and only served to the correct user. \n- **Permission awareness**: API responses are tailored to each user's permissions, ensuring consistent behavior. \n- **Performance**: By caching user-specific responses, private caching reduces backend load while maintaining secure and accurate data delivery. \n\nThis approach prevents the accidental exposure of user-specific or restricted data through shared caches while still enabling performance optimizations where possible.\n\n# Caching rules {% #custom-caching %}\n\nEnterprise customers can collaborate with their Appwrite success manager to define custom caching rules tailored to their applications. This includes: \n- Setting custom caching durations for specific resources. \n- Defining exclusion rules for sensitive or frequently changing data. \n- Optimizing cache invalidation strategies for complex workflows.\n\nFor more information on upgrading to the enterprise plan, [contact sales](https://appwrite.io/contact-us/enterprise)."}, {"path": "docs/products/network/cdn", "title": "Content Delivery Network (CDN)", "description": "Learn about Appwrite's CDN, designed to optimize content delivery with compression, and edge optimization for improved performance and reduced latency.", "content": "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.\n\n{% only_dark %}\n![PoPs map](/images/docs/network/dark/pops-map.avif)\n{% /only_dark %}\n{% only_light %}\n![PoPs map](/images/docs/network/pops-map.avif)\n{% /only_light %}\n\n# Key features {% #key-features %}\n\n- Global coverage: Fast access to content for users across continents through over 120 PoPs worldwide. Available on all projects.\n- Reduced latency: By caching static content at edge nodes, the CDN minimizes the distance between the user and the requested data, significantly reducing latency.\n- Dynamic content: The CDN supports both static and dynamic content delivery, seamlessly integrating with backend services hosted in Appwrite regions.\n- Content optimization: Appwrite's CDN uses advanced compression algorithms to reduce data transfer sizes, further improving delivery times.\n- High availability: Distributed edge nodes and redundant routing ensure that content remains accessible even during regional outages or high traffic loads.\n\n{% info title=\"Self-Hosting?\" %}\nAppwrite'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.\n{% /info %}\n\n# Design {% #design %}\n\n- Caching strategy: Configurable cache policies for control over TTL and content invalidation.\n- Secure delivery: All content is transmitted over TLS for secure, encrypted connections.\n- Integration: The CDN works seamlessly with the Appwrite edges and backend regions, providing a unified experience for developers.\n\nBy combining global caching, smart routing, and content optimization, the Appwrite CDN is built to handle the demands of modern, high-performance applications."}, {"path": "docs/products/network/compression", "title": "Compression", "description": "None", "content": "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.\n\n# API {% #api %}\n\nAppwrite 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.\n\n- **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.\n- **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.\n- **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.\n\n## Conditions {% #conditions %}\nCompression 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.\n\n1. **MIME types**: Only text-based MIME types are eligible for compression. These include:\n- `text/plain`\n- `text/css`\n- `text/javascript`\n- `application/javascript`\n- `text/html`\n- `application/json`\n- `image/svg+xml`\n- `application/xml+rss`\n \nThis selection is based on the nature of these content types being easily compressible, resulting in significant size reductions without loss of information.\n\n2. **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.\n\n3. **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:\n - `br`: Indicates support for Brotli compression.\n - `zstd`: Indicates support for Zstandard compression.\n - `gzip`: Indicates support for Gzip compression.\n - `identity`: Indicates that no compression is supported or requested.\n\n## Prioritizations {% #prioritizations %}\nAppwrite 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.\n\n| Algorithm {% width=120 %} | Ratio {% width=120 %} | Browsers | Notes |\n|-----------|-------------------|-----------------------|---------------------------------------------------|\n| Brotli | High | All modern browsers | Optimal for small text files; highly efficient. |\n| Gzip | Medium | Universal | Broad compatibility with older and modern clients.|\n| Zstd | Very High | Limited | High performance with faster decompression speeds, ideal for server-to-server communication. |\n| Identity | None | Universal | Used when no compression is applied or supported. |\n\n## Enabling compression {% #enabling-compression-1 %}\nCompression 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.\n\n# Image transformations {% #image-transformations %}\nAppwrite'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.\n\nAppwrite supports both legacy and modern image formats, including:\n\n- **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.\n- **WebP**: A modern format developed by Google, offering better compression rates than JPEG, PNG, or GIF while maintaining equivalent quality.\n- **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.\n\n## Supported API endpoints {% #supported-api-endpoints %}\n\nAppwrite 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.\n\n## Prioritization {% #prioritization %}\n\nAppwrite 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.\n\nAppwrite 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.\n\n```javascript\nimport { Client, Storage } from \"appwrite\";\n\nconst client = new Client();\nconst storage = new Storage(client);\n\nclient\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n;\n\nconst result = storage.getFilePreview(\n 'photos', // bucket ID\n 'sunset.png', // file ID\n 1800, // width, will be resized using this value.\n 0, // height, ignored when 0\n 'center', // crop center\n '90', // slight compression\n 5, // border width\n 'CDCA30', // border color\n 15, // border radius\n 1, // full opacity\n 0, // no rotation\n 'FFFFFF', // background color\n 'webp' // output jpg format\n);\n\nconsole.log(result.href);\n```\n\n| Algorithm {% width=120 %} | Formats | Ratio {% width=120 %} | Notes |\n| --------- | ----------------- | ----------------- | -------------------- |\n| WebP | PNG, JPEG, GIF | High | Great balance of compression efficiency and visual quality |\n| AVIF | PNG, JPEG, GIF | Best | Highest compression rate for modern use cases |\n| JPEG | JPEG | Medium | Legacy support for lossy compression |\n| PNG | PNG | Lossless | Necessary for lossless compression requirements |\n\n## Enabling compression {% #enabling-compression-2 %}\nYou 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."}, {"path": "docs/products/network/custom-domains", "title": "Custom domains", "description": "Customize your Appwrite platform with custom domains. Learn how to set up and configure custom domains to provide a branded experience for your users.", "content": "Appwrite custom domains allows you to use your own domain as your Appwrite API endpoint. \n\n# Third-party cookies {% #third-party-cookies %}\nA recent change made in modern browsers will not allow your web app to use 3rd-party cookies. \nThis change is done to protect your users' privacy from malicious web tracking services.\n\nWhen accessing Appwrite from a 3rd party domain, like `cloud.appwrite.io` or `example.com`, \nsome browsers will treat our secure cookies as 3rd-party cookies and block them, \nas a fallback Appwrite will store your users' sessions on the browser localStorage.\n\nUsing localStorage is very convenient to help you get started quickly with Appwrite, but it is not the best practice for your users' security. \nThe browser localStorage can't protect your users' sessions from being hijacked by a 3rd party script or an XSS vulnerability in your web app.\n\n# Appwrite API endpoint {% #endpoint %}\nTo prevent your browser from blocking your cookies, your Appwrite API endpoint should be set to under same domain of your web app's domain.\nWhen accessing Appwrite from the same domain as the one your app uses, \nAppwrite cookies will no longer be treated as 3rd-party cookies by any browser and will store your users' sessions securely.\n\nFor example, if your app runs on [my-app.com](https://my-app.com), \nyou can set the subdomain [appwrite.my-app.com](https://appwrite.my-app.com) to access the Appwrite API. \nThis will allow browsers to respect the Appwrite sessions cookies as they are set on the same domain as your app.\n\n# Add a custom domain {% #domain %}\n\n1. Go to the Appwrite Console and navigate to your project.\n2. Click on the **Settings** tab in the left sidebar.\n3. Select the **Custom domains** section and click **Create domain**.\n4. Add your domain, and copy associated CNAME record to your DNS provider. See the [Add a CNAME record](#cname-record) section.\n5. 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).\n6. 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.\n7. Once you verify your domain, you can generate an SSL certificate.\n\nWith these steps, your Appwrite project will accept API requests from your custom domain.\n\nIf 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.\n\n# Add a CNAME record {% #cname-record %}\n\nA [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.\n\nEvery 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.\n\nBelow, 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.\n\n| Provider | Documentation |\n| --------------- | -------- |\n| IONOS | [Settings](https://www.ionos.com/help/domains/dns-settings/) |\n| 101domain | [Settings](https://help.101domain.com/domain-management/name-servers-dns/modifying-name-servers-and-records/managing-name-server-records) |\n| 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/) |\n| AWS Route 53 | [Settings](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/resource-record-sets-creating.html) |\n| Alfahosting | [Settings](https://alfahosting.de/antworten-auf-ihre-fragen/?cid=78#faqContent) |\n| Binero | [Settings](https://docs.binero.com/dns.html) |\n| 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) |\n| ClouDNS | [A Record](https://www.cloudns.net/wiki/article/10/) / [CNAME Record](https://www.cloudns.net/wiki/article/13/) |\n| Cloudflare | [Settings](https://support.cloudflare.com/hc/en-us/articles/360019093151) |\n| Crazydomains | [Settings](https://www.crazydomains.com.au/help/manage-dns-records-in-cpanel/) |\n| 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) |\n| DNSimple | [A Record](https://support.dnsimple.com/articles/manage-a-record/) / [CNAME Record](https://support.dnsimple.com/articles/manage-cname-record/) |\n| 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) |\n| 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) |\n| Freeparking | [Settings](https://www.freeparking.co.nz/help/manage-dns-records-in-freeparking-dashboard) |\n| Gandi | [A Record](https://wiki.gandi.net/en/dns/zone/a-record) / [CNAME Record](https://wiki.gandi.net/en/dns/zone/cname-record) |\n| 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) |\n| 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) |\n| Host Europe | [Settings](https://www.hosteurope.de/faq/domains/verwaltung/nameserver-eintraege/) |\n| Hover | [Settings](https://help.hover.com/hc/en-us/articles/217282457-Managing-DNS-records) |\n| Hostinger | [Settings](https://support.hostinger.com/en/articles/1583249-how-to-manage-dns-records-at-hostinger) |\n| Infomaniak | [Settings](https://www.infomaniak.com/en/support/faq/2000/change-dns-zone-simple-mode) |\n| 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/) |\n| Internet.bs | [Settings](https://faq.internetbs.net/hc/en-gb/sections/360004926357-DNS-Nameservers) |\n| LeaseWeb | [Settings](https://kb.leaseweb.com/products/hosting/domain-name) |\n| LCN.com | [Settings](https://www.lcn.com/support/articles/how-to-manage-dns-settings-in-cpanel/) |\n| Loopia | [Settings](https://support.loopia.com/wiki/dns-editor-a-and-cname-2/) |\n| Media Temple | [Settings](https://mediatemple.zendesk.com/hc/en-us/articles/204403794-How-can-I-change-the-DNS-records-for-my-domain) |\n| 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) |\n| Namesilo | [A Record](https://www.namesilo.com/Support/DNS-Manager) / [CNAME Record](https://www.namesilo.com/Support/DNS-Manager) |\n| 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) |\n| One.com | [Settings](https://help.one.com/hc/en-us/articles/115005595925-Manage-your-DNS-settings) |\n| OVH | [Settings](https://help.ovhcloud.com/csm/en-dns-edit-dns-zone?id=kb_article_view&sysparm_article=KB0051682) |\n| 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) |\n| Register.it | [Settings](https://www.register.it/assistenza/cambiare-dns/) |\n| SiteGround | [Settings](https://www.siteground.com/kb/manage-dns-records/) |\n| United Domains | [A Record](https://www.uniteddomains.com/faq/question/11) / [CNAME Record](https://www.uniteddomains.com/faq/question/14)|\n| Vercel | [Settings](https://vercel.com/docs/custom-domains) |\n| Wix | [Settings](https://support.wix.com/en/article/connecting-a-wix-domain-to-an-external-site) |\n| 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) |\n\nDNS 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.\n\nIn 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."}, {"path": "docs/products/network/ddos", "title": "DDoS mitigation", "description": "Learn how Appwrite protects your applications from Distributed Denial-of-Service (DDoS) attacks with built-in, always-on protection for all Appwrite Cloud plans.", "content": "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.\n\nAppwrite'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: \n\n- Identifies and blocks large-scale attack patterns in real-time. \n- Ensures legitimate traffic continues to flow uninterrupted. \n- Prevents application downtime and minimizes performance impacts.\n\n# Design {% #design %}\n\nAppwrite's DDoS protection operates across multiple OSI layers to provide comprehensive coverage: \n\n- **Network Layer (Layer 3)**: Detects and mitigates large-scale attacks such as ICMP floods and UDP amplification. \n- **Transport Layer (Layer 4)**: Protects against attacks like SYN floods and TCP-based exploits by identifying anomalous traffic patterns. \n- **Application Layer (Layer 7)**: Blocks high-level attacks, such as HTTP floods, by filtering malicious requests while allowing legitimate user traffic. \n\n# Benefits {% #benefits %}\n\n- **Cost control**: Malicious traffic blocked by DDoS protection does not count towards your bandwidth or request usage, saving you from unnecessary charges. \n- **Reliability**: Keeps your application online and responsive, even during attempted attacks. \n- **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.\n\n# Availability {% #availability %}\n\nDDoS 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: \n\nThis protection is integrated directly into Appwrite's console, edge and region infrastructure, providing seamless coverage without requiring additional setup from developers."}, {"path": "docs/products/network/dns", "title": "Appwrite DNS service", "description": "Learn about Appwrite's DNS service and how to configure domain records for your applications", "content": "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.\n\nThe 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.\n\n# Benefits {% #benefits %}\n\n- **Support for apex domains**: Use root domains like `example.com` directly without workarounds\n- **Automatic SSL certificate management**: All domains get valid SSL certificates automatically\n- **Integrated management**: Simplified configuration for Sites, Functions, and APIs\n- **High availability**: Built on reliable infrastructure with global distribution\n\n# Managing DNS records {% #managing-dns-records %}\n\nYou can manage DNS records for your domains in two places within the Appwrite Console:\n\n## Organization-level DNS management\n\nTo manage DNS records at the organization level:\n\n1. Navigate to your organization in the Appwrite Console\n2. Select the **Domains** tab\n3. Here you can view and manage all your DNS records across different projects\n\nThis 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`.\n\n## Service-specific domain management\n\nYou can also manage domains directly from specific services:\n\n- **Sites**: Navigate to a site and select the **Domains** tab\n- **Functions**: Navigate to a function and select the **Domains** tab\n- **API Endpoints**: Configure custom domains in your project settings under **Settings** > **Custom domains**\n\n# Record types {% #record-types %}\n\nAppwrite DNS supports various DNS record types to meet your domain configuration needs:\n\n| Record Type | Description |\n|-------------|-------------|\n| A | Maps a domain to an IPv4 address |\n| AAAA | Maps a domain to an IPv6 address |\n| CNAME | Maps a domain to another domain (alias) |\n| MX | Specifies mail servers for the domain |\n| TXT | Stores text information (often used for verification) |\n| NS | Specifies the nameservers for the domain |\n| SRV | Specifies services available for a domain (used for Voice over IP, instant messaging, etc.) |\n| [CAA](/docs/products/network/caa-records) | Specifies which certificate authorities (CAs) are authorized to issue certificates for a domain |\n| HTTPS | Provides configuration for HTTPS connections |\n| ALIAS | Similar to CNAME but can be used at the zone apex |\n\n# Setting up apex domains {% #setting-up-apex-domains %}\n\nApex 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:\n\n## Using NS records {% #using-ns-records %}\n\nThis method delegates DNS management to Appwrite by changing your domain's nameservers:\n\n1. Navigate to your domain registrar's DNS settings\n2. Find the NS (nameserver) record settings\n3. Replace the existing nameservers with `ns1.appwrite.zone` and `ns2.appwrite.zone`\n4. Wait for DNS propagation (may take up to 48 hours)\n5. Return to Appwrite Console to verify and configure domain settings\n\n{% info title=\"DNS delegation\" %}\nWhen 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.\n{% /info %}\n\n## Using CNAME flattening {% #using-cname-flattening %}\n\nSome 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.\n\nDepending 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.\n\nTo use this method:\n\n1. In the Appwrite Console, add your apex domain and copy the provided CNAME and [CAA](/docs/products/network/caa-records) records\n2. In your DNS provider's settings, create an ALIAS, ANAME, or CNAME record at the apex (depending on what your provider supports)\n3. Point the record to the Appwrite hostname provided in the console\n4. 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\n5. Wait for DNS propagation (may take up to 48 hours)\n6. Return to Appwrite Console to verify the domain\n\nThis method allows you to maintain control over your remaining DNS configuration while still using your apex domain with Appwrite.\n\n{% info title=\"Existing CAA records?\" %}\nIf 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).\n{% /info %}\n\n# Adding records in Appwrite {% #adding-records %}\n\nIf you delegated your domain to Appwrite's DNS servers using NS records, you can add and manage records:\n\n1. Navigate to your organization's **Domains** tab in the Appwrite Console\n2. Find your domain and click **Manage Records**\n3. Click **Add Record** and select the record type\n4. Fill in the required information based on the record type\n5. Save the record\n\n{% info title=\"Record propagation\" %}\nNew 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.\n{% /info %}"}, {"path": "docs/products/network/edges", "title": "Edges", "description": "Learn about Appwrite edges, where lightweight compute tasks like caching, request routing, and content delivery are handled. Understand how edges enhance performance by bringing operations closer to end-users.", "content": "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.\n\n{% only_dark %}\n![Edges map](/images/docs/network/dark/edges-map.avif)\n{% /only_dark %}\n{% only_light %}\n![Edges map](/images/docs/network/edges-map.avif)\n{% /only_light %}\n\n{% info title=\"We're expanding!\" %}\nCurrently, 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.\n{% /info %}\n\n# List {% #list %}\n\nAppwrite 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:\n\n| Location | Code{% width=120 %} | Status {% width=120 %} |\n|---------------------|--------|-----------------|\n| Frankfurt | FRA | Available |\n| New York | NYC | Available |\n| Sydney | SYD | Available |\n| San Francisco | SFO | Available |\n| Singapore | SGP | Available |\n| Toronto | TOR | Available |\n| Bangalore | BLR | 2025 |\n| Amsterdam | AMS | 2025 |\n| London | LON | 2025 |\n\nEdges complement Appwrite regions, working together to provide fast, reliable, and scalable application performance.\n\n# Routing {% #routing %}\n\nAppwrite'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.\n\nGeo-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.\n\n{% info title=\"Edge vs Region\" %}\nUse 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.\n{% /info %}\n\n# Design {% #design %}\n\nEdges are optimized to reduce latency and improve user experience by processing operations closer to the source of requests. Key aspects include:\n\n- Geo-routing: Smart routing ensures user requests are served by the optimal edge node.\n- Caching: Frequently accessed data is cached at edges to reduce round-trips to core regions.\n- Lightweight compute: Handles quick, resource-efficient computations for real-time tasks.\n- Optimization: The edge network planned locations are strategically designed with fewer, high-capacity edges to maximize cache efficiency and achieve higher cache-hit ratios.\n\nEdges 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.\n\n{% info title=\"Self-Hosting?\" %}\nAppwrite'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.\n{% /info %}"}, {"path": "docs/products/network/endpoints", "title": "Endpoints", "description": "Understand the differences between Appwrite's endpoints, including geo-balanced edges, region-specific services, and custom domains for compute processes.", "content": "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.\n\n# Edge {% #edge %}\n\nThe **`appwrite.network`** domain provides geo-balanced endpoints that route traffic to the nearest edge node based on the user's geographic location.\n\n\nThe edge network endpoints are designed for:\n- **Latency-sensitive operations**: Quickly serving cached content, routing requests, or performing lightweight edge computations.\n- **Global traffic distribution**: Automatically balancing traffic across the edge network for consistent performance.\n\nExample:\n- `https://<ID>.appwrite.network`\n\nUse this endpoint when optimizing for low-latency and global availability is critical for your functions.\n\n# Region {% #region %}\n\nThe **`<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:\n\n- **Data-intensive operations**: Frequent access to databases, storage, authentication, and other region-hosted services.\n- **Regulatory compliance**: Ensuring data residency requirements are met by targeting specific regions.\n\nExample:\n- `https://fra.cloud.appwrite.io`\n- `https://nyc.cloud.appwrite.io`\n\nUse 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.\n\n# Compute {% #compute %}\n\nThe **`<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:\n\n- **Compute-Intensive Tasks**: Executing server-side functions, handling APIs, or processing asynchronous jobs.\n- **Custom Domain Support**: Enabling custom domains for specific function endpoints.\n\nExamples:\n- `https://fra.appwrite.run`\n- `https://[custom-domain]`\n\nUse this endpoint for scenarios where compute needs to happen close to your data or for deploying APIs under your own domain.\n\n# Summary {% #summary %}\n\n| Endpoint | Use Case |\n|--------------------------------------|------------------------------------------------------|\n| `https://<ID>.appwrite.network` | Geo-balanced edges for low-latency operations |\n| `https://<REGION>.cloud.appwrite.io` | Direct access to region services |\n| `https://<ID>.<REGION>.appwrite.run` | Region-based compute and function execution |\n\nKnowing how these endpoints work helps you choose the right one for your needs, ensuring better performance and alignment with your application's compliance requirements."}, {"path": "docs/products/network/regions", "title": "Regions", "description": "Learn about Appwrite regions, where core services like databases, auth, functions, sites, and storage are hosted. Understand data sovereignty, fault isolation, and scalability for compliant, high-performance deployments", "content": "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.\n\n{% only_dark %}\n![Regions map](/images/docs/network/dark/regions-map.avif)\n{% /only_dark %}\n{% only_light %}\n![Regions map](/images/docs/network/regions-map.avif)\n{% /only_light %}\n\n# List {% #list %}\n\nAppwrite is currently available in the following list of regions:\n\n| Region {% width=120 %} | Code{% width=120 %} | Endpoint | Status{% width=120 %} |\n|---------------------|--------|-----------------------------------------|-----------------|\n| Frankfurt | FRA | `https://fra.cloud.appwrite.io/v1` | Available |\n| New York | NYC | `https://nyc.cloud.appwrite.io/v1` | Available |\n| Sydney | SYD | `https://syd.cloud.appwrite.io/v1` | Available |\n| San Francisco | SFO | `https://sfo.cloud.appwrite.io/v1` | Available |\n| Singapore | SGP | `https://sgp.cloud.appwrite.io/v1` | Available |\n| Toronto | TOR | `https://tor.cloud.appwrite.io/v1` | Available |\n| Bangalore | BLR | `coming soon` | TBD |\n| Amsterdam | AMS | `coming soon` | TBD |\n| London | LON | `coming soon` | TBD |\n\nRegions 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.\n\n# Choosing a region {% #choosing-a-region %}\n\nSelecting 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.\n\n{% info title=\"Region vs Edge\" %}\nUse 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.\n{% /info %}\n\n\n# Design {% #design %}\n\nRegions are isolated environments designed for predictable performance and data security. Key aspects include:\n\n- Data storage: All data remains within the region and adheres to local data residency laws.\n- Fault isolation: Each region is self-contained, so failures in one region do not impact others.\n- Scalability: Resources within a region scale dynamically to meet application demands.\n- Networking: Regions connect via secure, low-latency private networks.\n- High availability: Redundant power, networking, and hardware configurations in data centers.\n- Cache efficiency: Fewer, data-dense regions boost hit probability, ensuring popular content is readily available.\n\nRegions provide the foundation for running scalable, reliable applications with full control over data locality and compliance."}, {"path": "docs/products/network/tls", "title": "Transport Layer Security (TLS)", "description": "Learn how Appwrite uses TLS to encrypt data in transit, ensuring secure and private communication between clients and servers.", "content": "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.\n\nTLS 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: \n\n- **Encryption**: Data is encrypted to prevent unauthorized access during transmission. \n- **Integrity**: Ensures that data cannot be tampered with or altered. \n- **Authentication**: Verifies the identity of the server to protect against impersonation or spoofing. \n\n# Key features {% #key-features %}\n\n1. **Modern protocols** \n Appwrite supports TLS 1.2 and TLS 1.3, offering the latest in encryption standards and performance optimization.\n\n2. **Automatic certificates** \n TLS certificates are automatically managed and renewed, ensuring that your applications always run on secure connections without manual intervention. \n\n3. **Strong ciphers** \n Only strong, industry-standard cipher suites are used to ensure robust encryption. \n\n4. **End-to-end security** \n TLS secures every connection in Appwrite's network, including communication between edge nodes and regions, protecting your data at every step.\n\n# Getting started {% #getting-started %}\n\nTLS 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."}, {"path": "docs/products/network/waf", "title": "Web application firewall (WAF)", "description": "Appwrite's Web Application Firewall (WAF) provides enterprise-grade protection against web vulnerabilities like SQL injection, XSS, and DDoS attacks.", "content": "The Web Application Firewall (WAF) is a critical feature of the Appwrite Network, designed to protect applications from common web vulnerabilities and attacks. Available exclusively to enterprise customers, WAF can be configured through your Appwrite success manager to meet the specific security needs of your application.\n\n{% info title=\"Availability\" %}\nThe WAF feature is available exclusively to enterprise customers as part of the Appwrite enterprise offering. Setup and configuration are managed through your dedicated Appwrite success manager, who ensures that the WAF aligns with your application's requirements and evolves with emerging security threats.\n{% /info %}\n\nThe WAF functions as a protective barrier at the application layer (Layer 7) of the OSI model, where it inspects and filters HTTP/HTTPS traffic in real-time. It is specifically designed to safeguard your application by analyzing request headers, payloads, and query strings to identify malicious patterns or anomalies. The WAF intercepts traffic before it reaches your application, blocking threats such as:\n\n- SQL injection: Malicious input targeting database queries is identified and neutralized by inspecting payloads and request parameters.\n- Cross-site scripting (XSS): WAF detects and blocks scripts attempting to manipulate or steal client-side data through injection into the DOM or other contexts.\n- Cross-site request forgery (CSRF): By analyzing session headers and request origins, the WAF ensures only legitimate actions are processed.\n- DDoS attacks: Although primarily focused on Layer 7 attacks, the WAF works in tandem with other mitigation systems to handle floods of application-level requests.\n\n# Design {% #design %}\n\n1. **Traffic inspection**\n The WAF analyzes all incoming HTTP/HTTPS requests in real-time, matching them against a dynamic set of security rules. \n\n2. **Threat mitigation**\n Suspicious requests are blocked before they reach your application, ensuring uninterrupted service and secure user interactions. \n\n3. **Custom rules**\n Enterprise customers can work with their success manager to define custom rules tailored to their specific application requirements. \n\n# Key features {% #key-features %}\n\n- Real-time protection: Continuous monitoring and blocking of malicious traffic. \n- Customizable rulesets: Configure WAF policies to align with your application's unique architecture. \n- Compliance support: Meet security standards and regulatory requirements by safeguarding sensitive data. \n- Seamless integration: Fully integrated into the Appwrite network, ensuring optimal performance and low-latency security enforcement. \n\n# Getting started {% #getting-started %}\n\nIf you're an enterprise customer, reach out to your success manager to enable WAF for your applications. They'll guide you through the setup process, help configure custom rules, and ensure your application is fully protected. \n\nFor more information on upgrading to the enterprise plan, [contact sales](https://appwrite.io/contact-us/enterprise).\n\n## Configuration {% #configuration %}\n\nAppwrite's WAF is designed to adapt to the specific needs of your applications. Tailored rules and actions can be implemented to address evolving security threats and traffic patterns effectively.\n\n### Rules {% #rules %}\n\nThe WAF supports the creation of fine-grained rules using logical expressions. These rules define how incoming traffic is inspected and handled.\n\n1. **Expression-based rules** \n Use logical expressions to create granular filters for incoming requests. These expressions operate on HTTP request fields, headers, cookies, and metadata. \n\n Examples of configurable expressions: \n - **URL matching**: Block or allow traffic based on URL patterns (e.g., `/v1/functions/*`). \n - **Request method**: Filter traffic based on HTTP methods (e.g., `GET`, `POST`, `PUT`). \n - **Header inspection**: Inspect headers like `User-Agent`, `Authorization`, or `X-Forwarded-For`. \n - **Cookie validation**: Check for specific cookies or validate their values to identify bot or malicious traffic. \n - **Rate limiting**: Match requests based on IP or session-level rate thresholds. \n - **Geolocation rules**: Allow or block traffic based on the geographic location of the user. \n\n2. **Regular expressions (Regex)** \n Use regex patterns for more advanced filtering, such as matching complex URL structures, payloads, or specific query parameters. \n\n3. **IP Filtering** \n Define rules to block, allow, or challenge traffic from specific IP ranges or CIDR blocks. \n\n### Actions {% #actions %}\n\nBased on the defined rules, the WAF can perform various actions to handle traffic appropriately. \n\n1. **Allow** \n Allows the request to pass through to the backend without modification. \n\n2. **Block** \n Rejects the request entirely, responding with an appropriate HTTP status code (e.g., `403 Forbidden`). \n\n3. **Challenge** \n Issues a challenge (e.g., CAPTCHA) to validate whether the request originates from a legitimate user. \n\n4. **Redirect** \n Redirects the request to a specified URL. Useful for handling deprecated endpoints or re-routing unauthorized traffic. \n\n5. **Log** \n Records the request in detailed logs for further analysis without altering the request flow. \n\n6. **Rate limiting** \n Throttles traffic from specific IPs or sessions that exceed defined thresholds, reducing the risk of abuse or overload. \n\n7. **Modify request** \n Dynamically alters request components, such as headers, query parameters, or cookies. \n\n### Examples {% #examples %}\n\n#### Blocking malicious bots\n- **Expression**: Match requests with suspicious `User-Agent` strings or invalid headers. \n- **Action**: Block or issue a CAPTCHA challenge to confirm legitimate traffic. \n\n#### Preventing SQL injection\n- **Expression**: Match payloads or query strings containing SQL keywords like `SELECT`, `DROP`, or `--`. \n- **Action**: Block the request and log it for analysis. \n\n#### Rate limiting by endpoint\n- **Expression**: Limit requests to sensitive endpoints (e.g., `/login`) to prevent abuse. \n- **Action**: Throttle or temporarily block repeated requests from the same IP. \n\n#### Geo-based blocking\n- **Expression**: Match requests originating from specific regions or countries. \n- **Action**: Block or redirect users to a region-specific notice page. \n\n#### Header enforcement\n- **Expression**: Check for required headers (e.g., `Authorization`) on API requests. \n- **Action**: Block requests missing critical headers."}, {"path": "docs/products/sites", "title": "Sites", "description": "Appwrite Sites is your gateway to scalable web applications. Explore our complete guide to building and deploying websites effortlessly.", "content": "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.\n\nAppwrite Sites leverages the [Appwrite Network](/docs/products/network) infrastructure to enhance your sites' performance and reliability. Your deployed sites automatically benefit from global content distribution across strategic edge locations, reducing latency and improving load times, while also gaining advanced security features including DDoS protection, Web Application Firewall (WAF), and TLS encryption.\n\n# Getting started\n\nAppwrite 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.\n\n{% only_dark %}\n![Create first site](/images/docs/sites/dark/create-first-site.avif)\n{% /only_dark %}\n{% only_light %}\n![Create first site](/images/docs/sites/create-first-site.avif)\n{% /only_light %}\n\nTry out one of our most popular framework quick-starts:\n\n{% cards %}\n\n{% cards_item href=\"/docs/products/sites/quick-start/nextjs\" title=\"Next.js\" icon=\"icon-nextjs\" %}\n{% /cards_item %}\n\n{% cards_item href=\"/docs/products/sites/quick-start/nuxt\" title=\"Nuxt\" icon=\"icon-nuxt\" %}\n{% /cards_item %}\n\n{% cards_item href=\"/docs/products/sites/quick-start/sveltekit\" title=\"SvelteKit\" icon=\"icon-svelte\" %}\n{% /cards_item %}\n\n{% cards_item href=\"/docs/products/sites/quick-start/astro\" title=\"Astro\" icon=\"icon-astro\" %}\n{% /cards_item %}\n\n{% cards_item href=\"/docs/products/sites/quick-start/vue\" title=\"Vue\" icon=\"web-icon-vue\" %}\n{% /cards_item %}\n\n{% cards_item href=\"/docs/products/sites/quick-start/tanstack-start\" title=\"TanStack Start\" icon=\"web-icon-tanstack\" %}\n{% /cards_item %}\n\n{% /cards %}\n\nOr, [setup your first site using your favorite framework >](/docs/products/sites/quick-start)\n\n# Migrating from other platforms?\n\nLooking to migrate existing web applications from other hosting platforms to Appwrite Sites? Find detailed migration guides for various platforms:\n\n{% cards %}\n\n{% cards_item href=\"/docs/products/sites/migrations/vercel\" title=\"Migrating from Vercel\" icon=\"icon-vercel\" %}\n{% /cards_item %}\n\n{% /cards %}"}, {"path": "docs/products/sites/deploy-from-cli", "title": "Deploy from CLI", "description": "Learn to deploy Appwrite Sites from the Appwrite CLI.", "content": "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.\n\nWhile we recommend you create deployments through [automatic Git deployments](/docs/products/sites/deploy-from-git), you can also create deployments via the Appwrite CLI.\n\n# CLI {% #cli %} \n\n{% partial file=\"cli-sites.md\" /%}\n\n## Configure CLI deployments {% #configure-cli-deployments %}\nIf 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, \nyou can do so by editing the `appwrite.config.json` file.\n\n{% arrow_link href=\"/docs/tooling/command-line/sites#appwritejson\" %}\nLearn more about appwrite.config.json\n{% /arrow_link %}\n\nFor 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).\n\n# Debugging\n\n- 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."}, {"path": "docs/products/sites/deploy-from-git", "title": "Deploy from Git", "description": "Learn to version and update your Appwrite Sites' code with deployments.", "content": "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.\n\nWith 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.\n\n# Create deployment\n\nThe 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.\n\nYou 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**.\n\n{% only_dark %}\n![Git repository](/images/docs/sites/dark/git-repo.avif)\n{% /only_dark %}\n{% only_light %}\n![Git repository](/images/docs/sites/git-repo.avif)\n{% /only_light %}\n\n1. Using Git, checkout to the branch you configured as the production branch when creating the Appwrite Site.\n2. Create a new commit.\n3. Push the commit.\n4. A new deployment will be automatically created, built, and activated.\n\n## Commits to the production branch\n\nWhen 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.\n\n## Commits to other branches\n\nWhen 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.\n\n# Git configuration\n\nIf you need to update your Git configuration, navigate to **Sites** > your site > **Settings** > **Git repository**.\n\n## Build triggers {% #build-triggers %}\n\nBuild triggers control which Git changes create automatic deployments. You can configure branch filters and path filters with glob patterns.\n\n**Branch filters** match branch names. Add patterns to limit automatic deployments to specific branches.\n\n```txt\nmain\nstaging\npreview/**\n```\n\n**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.\n\n```txt\napps/web/**\npackages/ui/**\n!docs/**\n```\n\nUse these formats to write glob patterns.\n\n{% partial file=\"git-glob-patterns.md\" /%}\n\n## Install command\n\nThe 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`.\n\n## Build command\n\nThe 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.\n\n## Output directory\n\nThe 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.\n\n# Debugging\n\n- 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.\n- 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.\n- 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."}, {"path": "docs/products/sites/deploy-manually", "title": "Deploy manually", "description": "Learn to deploy Appwrite Sites manually via the Appwrite Console.", "content": "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.\n\nWhile 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.\n\n# Manual Deployment\n\nYou can upload your sites to be deployed using the Appwrite Console. The example below shows a skeleton SvelteKit app.\n\n```bash\n.\n├ src/\n├ static/\n├ package.json\n├ svelte.config.js\n├ tsconfig.json\n└ vite.config.js\n```\n\nFirst, 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:\n\n```bash\ntar --exclude code.tar.gz -czf code.tar.gz .\n```\n\nNext, navigate to your Appwrite Console and upload the site.\n\n1. Navigate to the site you want to deploy.\n2. Head to the **Deployments** tab.\n3. Click on the **Create deployment** button.\n4. Select the **Manual** option.\n {% only_dark %}\n ![Manual deployment](/images/docs/sites/dark/manual-deployment.avif)\n {% /only_dark %}\n {% only_light %}\n ![Manual deployment](/images/docs/sites/manual-deployment.avif)\n {% /only_light %}\n5. Upload `code.tar.gz`.\n6. Select **Activate deployment after build**.\n7. Click on the **Create** button.\n\n# Debugging\n\n- 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."}, {"path": "docs/products/sites/deployments", "title": "Deployments", "description": "Efficiently deploy your web apps with Appwrite. Explore deployment options, strategies, and best practices.", "content": "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.\n\n# Deployment status\n\nThroughout the life cycle of a deployment, it can have any of the following status:\n\n| Status | Description |\n| --- | --- |\n| `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. |\n| `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. |\n| `building` | A deployment is being built. Check the [deployment logs](#deployment-logs) for more info. |\n| `processing` | The creation of a site deployment has begun and has not finished. |\n| `waiting` | The deployment is queued but has not been picked up for processing. |\n| `failed` | A deployment was not successful. Check the [deployment logs](#deployment-logs) for more info for debugging. |\n\n# Deployment logs\n\nWhen 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**.\n\n{% only_dark %}\n![Deployment logs](/images/docs/sites/dark/deployment-logs.avif)\n{% /only_dark %}\n{% only_light %}\n![Deployment logs](/images/docs/sites/deployment-logs.avif)\n{% /only_light %}\n\n# Create deployment\n\nTo 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:\n\n- **Git**: Lets you select a branch on your connected Git repo and whether you would like to activate the build post-deployment\n- **CLI**: Lets you run a [CLI command](/docs/products/sites/deploy-from-cli#cli) in your site's directory\n- **Manual**: Lets you upload a [.tar.gz file](/docs/products/sites/deploy-manually#manual-deployment) containing your site's build output\n\n{% only_dark %}\n![Create deployment](/images/docs/sites/dark/create-deployment.avif)\n{% /only_dark %}\n{% only_light %}\n![Create deployment](/images/docs/sites/create-deployment.avif)\n{% /only_light %}\n\n# Cancel deployment\n\nIf 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.\n\n{% only_dark %}\n![Cancel deployment](/images/docs/sites/dark/cancel-deployment.avif)\n{% /only_dark %}\n{% only_light %}\n![Cancel deployment](/images/docs/sites/cancel-deployment.avif)\n{% /only_light %}\n\n# Update deployment\n\nSome 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.\n\n# Redeploy\n\nAfter updating the configuration, redeploy your site for changes to take effect. You can also redeploy to retry failed builds.\n\n1. Navigate to your site on Appwrite Console.\n2. Under the **Deployments** tab, find the status of the current active deployment.\n3. Redeploy by clicking the triple-dots beside a deployment and hitting the **Redeploy** button.\n\n{% only_dark %}\n![Redeploy](/images/docs/sites/dark/redeploy.avif)\n{% /only_dark %}\n{% only_light %}\n![Redeploy](/images/docs/sites/redeploy.avif)\n{% /only_light %}\n\nRedeployment behavior varies depending on how the initial deployment was created.\n\n{% info title=\"Benefits for Pro+ users\" %}\nUsers subscribed to the Appwrite Pro plan or above receive certain special benefits:\n\n- [Express builds](/changelog/entry/2024-08-10) for quicker deployments, resulting in reduced wait times and smoother workflows\n- Longer [build timeouts](/docs/advanced/billing/compute#build-timeouts) (45 minutes vs 15 minutes on Free; Enterprise is custom)\n- Customizable [build and runtime specifications](/docs/advanced/billing/compute) for CPU and memory on each site\n{% /info %}\n\n# Deployment retention {% #deployment-retention %}\n\nDeployment 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.\n\nTo configure deployment retention from the Appwrite Console:\n\n1. Navigate to **Sites**.\n2. Open the site you want to configure.\n3. Go to **Settings** > **Deployment retention**.\n4. Turn on **Keep deployments forever**, or turn it off and choose how long to keep non-active deployments.\n5. Click **Update**.\n\n{% only_dark %}\n![Site deployment retention settings](/images/docs/sites/dark/deployment-retention.avif)\n{% /only_dark %}\n{% only_light %}\n![Site deployment retention settings](/images/docs/sites/deployment-retention.avif)\n{% /only_light %}\n\nThe 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.\n\nWhen updating a site with a Server SDK, pass the existing settings you do not intend to change and update only `deploymentRetention`.\n\n{% multicode %}\n\n```server-nodejs\nconst site = await sites.get({\n siteId: '<SITE_ID>'\n});\n\nawait sites.update({\n siteId: site.$id,\n name: site.name,\n framework: site.framework,\n enabled: site.enabled ?? undefined,\n logging: site.logging ?? undefined,\n timeout: site.timeout ?? undefined,\n installCommand: site.installCommand ?? undefined,\n buildCommand: site.buildCommand ?? undefined,\n startCommand: site.startCommand ?? undefined,\n outputDirectory: site.outputDirectory ?? undefined,\n buildRuntime: site.buildRuntime ?? undefined,\n adapter: site.adapter ?? undefined,\n fallbackFile: site.fallbackFile ?? undefined,\n installationId: site.installationId ?? undefined,\n providerRepositoryId: site.providerRepositoryId ?? undefined,\n providerBranch: site.providerBranch ?? undefined,\n providerSilentMode: site.providerSilentMode ?? undefined,\n providerRootDirectory: site.providerRootDirectory ?? undefined,\n buildSpecification: site.buildSpecification ?? undefined,\n runtimeSpecification: site.runtimeSpecification ?? undefined,\n deploymentRetention: 90\n});\n```\n\n{% /multicode %}"}, {"path": "docs/products/sites/develop", "title": "Develop Appwrite Sites", "description": "Master site development with Appwrite.", "content": "# Rendering strategies\n\nAppwrite allows you to host both statically-generated and server-rendered websites. \n\n[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. \n\n[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.\n\nYou can configure your preferred rendering strategy through the following steps:\n\n1. Navigate to your site on Appwrite Console.\n2. Head to the **Settings** tab > **Build settings** section.\n3. Select the SSR or the Static checkbox.\n4. Confirm that the appropriate install command, build command, and output directory are set.\n5. *(For SPAs)* Add a fallback file.\n6. Click on the **Update** button and redeploy your site.\n\n{% only_dark %}\n![Rendering strategy](/images/docs/sites/dark/build-settings-rendering-ssr.avif)\n{% /only_dark %}\n{% only_light %}\n![Rendering strategy](/images/docs/sites/build-settings-rendering-ssr.avif)\n{% /only_light %}\n\n# Timeouts {% #timeouts %}\n\nEach 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:\n\n1. Navigate to your site on Appwrite Console.\n2. Head to the **Settings** tab > **Timeout** section.\n3. Add an appropriate time limit (in seconds).\n\n{% only_dark %}\n![Timeout](/images/docs/sites/dark/timeout.avif)\n{% /only_dark %}\n{% only_light %}\n![Timeout](/images/docs/sites/timeout.avif)\n{% /only_light %}\n\nThe default timeout is set at `15 seconds` and the maximum value possible is `30 seconds`.\n\n# Resource limits {% #resource-limits %}\n\nUnder **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.\n\nOn Appwrite Cloud, customizing specifications requires the **Pro** plan. See [Compute](/docs/advanced/billing/compute) for tiers, GB-hours, and pricing.\n\n## Build timeouts {% #build-timeouts %}\n\nOn 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).\n\n# Project dependencies\n\nTo 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:\n\n1. Navigate to your site on Appwrite Console.\n2. Head to the **Settings** tab > **Build settings** section.\n3. Confirm that the appropriate install command is set.\n\n{% only_dark %}\n![Install command](/images/docs/sites/dark/build-settings-install-command.avif)\n{% /only_dark %}\n{% only_light %}\n![Install command](/images/docs/sites/build-settings-install-command.avif)\n{% /only_light %}\n\nMake 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.\n\nYour site's dependencies should be managed by the package manager of each language. We include the following package managers and setup commands by default.\n\n{% table %}\n-   {% width=48 %}\n- Framework\n- Default package manager\n- Install command\n- Build command\n- Output directory\n\n---\n\n- {% icon icon=\"nextjs\" size=\"m\" /%}\n- **Next.js**\n- `npm`\n- `npm install`\n- `npm run build`\n- `./.next`\n\n---\n\n- {% icon icon=\"nuxt\" size=\"m\" /%}\n- **Nuxt**\n- `npm`\n- `npm install` or `yarn install`\n- `npm run build` or `yarn build`\n- `./.output`\n\n---\n\n- {% icon icon=\"svelte\" size=\"m\" /%}\n- **SvelteKit**\n- `npm`\n- `npm install`\n- `npm run build`\n- `./build`\n\n---\n\n- {% icon icon=\"angular\" size=\"m\" /%}\n- **Angular**\n- `npm`\n- `npm install`\n- `npm run build`\n- `./dist/angular/browser`\n\n---\n\n- {% icon icon=\"astro\" size=\"m\" /%}\n- **Astro**\n- `npm`\n- `npm install`\n- `npm run build`\n- `./dist`\n\n---\n\n- {% only_dark %}{% icon_image src=\"/images/platforms/dark/remix.svg\" alt=\"Remix logo\" size=\"m\" /%}{% /only_dark %}\n{% only_light %}{% icon_image src=\"/images/platforms/light/remix.svg\" alt=\"Remix logo\" size=\"m\" /%}{% /only_light %}\n- **Remix**\n- `npm`\n- `npm install`\n- `npm run build`\n- `./build`\n\n---\n\n- {% only_dark %}{% icon_image src=\"/images/platforms/dark/tanstack.svg\" alt=\"TanStack Start logo\" size=\"m\" /%}{% /only_dark %}\n{% only_light %}{% icon_image src=\"/images/platforms/light/tanstack.svg\" alt=\"TanStack Start logo\" size=\"m\" /%}{% /only_light %}\n- **TanStack Start**\n- `npm`\n- `npm install`\n- `npm run build`\n- `./dist`\n\n---\n- {% icon icon=\"flutter\" size=\"m\" /%}\n- **Flutter**\n- `pub`\n- `flutter pub get`\n- `flutter build web --release -t lib/main.dart`\n- `./build/web`\n\n---\n\n- {% icon icon=\"react-native\" size=\"m\" /%}\n- **React Native**\n- `npm`\n- `npm install`\n- `npm run build`\n- `./dist`\n\n---\n\n- {% icon icon=\"js\" size=\"m\" /%}\n- **Other JavaScript**\n- `npm`\n- (Empty)\n- (Empty)\n- `./`\n\n{% /table %}"}, {"path": "docs/products/sites/domains", "title": "Domains", "description": "Discover how domains can be managed for an Appwrite Site", "content": "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.\n\n[Learn about Sites development >](/docs/products/sites/develop)\n\n# Generated domains\n\nEach site automatically receives a unique Appwrite-generated domain that's ready to use immediately.\n\n1. In the Appwrite Console's sidebar, click **Sites**.\n2. Under the **Domains** tab, you'll find the domain generated by Appwrite.\n\nThe domain usually has this format:\n\n```bash\nhttps://64d4d22db370ae41a32e.appwrite.network\n```\n\n## Branch and Commit URLs\n\nAdditional 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).\n\n{% only_dark %}\n![Branch and commit URLs](/images/docs/sites/dark/branch-commit-urls.avif)\n{% /only_dark %}\n{% only_light %}\n![Branch and commit URLs](/images/docs/sites/branch-commit-urls.avif)\n{% /only_light %}\n\nTo find the branch and commit URLs of any deployment, follow these steps:\n\n1. Navigate to your site on Appwrite Console.\n2. Head to the **Deployments** tab and click on any deployment.\n3. In the **Domains** section, click on the **+2** next to the mentioned domain.\n\n# Add a custom domain\n\nYou 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.\n\n## Add an apex domain with NS records\n\nApex 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.\n\nTo add an apex domain via NS records:\n\n1. Navigate to your site in the Appwrite Console.\n2. Head to the **Domains** tab and click on **Add domain**.\n3. Enter your apex domain (e.g., `example.com`).\n4. 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.\n5. Appwrite will provide NS record information.\n6. Go to your domain registrar and update the NS records for your domain to point to `ns1.appwrite.zone` and `ns2.appwrite.zone`.\n7. Return to the Appwrite Console and wait for the verification process to complete.\n\n{% only_dark %}\n![Add domain](/images/docs/sites/dark/add-domain-ns.avif)\n{% /only_dark %}\n{% only_light %}\n![Add domain](/images/docs/sites/add-domain-ns.avif)\n{% /only_light %}\n\nDNS 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.\n\n### Why Appwrite uses NS records instead of A records\n\nBy [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.\n\nUsing Appwrite's DNS servers for your apex domain provides several benefits:\n- Proper SSL certificate management\n- Automatic DNS configuration\n- Secure and reliable DNS resolution\n\nWhen 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.\n\n{% arrow_link href=\"/docs/products/network/dns\" %}\nLearn more about Appwrite DNS server\n{% /arrow_link %}\n\n## Add an apex domain without changing nameservers\n\nTo add an apex domain without changing your NS records via [CNAME flattening](/docs/products/network/dns#using-cname-flattening):\n\n1. Navigate to your site in the Appwrite Console.\n2. Head to the **Domains** tab and click on **Add domain**.\n3. Enter your apex domain (e.g., `example.com`).\n4. 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.\n5. Copy the **CNAME** record provided by Appwrite.\n6. Copy the **[CAA](/docs/products/network/caa-records)** record provided by Appwrite.\n7. In your DNS provider's settings, create an ALIAS, ANAME, or CNAME record at the apex (depending on what your provider supports).\n8. Point the record to the Appwrite hostname provided in the console.\n9. 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).\n10. Return to the Site settings and wait for verification status.\n\n{% only_dark %}\n![Add domain](/images/docs/sites/dark/add-domain-cname.avif)\n{% /only_dark %}\n{% only_light %}\n![Add domain](/images/docs/sites/add-domain-cname.avif)\n{% /only_light %}\n\nDNS 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.\n\n## Add a subdomain with CNAME\n\nSubdomains (like `www.example.com` or `app.example.com`) are set up using CNAME records, which point to Appwrite's hostname.\n\nTo add a subdomain:\n\n1. Navigate to your site in the Appwrite Console.\n2. Head to the **Domains** tab and click on **Add domain**.\n3. Input your subdomain (e.g., `www.example.com`).\n4. 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.\n5. Copy the specified **CNAME** record and add it to your domain registrar.\n6. Return to the Site settings and wait for verification status.\n\n{% only_dark %}\n![Add domain](/images/docs/sites/dark/add-domain.avif)\n{% /only_dark %}\n{% only_light %}\n![Add domain](/images/docs/sites/add-domain.avif)\n{% /only_light %}\n\nDNS records can take up to 48 hours to propagate. Once verified, the domain is ready to use.\n\n## Domain rule types\n\nWhen 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:\n\n### Active deployment\n\nPoints your domain to the latest deployed version of your site. This is the most common option for production domains.\n\n- When selected, your domain will always serve the most recent successful deployment\n- Any new deployments will automatically be available on this domain\n\n### Git branch\n\nPoints your domain to a specific branch in your repository. This is useful for testing or staging environments.\n\n- When selected, you'll need to choose a specific branch from your connected repository\n- Your domain will always serve the latest successful deployment from that branch\n- This allows you to have different domains for different branches (e.g., staging.example.com for your staging branch)\n\n### Redirect\n\nForwards all traffic from your domain to another URL. This is useful for domain migrations or creating shortcuts.\n\n- When selected, you'll need to specify the destination URL\n- You can choose from various HTTP status codes for the redirect:\n - 301 Moved permanently\n - 302 Found\n - 303 See other\n - 307 Temporary redirect\n - 308 Permanent redirect\n\n{% info title=\"Path and query parameters in redirects\" %}\nWhen you redirect an added domain to another URL, any additional path and queries will be ignored.\n\nFor example, if a domain `example.com` is set to redirect to `appwrite.io`, `example.com/docs?id=123` will also redirect to `appwrite.io`.\n{% /info %}"}, {"path": "docs/products/sites/environment-variables", "title": "Environment variables", "description": "Set environment variables for your Appwrite Sites to pass constants and secrets at build and runtime.", "content": "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.\n\nA site reads from three sources, in this order of precedence:\n\n1. **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/advanced/security/environment-variables) for the full reference.\n2. **Site variables** are scoped to a single site. Override a project variable for one site by setting the same key on the site itself.\n3. **Appwrite-injected variables** are set by Appwrite at deployment time (for example, `APPWRITE_SITE_PROJECT_ID`). These take final precedence and cannot be overridden.\n\n{% info title=\"Redeployment required\" %}\nVariable changes only take effect on the next deployment. Redeploy your site after creating, updating, or deleting variables.\n{% /info %}\n\n# Manage in the Console {% #console %}\n\n1. Navigate to your site in the Appwrite Console.\n2. Open the **Settings** tab > **Environment variables** section.\n3. Click **Create variable** and enter a key and value.\n4. Optionally select the **Secret** checkbox to prevent any team member from reading the value after creation.\n5. Click **Create**, then redeploy the site for the change to take effect.\n\n{% only_dark %}\n![Site environment variables](/images/docs/sites/dark/env-variables.avif)\n{% /only_dark %}\n{% only_light %}\n![Site environment variables](/images/docs/sites/env-variables.avif)\n{% /only_light %}\n\nYou can also configure global variables that apply to all your sites from your project's **Settings** page. See [project variables](/docs/advanced/security/environment-variables) for details.\n\n# Manage with a Server SDK {% #server-sdks %}\n\nYou can also manage site variables programmatically using a [Server SDK](/docs/sdks#server). Each call requires an [API key](/docs/advanced/security/api-keys) with the `sites.write` scope to create, update, or delete variables, or the `sites.read` scope to list and read them.\n\n## Create a variable {% #create-variable %}\n\n{% multicode %}\n```server-nodejs\nimport { Client, Sites } from 'node-appwrite';\n\nconst client = new Client()\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<YOUR_PROJECT_ID>') // Your project ID\n .setKey('<YOUR_API_KEY>'); // Your API key\n\nconst sites = new Sites(client);\n\nconst result = await sites.createVariable({\n siteId: '<SITE_ID>',\n key: '<KEY>',\n value: '<VALUE>',\n secret: false // optional\n});\n```\n```server-deno\nimport { Client, Sites } from \"npm:node-appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<YOUR_PROJECT_ID>') // Your project ID\n .setKey('<YOUR_API_KEY>'); // Your API key\n\nconst sites = new Sites(client);\n\nconst result = await sites.createVariable({\n siteId: '<SITE_ID>',\n key: '<KEY>',\n value: '<VALUE>',\n secret: false // optional\n});\n```\n```server-php\n<?php\n\nuse Appwrite\\Client;\nuse Appwrite\\Services\\Sites;\n\n$client = (new Client())\n ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n ->setProject('<YOUR_PROJECT_ID>') // Your project ID\n ->setKey('<YOUR_API_KEY>'); // Your API key\n\n$sites = new Sites($client);\n\n$result = $sites->createVariable(\n siteId: '<SITE_ID>',\n key: '<KEY>',\n value: '<VALUE>',\n secret: false // optional\n);\n```\n```server-python\nfrom appwrite.client import Client\nfrom appwrite.services.sites import Sites\nfrom appwrite.models import Variable\n\nclient = Client()\nclient.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\nclient.set_project('<YOUR_PROJECT_ID>') # Your project ID\nclient.set_key('<YOUR_API_KEY>') # Your API key\n\nsites = Sites(client)\n\nresult: Variable = sites.create_variable(\n site_id = '<SITE_ID>',\n key = '<KEY>',\n value = '<VALUE>',\n secret = False # optional\n)\n\nprint(result.model_dump())\n```\n```server-ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('<YOUR_PROJECT_ID>') # Your project ID\n .set_key('<YOUR_API_KEY>') # Your API key\n\nsites = Sites.new(client)\n\nresult = sites.create_variable(\n site_id: '<SITE_ID>',\n key: '<KEY>',\n value: '<VALUE>',\n secret: false # optional\n)\n```\n```server-dotnet\nusing Appwrite;\nusing Appwrite.Models;\nusing Appwrite.Services;\n\nClient client = new Client()\n .SetEndPoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .SetProject(\"<YOUR_PROJECT_ID>\") // Your project ID\n .SetKey(\"<YOUR_API_KEY>\"); // Your API key\n\nSites sites = new Sites(client);\n\nVariable result = await sites.CreateVariable(\n siteId: \"<SITE_ID>\",\n key: \"<KEY>\",\n value: \"<VALUE>\",\n secret: false // optional\n);\n```\n```server-dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\n\nClient client = Client()\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<YOUR_PROJECT_ID>') // Your project ID\n .setKey('<YOUR_API_KEY>'); // Your API key\n\nSites sites = Sites(client);\n\nVariable result = await sites.createVariable(\n siteId: '<SITE_ID>',\n key: '<KEY>',\n value: '<VALUE>',\n secret: false, // (optional)\n);\n```\n```server-kotlin\nimport io.appwrite.Client\nimport io.appwrite.coroutines.CoroutineCallback\nimport io.appwrite.services.Sites\n\nval client = Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<YOUR_PROJECT_ID>\") // Your project ID\n .setKey(\"<YOUR_API_KEY>\") // Your API key\n\nval sites = Sites(client)\n\nval response = sites.createVariable(\n siteId = \"<SITE_ID>\",\n key = \"<KEY>\",\n value = \"<VALUE>\",\n secret = false // optional\n)\n```\n```server-java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Sites;\n\nClient client = new Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<YOUR_PROJECT_ID>\") // Your project ID\n .setKey(\"<YOUR_API_KEY>\"); // Your API key\n\nSites sites = new Sites(client);\n\nsites.createVariable(\n \"<SITE_ID>\", // siteId\n \"<KEY>\", // key\n \"<VALUE>\", // value\n false, // secret (optional)\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(result);\n })\n);\n```\n```server-swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<YOUR_PROJECT_ID>\") // Your project ID\n .setKey(\"<YOUR_API_KEY>\") // Your API key\n\nlet sites = Sites(client)\n\nlet variable = try await sites.createVariable(\n siteId: \"<SITE_ID>\",\n key: \"<KEY>\",\n value: \"<VALUE>\",\n secret: false // optional\n)\n```\n```server-go\npackage main\n\nimport (\n \"fmt\"\n \"github.com/appwrite/sdk-for-go/appwrite\"\n)\n\nclient := appwrite.NewClient(\n appwrite.WithEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\"),\n appwrite.WithProject(\"<YOUR_PROJECT_ID>\"),\n appwrite.WithKey(\"<YOUR_API_KEY>\"),\n)\n\nsites := appwrite.NewSites(client)\n\nresponse, error := sites.CreateVariable(\n \"<SITE_ID>\",\n \"<KEY>\",\n \"<VALUE>\",\n appwrite.WithCreateVariableSecret(false),\n)\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::Sites;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n let client = Client::new();\n client.set_endpoint(\"https://<REGION>.cloud.appwrite.io/v1\"); // Your API Endpoint\n client.set_project(\"<YOUR_PROJECT_ID>\"); // Your project ID\n client.set_key(\"<YOUR_API_KEY>\"); // Your API key\n\n let sites = Sites::new(&client);\n\n let result = sites.create_variable(\n \"<SITE_ID>\",\n \"<KEY>\",\n \"<VALUE>\",\n Some(false) // optional\n ).await?;\n\n let _ = result;\n\n Ok(())\n}\n```\n{% /multicode %}\n\n## List variables {% #list-variables %}\n\n{% multicode %}\n```server-nodejs\nimport { Client, Sites } from 'node-appwrite';\n\nconst client = new Client()\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<YOUR_PROJECT_ID>') // Your project ID\n .setKey('<YOUR_API_KEY>'); // Your API key\n\nconst sites = new Sites(client);\n\nconst result = await sites.listVariables({\n siteId: '<SITE_ID>'\n});\n```\n```server-deno\nimport { Client, Sites } from \"npm:node-appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<YOUR_PROJECT_ID>') // Your project ID\n .setKey('<YOUR_API_KEY>'); // Your API key\n\nconst sites = new Sites(client);\n\nconst result = await sites.listVariables({\n siteId: '<SITE_ID>'\n});\n```\n```server-php\n<?php\n\nuse Appwrite\\Client;\nuse Appwrite\\Services\\Sites;\n\n$client = (new Client())\n ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n ->setProject('<YOUR_PROJECT_ID>') // Your project ID\n ->setKey('<YOUR_API_KEY>'); // Your API key\n\n$sites = new Sites($client);\n\n$result = $sites->listVariables(\n siteId: '<SITE_ID>'\n);\n```\n```server-python\nfrom appwrite.client import Client\nfrom appwrite.services.sites import Sites\nfrom appwrite.models import VariableList\n\nclient = Client()\nclient.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\nclient.set_project('<YOUR_PROJECT_ID>') # Your project ID\nclient.set_key('<YOUR_API_KEY>') # Your API key\n\nsites = Sites(client)\n\nresult: VariableList = sites.list_variables(\n site_id = '<SITE_ID>'\n)\n\nprint(result.model_dump())\n```\n```server-ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('<YOUR_PROJECT_ID>') # Your project ID\n .set_key('<YOUR_API_KEY>') # Your API key\n\nsites = Sites.new(client)\n\nresult = sites.list_variables(\n site_id: '<SITE_ID>'\n)\n```\n```server-dotnet\nusing Appwrite;\nusing Appwrite.Models;\nusing Appwrite.Services;\n\nClient client = new Client()\n .SetEndPoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .SetProject(\"<YOUR_PROJECT_ID>\") // Your project ID\n .SetKey(\"<YOUR_API_KEY>\"); // Your API key\n\nSites sites = new Sites(client);\n\nVariableList result = await sites.ListVariables(\n siteId: \"<SITE_ID>\"\n);\n```\n```server-dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\n\nClient client = Client()\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<YOUR_PROJECT_ID>') // Your project ID\n .setKey('<YOUR_API_KEY>'); // Your API key\n\nSites sites = Sites(client);\n\nVariableList result = await sites.listVariables(\n siteId: '<SITE_ID>',\n);\n```\n```server-kotlin\nimport io.appwrite.Client\nimport io.appwrite.coroutines.CoroutineCallback\nimport io.appwrite.services.Sites\n\nval client = Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<YOUR_PROJECT_ID>\") // Your project ID\n .setKey(\"<YOUR_API_KEY>\") // Your API key\n\nval sites = Sites(client)\n\nval response = sites.listVariables(\n siteId = \"<SITE_ID>\"\n)\n```\n```server-java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Sites;\n\nClient client = new Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<YOUR_PROJECT_ID>\") // Your project ID\n .setKey(\"<YOUR_API_KEY>\"); // Your API key\n\nSites sites = new Sites(client);\n\nsites.listVariables(\n \"<SITE_ID>\", // siteId\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(result);\n })\n);\n```\n```server-swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<YOUR_PROJECT_ID>\") // Your project ID\n .setKey(\"<YOUR_API_KEY>\") // Your API key\n\nlet sites = Sites(client)\n\nlet variableList = try await sites.listVariables(\n siteId: \"<SITE_ID>\"\n)\n```\n```server-go\npackage main\n\nimport (\n \"fmt\"\n \"github.com/appwrite/sdk-for-go/appwrite\"\n)\n\nclient := appwrite.NewClient(\n appwrite.WithEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\"),\n appwrite.WithProject(\"<YOUR_PROJECT_ID>\"),\n appwrite.WithKey(\"<YOUR_API_KEY>\"),\n)\n\nsites := appwrite.NewSites(client)\n\nresponse, error := sites.ListVariables(\n \"<SITE_ID>\",\n)\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::Sites;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n let client = Client::new();\n client.set_endpoint(\"https://<REGION>.cloud.appwrite.io/v1\"); // Your API Endpoint\n client.set_project(\"<YOUR_PROJECT_ID>\"); // Your project ID\n client.set_key(\"<YOUR_API_KEY>\"); // Your API key\n\n let sites = Sites::new(&client);\n\n let result = sites.list_variables(\n \"<SITE_ID>\"\n ).await?;\n\n let _ = result;\n\n Ok(())\n}\n```\n{% /multicode %}\n\n## Get a variable {% #get-variable %}\n\n{% multicode %}\n```server-nodejs\nimport { Client, Sites } from 'node-appwrite';\n\nconst client = new Client()\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<YOUR_PROJECT_ID>') // Your project ID\n .setKey('<YOUR_API_KEY>'); // Your API key\n\nconst sites = new Sites(client);\n\nconst result = await sites.getVariable({\n siteId: '<SITE_ID>',\n variableId: '<VARIABLE_ID>'\n});\n```\n```server-deno\nimport { Client, Sites } from \"npm:node-appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<YOUR_PROJECT_ID>') // Your project ID\n .setKey('<YOUR_API_KEY>'); // Your API key\n\nconst sites = new Sites(client);\n\nconst result = await sites.getVariable({\n siteId: '<SITE_ID>',\n variableId: '<VARIABLE_ID>'\n});\n```\n```server-php\n<?php\n\nuse Appwrite\\Client;\nuse Appwrite\\Services\\Sites;\n\n$client = (new Client())\n ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n ->setProject('<YOUR_PROJECT_ID>') // Your project ID\n ->setKey('<YOUR_API_KEY>'); // Your API key\n\n$sites = new Sites($client);\n\n$result = $sites->getVariable(\n siteId: '<SITE_ID>',\n variableId: '<VARIABLE_ID>'\n);\n```\n```server-python\nfrom appwrite.client import Client\nfrom appwrite.services.sites import Sites\nfrom appwrite.models import Variable\n\nclient = Client()\nclient.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\nclient.set_project('<YOUR_PROJECT_ID>') # Your project ID\nclient.set_key('<YOUR_API_KEY>') # Your API key\n\nsites = Sites(client)\n\nresult: Variable = sites.get_variable(\n site_id = '<SITE_ID>',\n variable_id = '<VARIABLE_ID>'\n)\n\nprint(result.model_dump())\n```\n```server-ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('<YOUR_PROJECT_ID>') # Your project ID\n .set_key('<YOUR_API_KEY>') # Your API key\n\nsites = Sites.new(client)\n\nresult = sites.get_variable(\n site_id: '<SITE_ID>',\n variable_id: '<VARIABLE_ID>'\n)\n```\n```server-dotnet\nusing Appwrite;\nusing Appwrite.Models;\nusing Appwrite.Services;\n\nClient client = new Client()\n .SetEndPoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .SetProject(\"<YOUR_PROJECT_ID>\") // Your project ID\n .SetKey(\"<YOUR_API_KEY>\"); // Your API key\n\nSites sites = new Sites(client);\n\nVariable result = await sites.GetVariable(\n siteId: \"<SITE_ID>\",\n variableId: \"<VARIABLE_ID>\"\n);\n```\n```server-dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\n\nClient client = Client()\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<YOUR_PROJECT_ID>') // Your project ID\n .setKey('<YOUR_API_KEY>'); // Your API key\n\nSites sites = Sites(client);\n\nVariable result = await sites.getVariable(\n siteId: '<SITE_ID>',\n variableId: '<VARIABLE_ID>',\n);\n```\n```server-kotlin\nimport io.appwrite.Client\nimport io.appwrite.coroutines.CoroutineCallback\nimport io.appwrite.services.Sites\n\nval client = Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<YOUR_PROJECT_ID>\") // Your project ID\n .setKey(\"<YOUR_API_KEY>\") // Your API key\n\nval sites = Sites(client)\n\nval response = sites.getVariable(\n siteId = \"<SITE_ID>\",\n variableId = \"<VARIABLE_ID>\"\n)\n```\n```server-java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Sites;\n\nClient client = new Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<YOUR_PROJECT_ID>\") // Your project ID\n .setKey(\"<YOUR_API_KEY>\"); // Your API key\n\nSites sites = new Sites(client);\n\nsites.getVariable(\n \"<SITE_ID>\", // siteId\n \"<VARIABLE_ID>\", // variableId\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(result);\n })\n);\n```\n```server-swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<YOUR_PROJECT_ID>\") // Your project ID\n .setKey(\"<YOUR_API_KEY>\") // Your API key\n\nlet sites = Sites(client)\n\nlet variable = try await sites.getVariable(\n siteId: \"<SITE_ID>\",\n variableId: \"<VARIABLE_ID>\"\n)\n```\n```server-go\npackage main\n\nimport (\n \"fmt\"\n \"github.com/appwrite/sdk-for-go/appwrite\"\n)\n\nclient := appwrite.NewClient(\n appwrite.WithEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\"),\n appwrite.WithProject(\"<YOUR_PROJECT_ID>\"),\n appwrite.WithKey(\"<YOUR_API_KEY>\"),\n)\n\nsites := appwrite.NewSites(client)\n\nresponse, error := sites.GetVariable(\n \"<SITE_ID>\",\n \"<VARIABLE_ID>\",\n)\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::Sites;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n let client = Client::new();\n client.set_endpoint(\"https://<REGION>.cloud.appwrite.io/v1\"); // Your API Endpoint\n client.set_project(\"<YOUR_PROJECT_ID>\"); // Your project ID\n client.set_key(\"<YOUR_API_KEY>\"); // Your API key\n\n let sites = Sites::new(&client);\n\n let result = sites.get_variable(\n \"<SITE_ID>\",\n \"<VARIABLE_ID>\"\n ).await?;\n\n let _ = result;\n\n Ok(())\n}\n```\n{% /multicode %}\n\n## Update a variable {% #update-variable %}\n\nYou 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.\n\n{% multicode %}\n```server-nodejs\nimport { Client, Sites } from 'node-appwrite';\n\nconst client = new Client()\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<YOUR_PROJECT_ID>') // Your project ID\n .setKey('<YOUR_API_KEY>'); // Your API key\n\nconst sites = new Sites(client);\n\nconst result = await sites.updateVariable({\n siteId: '<SITE_ID>',\n variableId: '<VARIABLE_ID>',\n key: '<KEY>',\n value: '<VALUE>', // optional\n secret: false // optional\n});\n```\n```server-deno\nimport { Client, Sites } from \"npm:node-appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<YOUR_PROJECT_ID>') // Your project ID\n .setKey('<YOUR_API_KEY>'); // Your API key\n\nconst sites = new Sites(client);\n\nconst result = await sites.updateVariable({\n siteId: '<SITE_ID>',\n variableId: '<VARIABLE_ID>',\n key: '<KEY>',\n value: '<VALUE>', // optional\n secret: false // optional\n});\n```\n```server-php\n<?php\n\nuse Appwrite\\Client;\nuse Appwrite\\Services\\Sites;\n\n$client = (new Client())\n ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n ->setProject('<YOUR_PROJECT_ID>') // Your project ID\n ->setKey('<YOUR_API_KEY>'); // Your API key\n\n$sites = new Sites($client);\n\n$result = $sites->updateVariable(\n siteId: '<SITE_ID>',\n variableId: '<VARIABLE_ID>',\n key: '<KEY>',\n value: '<VALUE>', // optional\n secret: false // optional\n);\n```\n```server-python\nfrom appwrite.client import Client\nfrom appwrite.services.sites import Sites\nfrom appwrite.models import Variable\n\nclient = Client()\nclient.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\nclient.set_project('<YOUR_PROJECT_ID>') # Your project ID\nclient.set_key('<YOUR_API_KEY>') # Your API key\n\nsites = Sites(client)\n\nresult: Variable = sites.update_variable(\n site_id = '<SITE_ID>',\n variable_id = '<VARIABLE_ID>',\n key = '<KEY>',\n value = '<VALUE>', # optional\n secret = False # optional\n)\n\nprint(result.model_dump())\n```\n```server-ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('<YOUR_PROJECT_ID>') # Your project ID\n .set_key('<YOUR_API_KEY>') # Your API key\n\nsites = Sites.new(client)\n\nresult = sites.update_variable(\n site_id: '<SITE_ID>',\n variable_id: '<VARIABLE_ID>',\n key: '<KEY>',\n value: '<VALUE>', # optional\n secret: false # optional\n)\n```\n```server-dotnet\nusing Appwrite;\nusing Appwrite.Models;\nusing Appwrite.Services;\n\nClient client = new Client()\n .SetEndPoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .SetProject(\"<YOUR_PROJECT_ID>\") // Your project ID\n .SetKey(\"<YOUR_API_KEY>\"); // Your API key\n\nSites sites = new Sites(client);\n\nVariable result = await sites.UpdateVariable(\n siteId: \"<SITE_ID>\",\n variableId: \"<VARIABLE_ID>\",\n key: \"<KEY>\",\n value: \"<VALUE>\", // optional\n secret: false // optional\n);\n```\n```server-dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\n\nClient client = Client()\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<YOUR_PROJECT_ID>') // Your project ID\n .setKey('<YOUR_API_KEY>'); // Your API key\n\nSites sites = Sites(client);\n\nVariable result = await sites.updateVariable(\n siteId: '<SITE_ID>',\n variableId: '<VARIABLE_ID>',\n key: '<KEY>',\n value: '<VALUE>', // (optional)\n secret: false, // (optional)\n);\n```\n```server-kotlin\nimport io.appwrite.Client\nimport io.appwrite.coroutines.CoroutineCallback\nimport io.appwrite.services.Sites\n\nval client = Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<YOUR_PROJECT_ID>\") // Your project ID\n .setKey(\"<YOUR_API_KEY>\") // Your API key\n\nval sites = Sites(client)\n\nval response = sites.updateVariable(\n siteId = \"<SITE_ID>\",\n variableId = \"<VARIABLE_ID>\",\n key = \"<KEY>\",\n value = \"<VALUE>\", // optional\n secret = false // optional\n)\n```\n```server-java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Sites;\n\nClient client = new Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<YOUR_PROJECT_ID>\") // Your project ID\n .setKey(\"<YOUR_API_KEY>\"); // Your API key\n\nSites sites = new Sites(client);\n\nsites.updateVariable(\n \"<SITE_ID>\", // siteId\n \"<VARIABLE_ID>\", // variableId\n \"<KEY>\", // key\n \"<VALUE>\", // value (optional)\n false, // secret (optional)\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(result);\n })\n);\n```\n```server-swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<YOUR_PROJECT_ID>\") // Your project ID\n .setKey(\"<YOUR_API_KEY>\") // Your API key\n\nlet sites = Sites(client)\n\nlet variable = try await sites.updateVariable(\n siteId: \"<SITE_ID>\",\n variableId: \"<VARIABLE_ID>\",\n key: \"<KEY>\",\n value: \"<VALUE>\", // optional\n secret: false // optional\n)\n```\n```server-go\npackage main\n\nimport (\n \"fmt\"\n \"github.com/appwrite/sdk-for-go/appwrite\"\n)\n\nclient := appwrite.NewClient(\n appwrite.WithEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\"),\n appwrite.WithProject(\"<YOUR_PROJECT_ID>\"),\n appwrite.WithKey(\"<YOUR_API_KEY>\"),\n)\n\nsites := appwrite.NewSites(client)\n\nresponse, error := sites.UpdateVariable(\n \"<SITE_ID>\",\n \"<VARIABLE_ID>\",\n \"<KEY>\",\n appwrite.WithUpdateVariableValue(\"<VALUE>\"),\n appwrite.WithUpdateVariableSecret(false),\n)\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::Sites;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n let client = Client::new();\n client.set_endpoint(\"https://<REGION>.cloud.appwrite.io/v1\"); // Your API Endpoint\n client.set_project(\"<YOUR_PROJECT_ID>\"); // Your project ID\n client.set_key(\"<YOUR_API_KEY>\"); // Your API key\n\n let sites = Sites::new(&client);\n\n let result = sites.update_variable(\n \"<SITE_ID>\",\n \"<VARIABLE_ID>\",\n \"<KEY>\",\n Some(\"<VALUE>\"), // optional\n Some(false) // optional\n ).await?;\n\n let _ = result;\n\n Ok(())\n}\n```\n{% /multicode %}\n\n## Delete a variable {% #delete-variable %}\n\n{% multicode %}\n```server-nodejs\nimport { Client, Sites } from 'node-appwrite';\n\nconst client = new Client()\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<YOUR_PROJECT_ID>') // Your project ID\n .setKey('<YOUR_API_KEY>'); // Your API key\n\nconst sites = new Sites(client);\n\nconst result = await sites.deleteVariable({\n siteId: '<SITE_ID>',\n variableId: '<VARIABLE_ID>'\n});\n```\n```server-deno\nimport { Client, Sites } from \"npm:node-appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<YOUR_PROJECT_ID>') // Your project ID\n .setKey('<YOUR_API_KEY>'); // Your API key\n\nconst sites = new Sites(client);\n\nconst result = await sites.deleteVariable({\n siteId: '<SITE_ID>',\n variableId: '<VARIABLE_ID>'\n});\n```\n```server-php\n<?php\n\nuse Appwrite\\Client;\nuse Appwrite\\Services\\Sites;\n\n$client = (new Client())\n ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n ->setProject('<YOUR_PROJECT_ID>') // Your project ID\n ->setKey('<YOUR_API_KEY>'); // Your API key\n\n$sites = new Sites($client);\n\n$result = $sites->deleteVariable(\n siteId: '<SITE_ID>',\n variableId: '<VARIABLE_ID>'\n);\n```\n```server-python\nfrom appwrite.client import Client\nfrom appwrite.services.sites import Sites\n\nclient = Client()\nclient.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\nclient.set_project('<YOUR_PROJECT_ID>') # Your project ID\nclient.set_key('<YOUR_API_KEY>') # Your API key\n\nsites = Sites(client)\n\nresult = sites.delete_variable(\n site_id = '<SITE_ID>',\n variable_id = '<VARIABLE_ID>'\n)\n```\n```server-ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('<YOUR_PROJECT_ID>') # Your project ID\n .set_key('<YOUR_API_KEY>') # Your API key\n\nsites = Sites.new(client)\n\nresult = sites.delete_variable(\n site_id: '<SITE_ID>',\n variable_id: '<VARIABLE_ID>'\n)\n```\n```server-dotnet\nusing Appwrite;\nusing Appwrite.Models;\nusing Appwrite.Services;\n\nClient client = new Client()\n .SetEndPoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .SetProject(\"<YOUR_PROJECT_ID>\") // Your project ID\n .SetKey(\"<YOUR_API_KEY>\"); // Your API key\n\nSites sites = new Sites(client);\n\nawait sites.DeleteVariable(\n siteId: \"<SITE_ID>\",\n variableId: \"<VARIABLE_ID>\"\n);\n```\n```server-dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\n\nClient client = Client()\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<YOUR_PROJECT_ID>') // Your project ID\n .setKey('<YOUR_API_KEY>'); // Your API key\n\nSites sites = Sites(client);\n\nawait sites.deleteVariable(\n siteId: '<SITE_ID>',\n variableId: '<VARIABLE_ID>',\n);\n```\n```server-kotlin\nimport io.appwrite.Client\nimport io.appwrite.coroutines.CoroutineCallback\nimport io.appwrite.services.Sites\n\nval client = Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<YOUR_PROJECT_ID>\") // Your project ID\n .setKey(\"<YOUR_API_KEY>\") // Your API key\n\nval sites = Sites(client)\n\nval response = sites.deleteVariable(\n siteId = \"<SITE_ID>\",\n variableId = \"<VARIABLE_ID>\"\n)\n```\n```server-java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Sites;\n\nClient client = new Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<YOUR_PROJECT_ID>\") // Your project ID\n .setKey(\"<YOUR_API_KEY>\"); // Your API key\n\nSites sites = new Sites(client);\n\nsites.deleteVariable(\n \"<SITE_ID>\", // siteId\n \"<VARIABLE_ID>\", // variableId\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(result);\n })\n);\n```\n```server-swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<YOUR_PROJECT_ID>\") // Your project ID\n .setKey(\"<YOUR_API_KEY>\") // Your API key\n\nlet sites = Sites(client)\n\nlet result = try await sites.deleteVariable(\n siteId: \"<SITE_ID>\",\n variableId: \"<VARIABLE_ID>\"\n)\n```\n```server-go\npackage main\n\nimport (\n \"fmt\"\n \"github.com/appwrite/sdk-for-go/appwrite\"\n)\n\nclient := appwrite.NewClient(\n appwrite.WithEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\"),\n appwrite.WithProject(\"<YOUR_PROJECT_ID>\"),\n appwrite.WithKey(\"<YOUR_API_KEY>\"),\n)\n\nsites := appwrite.NewSites(client)\n\nresponse, error := sites.DeleteVariable(\n \"<SITE_ID>\",\n \"<VARIABLE_ID>\",\n)\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::Sites;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n let client = Client::new();\n client.set_endpoint(\"https://<REGION>.cloud.appwrite.io/v1\"); // Your API Endpoint\n client.set_project(\"<YOUR_PROJECT_ID>\"); // Your project ID\n client.set_key(\"<YOUR_API_KEY>\"); // Your API key\n\n let sites = Sites::new(&client);\n\n sites.delete_variable(\n \"<SITE_ID>\",\n \"<VARIABLE_ID>\"\n ).await?;\n\n Ok(())\n}\n```\n{% /multicode %}\n\n# Read variables in your site {% #read-variables %}\n\nRead 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.\n\nFor framework-specific guidance such as Next.js, SvelteKit, or Astro, see the [framework adapters](/docs/products/sites/frameworks).\n\n# Appwrite-injected variables {% #appwrite-variables %}\n\nAppwrite 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.\n\n{% partial file=\"sites-env-vars.md\" /%}\n\n# Secret variables {% #secret-variables %}\n\nMark 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.\n\nYou 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.\n\n# Limits {% #limits %}\n\n| Field | Limit |\n|----------------|------------------------------------------------|\n| Variable ID | 36 characters, `a-z A-Z 0-9 . - _` |\n| Key | 255 characters |\n| Value | 8192 characters |"}, {"path": "docs/products/sites/frameworks", "title": "Frameworks", "description": "Discover which frameworks are supported out-of-the-box by Appwrite Sites.", "content": "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.\n\n# Zero-configuration approach\n\nAppwrite Sites uses a zero-config approach to make deployments as frictionless as possible. When you deploy a project, Appwrite:\n\n1. Automatically detects your framework based on your package dependencies and configuration files (like `next.config.js`, `nuxt.config.js`, etc.)\n2. Selects one of the [SSR](/docs/products/sites/rendering/ssr) or [Static](/docs/products/sites/rendering/static) rendering strategies\n3. Sets up the appropriate install command, build command, and output directory\n\nThis means you can focus on building your application while Appwrite handles the deployment complexities.\n\n# Supported frameworks\n\n{% table %}\n-   {% width=48 %}\n- Framework\n- Rendering strategy\n\n---\n\n- {% icon icon=\"nextjs\" size=\"m\" /%}\n- [**Next.js**](/docs/products/sites/quick-start/nextjs)\n- `SSR` `Static`\n\n---\n\n- {% icon icon=\"nuxt\" size=\"m\" /%}\n- [**Nuxt**](/docs/products/sites/quick-start/nuxt)\n- `SSR` `Static`\n\n---\n\n- {% icon icon=\"svelte\" size=\"m\" /%}\n- [**SvelteKit**](/docs/products/sites/quick-start/sveltekit)\n- `SSR` `Static`\n\n---\n\n- {% icon icon=\"angular\" size=\"m\" /%}\n- [**Angular**](/docs/products/sites/quick-start/angular)\n- `SSR` `Static`\n\n---\n\n- {% icon icon=\"astro\" size=\"m\" /%}\n- [**Astro**](/docs/products/sites/quick-start/astro)\n- `SSR` `Static`\n\n---\n\n- {% only_dark %}{% icon_image src=\"/images/platforms/dark/remix.svg\" alt=\"Remix logo\" size=\"m\" /%}{% /only_dark %}\n{% only_light %}{% icon_image src=\"/images/platforms/light/remix.svg\" alt=\"Remix logo\" size=\"m\" /%}{% /only_light %}\n- [**Remix**](/docs/products/sites/quick-start/remix)\n- `SSR` `Static`\n\n---\n\n- {% only_dark %}{% icon_image src=\"/images/platforms/dark/tanstack.svg\" alt=\"TanStack Start logo\" size=\"m\" /%}{% /only_dark %}\n{% only_light %}{% icon_image src=\"/images/platforms/light/tanstack.svg\" alt=\"TanStack Start logo\" size=\"m\" /%}{% /only_light %}\n- [**TanStack Start**](/docs/products/sites/quick-start/tanstack-start)\n- `SSR` `Static`\n\n---\n- {% icon icon=\"flutter\" size=\"m\" /%}\n- [**Flutter Web**](/docs/products/sites/quick-start/flutter)\n- `Static`\n\n---\n\n- {% icon icon=\"react-native\" size=\"m\" /%}\n- [**React Native**](/docs/products/sites/quick-start/react-native)\n- `Static`\n\n---\n\n- {% icon icon=\"js\" size=\"m\" /%}\n- [**Other JavaScript**](/docs/products/sites/quick-start/vanilla)\n- `Static`\n\n---\n{% /table %}\n\n## Using unsupported frameworks\n\nEven 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:\n\n1. **Manual configuration**: You can specify a custom install command, build command, and output directory in your build setting by selecting the **Other** framework option.\n2. **Static builds**: Most JavaScript frameworks (and some non-JavaScript ones) can generate static builds that work with Appwrite Sites."}, {"path": "docs/products/sites/instant-rollbacks", "title": "Instant rollbacks", "description": "Safely revert a site to a previous deployment using instant rollbacks.", "content": "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.\n\nInstant 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.\n\n# Use instant rollbacks\n\nTo use the instant rollback feature, follow these steps:\n\n1. Navigate to your site on Appwrite Console.\n2. Under the **Overview** tab, click on the **Instant Rollback** button.\n3. Once the modal opens, click on the **Rollback** button.\n\n{% only_dark %}\n![Instant rollback](/images/docs/sites/dark/instant-rollback.avif)\n{% /only_dark %}\n{% only_light %}\n![Instant rollback](/images/docs/sites/instant-rollback.avif)\n{% /only_light %}\n\nThis will allow you to select a previously active, ready deployment to promote to currently active state."}, {"path": "docs/products/sites/logs", "title": "Logs", "description": "Learn how Appwrite Sites handles logs", "content": "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.\n\n# Logs table\n\nIn your site's **Logs** tab, you will see a table of your recent logs. The following information is shown in this table:\n\n| Column | Description |\n| --- | --- |\n| Log ID | Unique identifier for each log |\n| Status code | The HTTP status of the request |\n| Created | Timestamp of when the log was created |\n| Method | The HTTP method used to create the request |\n| Path | The URL path the request was made to |\n| Duration | The time taken for the request |\n\n# Log details\n\nWhen you click on a log, you will be shown a set of log details.\n\n{% only_dark %}\n![Log details](/images/docs/sites/dark/log-details.avif)\n{% /only_dark %}\n{% only_light %}\n![Log details](/images/docs/sites/log-details.avif)\n{% /only_light %}\n\nYou can find both request and response information, including parameters and headers.\n\n{% info title=\"Response logs for SSR apps\" %}\n\nIf your app uses **SSR hosting** on Appwrite Sites, you can also observe `console.log` and `console.error` outputs in the response logs.\n\n{% /info %}\n\n# Disable logs\n\nYou 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:\n\n1. Navigate to your site on Appwrite Console.\n2. Under the **Settings** tab, find the **Logging** section.\n3. Disable logs and click on the **Update** button.\n\n{% only_dark %}\n![Logging settings](/images/docs/sites/dark/logging-settings.avif)\n{% /only_dark %}\n{% only_light %}\n![Logging settings](/images/docs/sites/logging-settings.avif)\n{% /only_light %}\n\n# Log retention\n\nLogs 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.\n\nIf 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."}, {"path": "docs/products/sites/migrations/vercel", "title": "Migrating from Vercel to Appwrite Sites", "description": "A step-by-step guide to migrate your web applications from Vercel to Appwrite Sites.", "content": "This guide walks you through migrating from Vercel to Appwrite Sites, covering project setup, configuration, routing, and serverless functionality.\n\n# Prerequisites\n\nBefore starting your migration:\n\n- Have access to your Vercel project dashboard\n- Ensure you can modify your domain's DNS settings\n- Prepare your source code repository\n\n# Platform differences\n\nUnderstanding the key differences between Vercel and Appwrite Sites will help you plan your migration effectively.\n\n{% table %}\n- Feature\n- Vercel\n- Appwrite Sites\n\n---\n\n- DNS configuration\n- Uses A records for apex domains\n- Uses NS records for apex domains\n\n---\n\n- Configuration approach\n- Platform-level configuration via vercel.json\n- Framework-native configuration with SSR support\n\n---\n\n- Redirects\n- Platform-level path redirects via vercel.json\n- Domain-level redirects and framework-level path redirects\n{% /table %}\n\n# Migration process\n\nFollow these steps to move your application from Vercel to Appwrite Sites.\n\n{% section id=\"create-project\" step=1 title=\"Create an Appwrite project\" %}\nStart by setting up a new project in Appwrite:\n\n1. Sign in to the [Appwrite Console](https://cloud.appwrite.io)\n2. Click **Create Project**\n3. Enter a name for your project and click **Create**\n\nYour new project will serve as the container for your migrated site and any other Appwrite services you might need.\n{% /section %}\n\n{% section id=\"connect-repo\" step=2 title=\"Connect your repository\" %}\nNext, create a new site by connecting your existing repository:\n\n1. In your Appwrite project, select **Sites** from the sidebar\n2. Click **Create Site**\n3. Select **Connect a repository**\n4. Authenticate with GitHub\n5. Select the repository containing your Vercel project\n6. Choose your production branch (typically `main`)\n\nAppwrite will auto-detect your framework. Verify this is correct or select manually from the dropdown menu.\n{% /section %}\n\n# Configure your domain\n\nOne 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.\n\n# Migrating an apex domain\n\n{% section id=\"prepare-vercel-domain\" step=1 title=\"Prepare your Vercel domain\" %}\nBefore migrating, row your current Vercel DNS configuration:\n\n1. In Vercel, go to project settings > Domains\n2. Note any custom DNS records you've configured (MX, TXT, etc.)\n3. Don't remove the domain from Vercel until Appwrite is fully configured\n{% /section %}\n\n{% section id=\"add-apex-domain\" step=2 title=\"Add domain to Appwrite Sites\" %}\nAdd your apex domain to your Appwrite site:\n\n1. Navigate to your site > **Domains** tab\n2. Click **Add domain**\n3. Enter your apex domain (e.g., `example.com`)\n4. Select **Active deployment** as the domain rule type\n5. Note the NS records Appwrite provides:\n - `ns1.appwrite.zone`\n - `ns2.appwrite.zone`\n{% /section %}\n\n{% section id=\"update-nameservers\" step=3 title=\"Update nameservers at your registrar\" %}\nAt your domain registrar:\n\n1. Update your domain's nameservers to point to Appwrite's nameservers\n2. If you had custom DNS records in Vercel (like MX records for email), you'll need to recreate these in Appwrite's DNS configuration\n\n{% info title=\"Important DNS change note\" %}\nChanging nameservers delegates your entire domain's DNS management to Appwrite. DNS changes can take up to 48 hours to fully propagate.\n{% /info %}\n{% /section %}\n\n# Migrating a subdomain\n\nFor subdomains, both Vercel and Appwrite use CNAME records, making the migration process simpler.\n\n{% section id=\"add-subdomain\" step=1 title=\"Add subdomain to Appwrite Sites\" %}\n1. Navigate to your site > **Domains** tab\n2. Click **Add domain**\n3. Enter your subdomain (e.g., `www.example.com`)\n4. Select **Active deployment** as the domain rule type\n5. Copy the CNAME value provided by Appwrite\n{% /section %}\n\n{% section id=\"update-dns-records\" step=2 title=\"Update DNS records\" %}\nAt your domain registrar or DNS provider:\n\n1. Create or update the CNAME record for your subdomain\n2. Point it to the value provided by Appwrite\n3. Wait for DNS propagation and verification\n{% /section %}\n\n# Domain rule types\n\nWhen adding a domain in Appwrite, you can choose from three rule types: Active deployment, Git branch, or Redirect.\n\n{% arrow_link href=\"/docs/products/sites/domains#domain-rule-types\" %}\nLearn more about domain rule types\n{% /arrow_link %}\n\n\n# Configure build settings\n\nAfter setting up your project and domain, you'll need to configure your build settings to match your Vercel configuration.\n\n{% section id=\"build-settings\" step=1 title=\"Set up build configuration\" %}\nNavigate to your site > **Settings** > **Build settings** and configure the following:\n\n- **Framework:** Appwrite will attempt to automatically detect your framework. Verify and ensure it is the same framework as in Vercel.\n- **Install command:** Enter the same install command from Vercel\n- **Build command:** Enter the same build command from Vercel\n- **Output directory:** Enter the same output directory from Vercel\n- **Root directory:** If your app is in a monorepo subdirectory, specify the path\n- **Rendering:** Select the appropriate rendering mode (Static or SSR)\n\n{% only_dark %}\n![Build settings](/images/docs/sites/dark/build-settings-install-command.avif)\n{% /only_dark %}\n{% only_light %}\n![Build settings](/images/docs/sites/build-settings-install-command.avif)\n{% /only_light %}\n{% /section %}\n\n# Framework defaults\n\nAppwrite automatically detects and applies default settings for popular frameworks like Next.js, Nuxt, SvelteKit, Angular, and Astro.\n\n{% arrow_link href=\"/docs/products/sites/develop#project-dependencies\" %}\nLearn more about framework defaults and project dependencies\n{% /arrow_link %}\n\n# Manage environment variables\n\nEnvironment variables require special attention during migration, as they control how your application behaves in different environments.\n\n{% section id=\"gather-env-vars\" step=1 title=\"Gather variables from Vercel\" %}\nBefore migrating, row all your Vercel environment variables:\n\n1. In Vercel, go to your project settings > **Environment Variables**\n2. Row all variables, noting which are for development, preview, or production\n3. Identify any system variables your application relies on\n\n{% info title=\"System variables\" %}\nVercel automatically provides system variables like `VERCEL_URL`. You'll need to adapt your code to use Appwrite's equivalent system variables.\n{% /info %}\n{% /section %}\n\n{% section id=\"set-env-vars\" step=2 title=\"Set variables in Appwrite\" %}\n1. Navigate to your site > **Settings** > **Environment variables**\n2. Click the **plus (+)** icon to add a new variable\n3. Enter the key and value for each variable. You can optionally import a `.env` file.\n4. Toggle **Secret** for sensitive variables that should be hidden\n\n{% only_dark %}\n![Environment variables](/images/docs/sites/dark/env-variables.avif)\n{% /only_dark %}\n{% only_light %}\n![Environment variables](/images/docs/sites/env-variables.avif)\n{% /only_light %}\n{% /section %}\n\n# Appwrite system variables\nAppwrite automatically injects these variables into your site:\n\n{% partial file=\"sites-env-vars.md\" /%}\n\n# Handle redirects and rewrites\n\nOne key difference between Vercel and Appwrite is how they handle redirects and rewrites.\n\n{% info title=\"Key difference\" %}\nVercel 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.\n{% /info %}\n\n{% section id=\"domain-redirects\" step=1 title=\"Domain-level redirects\" %}\nAppwrite's domain-level redirects are configured in the Domains tab of your site:\n\n1. Navigate to your site > **Domains** tab\n2. Click **Add domain** or select an existing domain\n3. Choose **Redirect** as the domain rule type\n4. Enter the destination URL and select an appropriate HTTP status code (301, 302, 303, 307, or 308)\n\nDomain 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`).\n{% /section %}\n\n{% section id=\"framework-redirects\" step=2 title=\"Framework-level redirects\" %}\nFor path-based redirects, use your framework's built-in functionality:\n\n# Next.js\n```javascript\n// next.config.js\nmodule.exports = {\n async redirects() {\n return [\n {\n source: '/old-path',\n destination: '/new-path',\n permanent: true, // 308 status code\n },\n ];\n },\n};\n```\n\n# SvelteKit\n```javascript\n// src/routes/+layout.server.js\nexport function load({ url }) {\n if (url.pathname === '/old-path') {\n return {\n status: 301,\n redirect: '/new-path'\n };\n }\n}\n```\n\n# Nuxt\n```javascript\n// nuxt.config.js\nexport default {\n router: {\n extendRoutes(routes, resolve) {\n routes.push({\n path: '/old-path',\n redirect: {\n to: '/new-path',\n statusCode: 301\n }\n });\n }\n }\n}\n```\n{% /section %}\n\n# Migrate serverless functions\n\nThere are two approaches to serverless functions when migrating from Vercel to Appwrite:\n\n{% info title=\"Framework API routes vs. standalone functions\" %}\nWhen 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).\n{% /info %}\n\n{% section id=\"framework-api-routes\" step=1 title=\"Framework API routes\" %}\nFor frameworks with built-in API routes:\n\n1. **Next.js**: API routes in `/pages/api` or route handlers in `/app/api` work natively\n2. **Nuxt**: Server API endpoints work as expected\n3. **SvelteKit**: Server routes and API endpoints function normally\n\nNo migration is needed for these framework-native API routes - they'll work automatically when you deploy your site with SSR enabled.\n{% /section %}\n\n{% section id=\"standalone-functions\" step=2 title=\"Standalone Appwrite Functions\" %}\nFor standalone serverless functions or more complex use cases:\n\n1. In your Appwrite project, go to **Functions**\n2. Click **Create Function**\n3. Select a runtime that matches your needs (Node.js, Python, PHP, Ruby, etc.)\n4. Create your function code\n5. Deploy your function\n{% /section %}\n\n{% arrow_link href=\"/docs/products/functions/quick-start\" %}\nLearn how to create and deploy functions\n{% /arrow_link %}\n\n{% section id=\"update-endpoints\" step=3 title=\"API endpoints usage\" %}\n\n# Framework API routes\nWhen using framework API routes within Appwrite Sites (with SSR enabled), your endpoints remain the same:\n\n- Vercel: `/api/hello`\n- Appwrite Sites: `/api/hello` (no change needed)\n\nYour existing API routes code will work without modification:\n\n```javascript\n// /api/hello.js in Next.js (works the same in both Vercel and Appwrite Sites)\nexport default function handler(req, res) {\n res.status(200).json({ message: 'Hello!' });\n}\n```\n\n# Standalone Appwrite Functions\nIf 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:\n\n```javascript\n// Calling an Appwrite Function from your frontend\nimport { Client, Functions } from 'appwrite';\n\nconst client = new Client()\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')\n .setProject('your-project-id');\n\nconst functions = new Functions(client);\n\nconst response = await functions.createExecution({\n functionId: 'your-function-id'\n});\n```\n{% /section %}\n\n# Handle middleware\n\nWhile 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.\n\n{% section id=\"framework-middleware\" step=1 title=\"Framework-native middleware\" %}\n# Next.js\n```javascript\n// middleware.js\nexport function middleware(request) {\n // Your middleware logic\n}\n\nexport const config = {\n matcher: '/path/:path*',\n};\n```\n\n# SvelteKit\n```javascript\n// src/hooks.server.js\nexport async function handle({ event, resolve }) {\n // Your middleware logic before response\n const response = await resolve(event);\n // Your middleware logic after response\n return response;\n}\n```\n\n# Nuxt\n```javascript\n// server/middleware/example.js\nexport default defineEventHandler((event) => {\n // Your middleware logic\n});\n```\n{% /section %}\n\n# Next steps\n\nAfter completing your migration from Vercel to Appwrite Sites, we recommend:\n\n1. **Test thoroughly** - Verify all routes, functionality, and environment-specific features\n2. **Monitor performance** - Check that your site performs as expected on Appwrite\n3. **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.\n4. **Explore Appwrite services** - Consider integrating with other Appwrite services like [Authentication](/docs/products/auth), [Databases](/docs/products/databases), and [Storage](/docs/products/storage)\n\n# Conclusion\n\nThis 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.\n\nWhile domain configuration and platform-specific features like middleware require some adaptation, the framework-native approaches detailed in this guide help ensure a smooth transition.\n\nFor additional help, refer to the [Sites documentation](/docs/products/sites) or reach out to the Appwrite community on [Discord](https://appwrite.io/discord)."}, {"path": "docs/products/sites/previews", "title": "Previews", "description": "Preview site deployments to test changes before promoting to production.", "content": "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.\n\n# Visit preview deployments\n\nTo access a preview deployment, follow these steps:\n\n1. Navigate to your site on Appwrite Console.\n2. Under the **Deployments** tab, click on a ready deployment.\n3. Click on the **Visit** button. This preview URL is also visible under the **Domains** section.\n\n{% only_dark %}\n![Preview deployment](/images/docs/sites/dark/preview-deployment.avif)\n{% /only_dark %}\n{% only_light %}\n![Preview deployment](/images/docs/sites/preview-deployment.avif)\n{% /only_light %}\n\nAppwrite 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.\n\n## Deployments for GitHub\n\nAside 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.\n\nYou can also directly access the branch and commit that these deployments are created from, by clicking on **GitHub** under the **Source** section.\n\n{% only_dark %}\n![GitHub as source](/images/docs/sites/dark/github-source.avif)\n{% /only_dark %}\n{% only_light %}\n![GitHub as source](/images/docs/sites/github-source.avif)\n{% /only_light %}"}, {"path": "docs/products/sites/quick-start", "title": "Start with Sites", "description": "Get started quickly with Appwrite Sites. Follow a step-by-step guide to create your first Appwrite Site and deploy a web app.", "content": "# Start with Sites\n\nYou can create and execute your first Appwrite Site in minutes.\n\n# Create site\n\nBefore deploying your web app with Git, create a new Site attached to your GitHub repository.\n\n{% only_dark %}\n![Create site wizard](/images/docs/sites/dark/create-site-wizard.avif)\n{% /only_dark %}\n{% only_light %}\n![Create site wizard](/images/docs/sites/create-site-wizard.avif)\n{% /only_light %}\n\n1. In the Appwrite Console's sidebar, click **Sites**.\n2. Click on the **Create site** button.\n3. After clicking on **Connect Git repository**, select your repository.\n4. After connecting to GitHub, (optionally) add a name and site ID.\n5. Verify that the correct framework is selected.\n6. 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.\n7. Add any environment variables required by the site.\n6. 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.\n\n# Framework quick-starts\n\nLearn how to quickly setup a web app developed using any of the following frameworks and deploy it on Appwrite Sites.\n\n{% cards %}\n\n{% cards_item href=\"/docs/products/sites/quick-start/nextjs\" title=\"Next.js\" icon=\"icon-nextjs\" %}\n{% /cards_item %}\n\n{% cards_item href=\"/docs/products/sites/quick-start/nuxt\" title=\"Nuxt\" icon=\"icon-nuxt\" %}\n{% /cards_item %}\n\n{% cards_item href=\"/docs/products/sites/quick-start/sveltekit\" title=\"SvelteKit\" icon=\"icon-svelte\" %}\n{% /cards_item %}\n\n{% cards_item href=\"/docs/products/sites/quick-start/angular\" title=\"Angular\" icon=\"icon-angular\" %}\n{% /cards_item %}\n\n{% cards_item href=\"/docs/products/sites/quick-start/remix\" title=\"Remix\" icon=\"web-icon-remix\" %}\n{% /cards_item %}\n\n{% cards_item href=\"/docs/products/sites/quick-start/astro\" title=\"Astro\" icon=\"icon-astro\" %}\n{% /cards_item %}\n\n{% cards_item href=\"/docs/products/sites/quick-start/react\" title=\"React\" icon=\"icon-react\" %}\n{% /cards_item %}\n\n{% cards_item href=\"/docs/products/sites/quick-start/vue\" title=\"Vue.js\" icon=\"web-icon-vue\" %}\n{% /cards_item %}\n\n{% cards_item href=\"/docs/products/sites/quick-start/flutter\" title=\"Flutter\" icon=\"icon-flutter\" %}\n{% /cards_item %}\n\n{% cards_item href=\"/docs/products/sites/quick-start/react-native\" title=\"React Native\" icon=\"icon-react-native\" %}\n{% /cards_item %}\n\n{% cards_item href=\"/docs/products/sites/quick-start/vanilla\" title=\"Vanilla JS\" icon=\"icon-js\" %}\n{% /cards_item %}\n\n{% cards_item href=\"/docs/products/sites/quick-start/tanstack-start\" title=\"TanStack Start\" icon=\"web-icon-tanstack\" %}\n{% /cards_item %}\n\n{% /cards %}\n\n# Explore\n\nUse your first site as a springboard to explore the flexible and powerful features of Appwrite Sites.\n\n{% cards %}\n\n{% cards_item href=\"/docs/products/sites/templates\" title=\"Templates\" %}\nGet a template site up and running with a single click.\n{% /cards_item %}\n\n{% cards_item href=\"/docs/products/sites/develop\" title=\"Develop\" %}\nLearn about developing your own Appwrite Site.\n{% /cards_item %}\n\n{% cards_item href=\"/docs/products/sites/deploy-from-git\" title=\"Deploy\" %}\nConfigure and deploy your Appwrite Site from Git.\n{% /cards_item %}\n\n{% cards_item href=\"/docs/products/sites/frameworks\" title=\"Frameworks\" %}\nLearn which frameworks are supported by Appwrite Sites.\n{% /cards_item %}\n\n{% /cards %}"}, {"path": "docs/products/sites/quick-start/angular", "title": "Deploy an Angular app to Appwrite Sites", "description": "Learn how to setup and deploy Angular apps on Appwrite Sites.", "content": "{% section #step-1 step=1 title=\"Create Angular app\" %}\n\nFirst, you must either create an Angular app or setup the [Angular starter template](https://github.com/appwrite/templates-for-sites).\n\nOpen your terminal, and run the following command.\n\n```bash\nnpm install -g @angular/cli@17\nng new my-app\n```\n\nPush this project to a [GitHub repository](https://github.com/new).\n{% /section %}\n\n{% section #step-2 step=2 title=\"Create Appwrite project\" %}\nHead to the [Appwrite Console](https://cloud.appwrite.io).\n\n{% only_dark %}\n![Create Appwrite project](/images/docs/quick-starts/dark/create-project.avif)\n{% /only_dark %}\n{% only_light %}\n![Create Appwrite project](/images/docs/quick-starts/create-project.avif)\n{% /only_light %}\n\nIf this is your first time using Appwrite, create an account and create your first project.\n{% /section %}\n\n{% section #step-3 step=3 title=\"Create site\" %}\nHead to the **Sites** page in your Appwrite project, click on the **Create site** button, and select **Connect a repository**.\n\nConnect your GitHub account and select the repository you intend to deploy (or allow all repositories, for future ease).\n\n1. Select the **production branch** and **root directory** from your repo.\n2. Verify that the **correct framework** is selected. In case an incorrect framework is visible, pick **Angular** from the drop-down list. \n3. Confirm the **install command**, **build command**, and **output directory** in the build settings. The default build settings for Angular are:\n - **Install command:** `npm install`\n - **Build command:** `npm run build`\n - **Output directory:** `./dist/angular/browser`\n4. Add any **environment variables** required by the site. This is not necessary if you're deploying the starter app.\n\nClick on the **Deploy** button.\n{% /section %}\n\n{% section #step-4 step=4 title=\"Visit site\" %}\nAfter successful deployment, click on the **Visit site** button.\n\n{% /section %}"}, {"path": "docs/products/sites/quick-start/astro", "title": "Deploy a Astro app to Appwrite Sites", "description": "Learn how to setup and deploy Astro apps on Appwrite Sites.", "content": "{% section #step-1 step=1 title=\"Create Astro app\" %}\n\nFirst, you must either create an Astro app or setup the [Astro starter template](https://github.com/appwrite/templates-for-sites).\n\nOpen your terminal, and run the following command.\n\n```bash\nnpm create astro@latest\n```\n\nPush this project to a [GitHub repository](https://github.com/new).\n{% /section %}\n\n{% section #step-2 step=2 title=\"Create Appwrite project\" %}\nHead to the [Appwrite Console](https://cloud.appwrite.io).\n\n{% only_dark %}\n![Create Appwrite project](/images/docs/quick-starts/dark/create-project.avif)\n{% /only_dark %}\n{% only_light %}\n![Create Appwrite project](/images/docs/quick-starts/create-project.avif)\n{% /only_light %}\n\nIf this is your first time using Appwrite, create an account and create your first project.\n{% /section %}\n\n{% section #step-3 step=3 title=\"Create site\" %}\nHead to the **Sites** page in your Appwrite project, click on the **Create site** button, and select **Connect a repository**.\n\nConnect your GitHub account and select the repository you intend to deploy (or allow all repositories, for future ease).\n\n1. Select the **production branch** and **root directory** from your repo.\n2. Verify that the **correct framework** is selected. In case an incorrect framework is visible, pick **Astro** from the drop-down list. \n3. Confirm the **install command**, **build command**, and **output directory** in the build settings. The default build settings for Astro are:\n - **Install command:** `npm install`\n - **Build command:** `npm run build`\n - **Output directory:** `./dist`\n4. Add any **environment variables** required by the site. This is not necessary if you're deploying the starter app.\n\nClick on the **Deploy** button.\n{% /section %}\n\n{% section #step-4 step=4 title=\"Visit site\" %}\nAfter successful deployment, click on the **Visit site** button.\n\n{% /section %}"}, {"path": "docs/products/sites/quick-start/flutter", "title": "Deploy a Flutter Web app to Appwrite Sites", "description": "Learn how to setup and deploy Flutter Web apps on Appwrite Sites.", "content": "{% section #step-1 step=1 title=\"Create Flutter Web app\" %}\n\nFirst, you must either create a Flutter Web app or setup the [Flutter Web starter template](https://github.com/appwrite/templates-for-sites).\n\nOpen your terminal, and run the following command.\n\n```bash\nflutter create my_app\n```\n\nIn 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:\n\n```bash\nflutter create . --platforms web\n```\n\nPush this project to a [GitHub repository](https://github.com/new).\n{% /section %}\n\n{% section #step-2 step=2 title=\"Create Appwrite project\" %}\nHead to the [Appwrite Console](https://cloud.appwrite.io).\n\n{% only_dark %}\n![Create Appwrite project](/images/docs/quick-starts/dark/create-project.avif)\n{% /only_dark %}\n{% only_light %}\n![Create Appwrite project](/images/docs/quick-starts/create-project.avif)\n{% /only_light %}\n\nIf this is your first time using Appwrite, create an account and create your first project.\n{% /section %}\n\n{% section #step-3 step=3 title=\"Create site\" %}\nHead to the **Sites** page in your Appwrite project, click on the **Create site** button, and select **Connect a repository**.\n\nConnect your GitHub account and select the repository you intend to deploy (or allow all repositories, for future ease).\n\n1. Select the **production branch** and **root directory** from your repo.\n2. Verify that the **correct framework** is selected. In case an incorrect framework is visible, pick **Flutter Web** from the drop-down list. \n3. Confirm the **install command**, **build command**, and **output directory** in the build settings. The default build settings for Flutter Web are:\n - **Install command:** `flutter pub get`\n - **Build command:** `flutter build web --release -t lib/main.dart`\n - **Output directory:** `./build/web`\n4. Add any **environment variables** required by the site. This is not necessary if you're deploying the starter app.\n\nClick on the **Deploy** button.\n{% /section %}\n\n{% section #step-4 step=4 title=\"Visit site\" %}\nAfter successful deployment, click on the **Visit site** button.\n\n{% /section %}"}, {"path": "docs/products/sites/quick-start/nextjs", "title": "Deploy a Next.js app to Appwrite Sites", "description": "Learn how to setup and deploy Next.js apps on Appwrite Sites.", "content": "{% section #step-1 step=1 title=\"Create Next.js app\" %}\n\n{% info title=\"Full Next.js support available\" %}\nAppwrite 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.\n{% /info %}\n\nFirst, you must either create a Next.js app or setup the [Next.js starter template](https://github.com/appwrite/templates-for-sites).\n\nOpen your terminal, and run the following command.\n\n```bash\nnpx create-next-app@latest\n```\n\nPush this project to a [GitHub repository](https://github.com/new).\n{% /section %}\n\n{% section #step-2 step=2 title=\"Create Appwrite project\" %}\nHead to the [Appwrite Console](https://cloud.appwrite.io).\n\n{% only_dark %}\n![Create Appwrite project](/images/docs/quick-starts/dark/create-project.avif)\n{% /only_dark %}\n{% only_light %}\n![Create Appwrite project](/images/docs/quick-starts/create-project.avif)\n{% /only_light %}\n\nIf this is your first time using Appwrite, create an account and create your first project.\n{% /section %}\n\n{% section #step-3 step=3 title=\"Create site\" %}\nHead to the **Sites** page in your Appwrite project, click on the **Create site** button, and select **Connect a repository**.\n\nConnect your GitHub account and select the repository you intend to deploy (or allow all repositories, for future ease).\n\n1. Select the **production branch** and **root directory** from your repo.\n2. Verify that the **correct framework** is selected. In case an incorrect framework is visible, pick **Next.js** from the drop-down list. \n3. Confirm the **install command**, **build command**, and **output directory** in the build settings. The default build settings for Next.js are:\n - **Install command:** `npm install`\n - **Build command:** `npm run build`\n - **Output directory:** `./.next`\n4. Add any **environment variables** required by the site. This is not necessary if you're deploying the starter app.\n\nClick on the **Deploy** button.\n{% /section %}\n\n{% section #step-4 step=4 title=\"Visit site\" %}\nAfter successful deployment, click on the **Visit site** button.\n\n{% /section %}"}, {"path": "docs/products/sites/quick-start/nuxt", "title": "Deploy a Nuxt app to Appwrite Sites", "description": "Learn how to setup and deploy Nuxt apps on Appwrite Sites.", "content": "{% section #step-1 step=1 title=\"Create Nuxt app\" %}\n\nFirst, you must either create a Nuxt app or setup the [Nuxt starter template](https://github.com/appwrite/templates-for-sites).\n\nOpen your terminal, and run the following command.\n\n```bash\nnpm create nuxt my-app\n```\n\nPush this project to a [GitHub repository](https://github.com/new).\n{% /section %}\n\n{% section #step-2 step=2 title=\"Create Appwrite project\" %}\nHead to the [Appwrite Console](https://cloud.appwrite.io).\n\n{% only_dark %}\n![Create Appwrite project](/images/docs/quick-starts/dark/create-project.avif)\n{% /only_dark %}\n{% only_light %}\n![Create Appwrite project](/images/docs/quick-starts/create-project.avif)\n{% /only_light %}\n\nIf this is your first time using Appwrite, create an account and create your first project.\n{% /section %}\n\n{% section #step-3 step=3 title=\"Create site\" %}\nHead to the **Sites** page in your Appwrite project, click on the **Create site** button, and select **Connect a repository**.\n\nConnect your GitHub account and select the repository you intend to deploy (or allow all repositories, for future ease).\n\n1. Select the **production branch** and **root directory** from your repo.\n2. Verify that the **correct framework** is selected. In case an incorrect framework is visible, pick **Nuxt** from the drop-down list. \n3. Confirm the **install command**, **build command**, and **output directory** in the build settings. The default build settings for Nuxt are:\n - **Install command:** `npm install`\n - **Build command:** `npm run build`\n - **Output directory:** `./.output`\n4. Add any **environment variables** required by the site. This is not necessary if you're deploying the starter app.\n\nClick on the **Deploy** button.\n{% /section %}\n\n{% section #step-4 step=4 title=\"Visit site\" %}\nAfter successful deployment, click on the **Visit site** button.\n\n{% /section %}"}, {"path": "docs/products/sites/quick-start/react", "title": "Deploy a React app to Appwrite Sites", "description": "Learn how to setup and deploy React apps on Appwrite Sites.", "content": "{% section #step-1 step=1 title=\"Create React app\" %}\n\nFirst, you must either create a React app or setup the [React starter template](https://github.com/appwrite/templates-for-sites).\n\nOpen your terminal, and run the following command.\n\n```bash\nnpm create vite@latest my-app -- --template react\n```\n\nPush this project to a [GitHub repository](https://github.com/new).\n{% /section %}\n\n{% section #step-2 step=2 title=\"Create Appwrite project\" %}\nHead to the [Appwrite Console](https://cloud.appwrite.io).\n\n{% only_dark %}\n![Create Appwrite project](/images/docs/quick-starts/dark/create-project.avif)\n{% /only_dark %}\n{% only_light %}\n![Create Appwrite project](/images/docs/quick-starts/create-project.avif)\n{% /only_light %}\n\nIf this is your first time using Appwrite, create an account and create your first project.\n{% /section %}\n\n{% section #step-3 step=3 title=\"Create site\" %}\nHead to the **Sites** page in your Appwrite project, click on the **Create site** button, and select **Connect a repository**.\n\nConnect your GitHub account and select the repository you intend to deploy (or allow all repositories, for future ease).\n\n1. Select the **production branch** and **root directory** from your repo.\n2. Verify that the **correct framework** is selected. In case an incorrect framework is visible, pick **React** from the drop-down list. \n3. Confirm the **install command**, **build command**, and **output directory** in the build settings. The default build settings for React are:\n - **Install command:** `npm install`\n - **Build command:** `npm run build`\n - **Output directory:** `./dist`\n4. Add any **environment variables** required by the site. This is not necessary if you're deploying the starter app.\n\nClick on the **Deploy** button.\n{% /section %}\n\n{% section #step-4 step=4 title=\"Visit site\" %}\nAfter successful deployment, click on the **Visit site** button.\n\n{% /section %}"}, {"path": "docs/products/sites/quick-start/react-native", "title": "Deploy a React Native app to Appwrite Sites", "description": "Learn how to setup and deploy React Native apps on Appwrite Sites.", "content": "{% section #step-1 step=1 title=\"Create React Native app\" %}\n\nFirst, you must either create a React Native app or setup the [React Native starter template](https://github.com/appwrite/templates-for-sites).\n\nOpen your terminal, and run the following command.\n\n```bash\nnpx create-expo-app my-app\n```\n\nOnce the app is created, navigate to the project directory, open the `package.json` file and add the following line under `scripts`:\n\n```json\n\"build\": \"expo export --platform web\"\n```\n\nPush this project to a [GitHub repository](https://github.com/new).\n{% /section %}\n\n{% section #step-2 step=2 title=\"Create Appwrite project\" %}\nHead to the [Appwrite Console](https://cloud.appwrite.io).\n\n{% only_dark %}\n![Create Appwrite project](/images/docs/quick-starts/dark/create-project.avif)\n{% /only_dark %}\n{% only_light %}\n![Create Appwrite project](/images/docs/quick-starts/create-project.avif)\n{% /only_light %}\n\nIf this is your first time using Appwrite, create an account and create your first project.\n{% /section %}\n\n{% section #step-3 step=3 title=\"Create site\" %}\nHead to the **Sites** page in your Appwrite project, click on the **Create site** button, and select **Connect a repository**.\n\nConnect your GitHub account and select the repository you intend to deploy (or allow all repositories, for future ease).\n\n1. Select the **production branch** and **root directory** from your repo.\n2. Verify that the **correct framework** is selected. In case an incorrect framework is visible, pick **React Native** from the drop-down list. \n3. Confirm the **install command**, **build command**, and **output directory** in the build settings. The default build settings for React Native are:\n - **Install command:** `npm install`\n - **Build command:** `npm run build`\n - **Output directory:** `./dist`\n4. Add any **environment variables** required by the site. This is not necessary if you're deploying the starter app.\n\nClick on the **Deploy** button.\n{% /section %}\n\n{% section #step-4 step=4 title=\"Visit site\" %}\nAfter successful deployment, click on the **Visit site** button.\n\n{% /section %}"}, {"path": "docs/products/sites/quick-start/remix", "title": "Deploy a Remix app to Appwrite Sites", "description": "Learn how to setup and deploy Remix apps on Appwrite Sites.", "content": "{% section #step-1 step=1 title=\"Create Remix app\" %}\n\nFirst, you must either create a Remix app or setup the [Remix starter template](https://github.com/appwrite/templates-for-sites).\n\nOpen your terminal, and run the following command.\n\n```bash\nnpx create-remix@latest\n```\n\nPush this project to a [GitHub repository](https://github.com/new).\n{% /section %}\n\n{% section #step-2 step=2 title=\"Create Appwrite project\" %}\nHead to the [Appwrite Console](https://cloud.appwrite.io).\n\n{% only_dark %}\n![Create Appwrite project](/images/docs/quick-starts/dark/create-project.avif)\n{% /only_dark %}\n{% only_light %}\n![Create Appwrite project](/images/docs/quick-starts/create-project.avif)\n{% /only_light %}\n\nIf this is your first time using Appwrite, create an account and create your first project.\n{% /section %}\n\n{% section #step-3 step=3 title=\"Create site\" %}\nHead to the **Sites** page in your Appwrite project, click on the **Create site** button, and select **Connect a repository**.\n\nConnect your GitHub account and select the repository you intend to deploy (or allow all repositories, for future ease).\n\n1. Select the **production branch** and **root directory** from your repo.\n2. Verify that the **correct framework** is selected. In case an incorrect framework is visible, pick **Remix** from the drop-down list. \n3. Confirm the **install command**, **build command**, and **output directory** in the build settings. The default build settings for Remix are:\n - **Install command:** `npm install`\n - **Build command:** `npm run build`\n - **Output directory:** `./build`\n4. Add any **environment variables** required by the site. This is not necessary if you're deploying the starter app.\n\nClick on the **Deploy** button.\n{% /section %}\n\n{% section #step-4 step=4 title=\"Visit site\" %}\nAfter successful deployment, click on the **Visit site** button.\n\n{% /section %}"}, {"path": "docs/products/sites/quick-start/sveltekit", "title": "Deploy a SvelteKit app to Appwrite Sites", "description": "Learn how to setup and deploy SvelteKit apps on Appwrite Sites.", "content": "{% section #step-1 step=1 title=\"Create SvelteKit app\" %}\n\nFirst, you must either create a SvelteKit app or setup the [SvelteKit starter template](https://github.com/appwrite/templates-for-sites).\n\nOpen your terminal, and run the following command.\n\n```bash\nnpx sv create\n```\n\nPush this project to a [GitHub repository](https://github.com/new).\n{% /section %}\n\n{% section #step-2 step=2 title=\"Create Appwrite project\" %}\nHead to the [Appwrite Console](https://cloud.appwrite.io).\n\n{% only_dark %}\n![Create Appwrite project](/images/docs/quick-starts/dark/create-project.avif)\n{% /only_dark %}\n{% only_light %}\n![Create Appwrite project](/images/docs/quick-starts/create-project.avif)\n{% /only_light %}\n\nIf this is your first time using Appwrite, create an account and create your first project.\n{% /section %}\n\n{% section #step-3 step=3 title=\"Create site\" %}\nHead to the **Sites** page in your Appwrite project, click on the **Create site** button, and select **Connect a repository**.\n\nConnect your GitHub account and select the repository you intend to deploy (or allow all repositories, for future ease).\n\n1. Select the **production branch** and **root directory** from your repo.\n2. Verify that the **correct framework** is selected. In case an incorrect framework is visible, pick **SvelteKit** from the drop-down list. \n3. Confirm the **install command**, **build command**, and **output directory** in the build settings. The default build settings for SvelteKit are:\n - **Install command:** `npm install`\n - **Build command:** `npm run build`\n - **Output directory:** `./build`\n4. Add any **environment variables** required by the site. This is not necessary if you're deploying the starter app.\n\nClick on the **Deploy** button.\n{% /section %}\n\n{% section #step-4 step=4 title=\"Visit site\" %}\nAfter successful deployment, click on the **Visit site** button.\n\n{% /section %}"}, {"path": "docs/products/sites/quick-start/tanstack-start", "title": "Deploy a TanStack Start app to Appwrite Sites", "description": "Learn how to setup and deploy TanStack Start apps on Appwrite Sites.", "content": "{% section #step-1 step=1 title=\"Create TanStack Start app\" %}\n\nFirst, you must either create a TanStack Start app or setup the [TanStack Start starter template](https://github.com/appwrite/templates-for-sites).\n\nOpen your terminal, and run the following command.\n\n```bash\nnpm create @tanstack/start@latest\n```\n\nPush this project to a [GitHub repository](https://github.com/new).\n{% /section %}\n\n{% section #step-2 step=2 title=\"Create Appwrite project\" %}\nHead to the [Appwrite Console](https://cloud.appwrite.io).\n\n{% only_dark %}\n![Create Appwrite project](/images/docs/quick-starts/dark/create-project.avif)\n{% /only_dark %}\n{% only_light %}\n![Create Appwrite project](/images/docs/quick-starts/create-project.avif)\n{% /only_light %}\n\nIf this is your first time using Appwrite, create an account and create your first project.\n{% /section %}\n\n{% section #step-3 step=3 title=\"Create site\" %}\nHead to the **Sites** page in your Appwrite project, click on the **Create site** button, and select **Connect a repository**.\n\nConnect your GitHub account and select the repository you intend to deploy (or allow all repositories, for future ease).\n\n1. Select the **production branch** and **root directory** from your repo.\n2. Verify that the **correct framework** is selected. In case an incorrect framework is visible, pick **TanStack Start** from the drop-down list. \n3. Confirm the **install command**, **build command**, and **output directory** in the build settings. The default build settings for TanStack Start are:\n - **Install command:** `npm install`\n - **Build command:** `npm run build`\n - **Output directory:** `./dist` (if you're using Nitro v2 or v3, this should be `./.output`)\n4. Add any **environment variables** required by the site. This is not necessary if you're deploying the starter app.\n\nClick on the **Deploy** button.\n{% /section %}\n\n{% section #step-4 step=4 title=\"Visit site\" %}\nAfter successful deployment, click on the **Visit site** button.\n\n{% /section %}"}, {"path": "docs/products/sites/quick-start/vanilla", "title": "Deploy a Vanilla JS app to Appwrite Sites", "description": "Learn how to setup and deploy Vanilla JS apps on Appwrite Sites.", "content": "{% section #step-1 step=1 title=\"Create web app\" %}\n\nOpen your terminal, and run the following command.\n\n```bash\nmkdir my-app\ncd my-app\n```\n\nIn this directory, create two files with the following code:\n\n- `index.html`\n\n```html\n<html>\n <head>\n <script type=\"module\" src=\"/app.js\"></script>\n </head>\n <body>\n <h1>Demo App</h1>\n <button>Click Me!</button>\n </body>\n</html>\n```\n\n- `app.js`\n\n```js\nrow.querySelector(\"button\").addEventListener(\"click\", () => {\n alert(\"Hello World!\");\n});\n```\n\nPush this project to a [GitHub repository](https://github.com/new).\n{% /section %}\n\n{% section #step-2 step=2 title=\"Create Appwrite project\" %}\nHead to the [Appwrite Console](https://cloud.appwrite.io).\n\n{% only_dark %}\n![Create Appwrite project](/images/docs/quick-starts/dark/create-project.avif)\n{% /only_dark %}\n{% only_light %}\n![Create Appwrite project](/images/docs/quick-starts/create-project.avif)\n{% /only_light %}\n\nIf this is your first time using Appwrite, create an account and create your first project.\n{% /section %}\n\n{% section #step-3 step=3 title=\"Create site\" %}\nHead to the **Sites** page in your Appwrite project, click on the **Create site** button, and select **Connect a repository**.\n\nConnect your GitHub account and select the repository you intend to deploy (or allow all repositories, for future ease).\n\n1. Select the **production branch** and **root directory** from your repo.\n2. Verify that the **correct framework** is selected. In case an incorrect framework is visible, pick **Other** from the drop-down list. \n3. Confirm the **install command**, **build command**, and **output directory** in the build settings. The build settings for this app will be:\n - **Install command:** `N/A` (leave empty, unless you have installed any external packages)\n - **Build command:** `N/A` (leave empty, unless you have installed any external packages)\n - **Output directory:** `./`\n4. Add any **environment variables** required by the site. This is not necessary if you're deploying the starter app.\n\nClick on the **Deploy** button.\n{% /section %}\n\n{% section #step-4 step=4 title=\"Visit site\" %}\nAfter successful deployment, click on the **Visit site** button.\n\n{% /section %}"}, {"path": "docs/products/sites/quick-start/vue", "title": "Deploy a Vue.js app to Appwrite Sites", "description": "Learn how to setup and deploy Vue.js apps on Appwrite Sites.", "content": "{% section #step-1 step=1 title=\"Create Vue.js app\" %}\n\nFirst, you must either create a Vue.js app or setup the [Vue.js starter template](https://github.com/appwrite/templates-for-sites).\n\nOpen your terminal, and run the following command.\n\n```bash\nnpm create vue@latest\n```\n\nPush this project to a [GitHub repository](https://github.com/new).\n{% /section %}\n\n{% section #step-2 step=2 title=\"Create Appwrite project\" %}\nHead to the [Appwrite Console](https://cloud.appwrite.io).\n\n{% only_dark %}\n![Create Appwrite project](/images/docs/quick-starts/dark/create-project.avif)\n{% /only_dark %}\n{% only_light %}\n![Create Appwrite project](/images/docs/quick-starts/create-project.avif)\n{% /only_light %}\n\nIf this is your first time using Appwrite, create an account and create your first project.\n{% /section %}\n\n{% section #step-3 step=3 title=\"Create site\" %}\nHead to the **Sites** page in your Appwrite project, click on the **Create site** button, and select **Connect a repository**.\n\nConnect your GitHub account and select the repository you intend to deploy (or allow all repositories, for future ease).\n\n1. Select the **production branch** and **root directory** from your repo.\n2. Verify that the **correct framework** is selected. In case an incorrect framework is visible, pick **Vue.js** from the drop-down list. \n3. Confirm the **install command**, **build command**, and **output directory** in the build settings. The default build settings for Vue.js are:\n - **Install command:** `npm install`\n - **Build command:** `npm run build`\n - **Output directory:** `./dist`\n4. Add any **environment variables** required by the site. This is not necessary if you're deploying the starter app.\n\nClick on the **Deploy** button.\n{% /section %}\n\n{% section #step-4 step=4 title=\"Visit site\" %}\nAfter successful deployment, click on the **Visit site** button.\n\n{% /section %}"}, {"path": "docs/products/sites/rendering", "title": "Rendering", "description": "Explore how sites are rendered on Appwrite Sites.", "content": "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.\n\n{% cards %}\n\n{% cards_item href=\"/docs/products/sites/rendering/static\" title=\"Static\" %}\nHost a static site or SPA\n{% /cards_item %}\n\n{% cards_item href=\"/docs/products/sites/rendering/ssr\" title=\"Server Side Rendering\" %}\nHost an SSR site\n{% /cards_item %}\n\n{% /cards %}\n\n# Differences\n\nThere are several differences between how static hosting and SSR work on Appwrite Sites.\n\n| Static/SPA/PWA | SSR |\n|---|---|\n| Pages are rendered at build time only | Pages are rendered every time a request is made to the server |\n| 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 |\n| The default `404` error page is Appwrite-branded (can be updated) | The default `404` error page is taken from the framework |\n| Faster cold starts | Slower cold starts |\n| Can access environment variables only during build-time | Can access environment variables during build-time and run-time |\n| Supported for all frameworks | Limited support for certain frameworks (learn more on [Frameworks page](/docs/products/sites/frameworks)) |\n\n# Choosing the right approach\n\nWhen deciding between static and SSR for your Appwrite Sites project, consider these factors:\n\n## Use static hosting when:\n- Your content doesn't change frequently\n- You want maximum performance with minimal server load\n- Your site is primarily client-side with limited data requirements\n- SEO is important but your content is relatively stable\n- You need compatibility with any web framework\n\n## Use SSR when:\n- Your content changes frequently or is user-specific\n- You need server-side access to data before rendering\n- SEO is critical for dynamic, frequently changing content\n- You want to reduce client-side JavaScript load\n- You're using a supported SSR framework (Next.js, Nuxt, etc.)\n\nMany 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."}, {"path": "docs/products/sites/rendering/ssr", "title": "Server Side Rendering", "description": "Learn how to host SSR web apps on Appwrite Sites.", "content": "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.\n\nSince 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).\n\n# Configuring your Appwrite Site to use SSR\n\nWhen 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.\n\nIf you need to manually update these settings, here are the steps to do so:\n\n1. Navigate to your site in the Appwrite Console and head to the **Settings** tab > **Build settings** section\n2. Select the **Server side rendering** checkbox (you may need to update your project codebase depending on your framework option)\n3. Confirm that the appropriate install command, build command, and output directory are set\n4. Click on the **Update** button and redeploy your site\n\n{% only_dark %}\n![Rendering strategy](/images/docs/sites/dark/build-settings-rendering-ssr.avif)\n{% /only_dark %}\n{% only_light %}\n![Rendering strategy](/images/docs/sites/build-settings-rendering-ssr.avif)\n{% /only_light %}\n\n## Enabling SSR builds on your web app\n\nTo enable SSR builds for your web app, you may need to make some additional updates in case of the following frameworks:\n\n{% tabs %}\n\n{% tabsitem #analog title=\"Analog\" %}\nSet `ssr: true` in `analog` plugin in the `vite.config.ts` file.\n{% /tabsitem %}\n\n{% tabsitem #angular title=\"Angular\" %}\nEnsure the `src/server.ts` file uses `@angular/ssr/node` package.\n{% /tabsitem %}\n\n{% tabsitem #nextjs title=\"Next.js\" %}\nThe following output modes are supported:\n\n- **Default mode**: No `output` configuration needed in `next.config.js`.\n- **Standalone mode**: Set `output` to `standalone` in `next.config.js` for smaller build sizes, lesser build times and cold start times.\n{% /tabsitem %}\n\n{% tabsitem #nuxt title=\"Nuxt\" %}\nSet build command to `npm run build` in site settings.\n{% /tabsitem %}\n\n{% tabsitem #sveltekit title=\"SvelteKit\" %}\nUse `@sveltejs/adapter-node` adapter in the `svelte.config.js` file.\n{% /tabsitem %}\n\n{% tabsitem #astro title=\"Astro\" %}\nUse `@astrojs/node` adapter in the `astro.config.mjs` file.\n{% /tabsitem %}\n\n{% tabsitem #remix title=\"Remix\" %}\nEnsure the `entry.server.tsx` file uses `@remix-run/node` package.\n{% /tabsitem %}\n\n{% tabsitem #tanstack-start title=\"TanStack Start\" %}\nNo additional configuration is needed as SSR is enabled by default.\n{% /tabsitem %}\n\n{% /tabs %}\n\n# Appwrite-specific environment variables\n\nYou can [access several environment variables](/docs/products/sites/develop#accessing-environment-variables) pertaining to your Appwrite project in SSR apps.\n\n{% partial file=\"sites-env-vars.md\" /%}"}, {"path": "docs/products/sites/rendering/static", "title": "Static", "description": "Learn how to host static web apps on Appwrite Sites.", "content": "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. \n\nSince 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.\n\nAll 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.\n\n# Configuring your Appwrite Site to use static hosting\n\nWhen 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.\n\nIf you need to manually update these settings, here are the steps to do so:\n\n1. Navigate to your site in the Appwrite Console and head to the **Settings** tab > **Build settings** section\n2. Select the **Static site** checkbox (you may need to update your project codebase depending on your framework option)\n3. Confirm that the appropriate install command, build command, and output directory are set\n4. Click on the **Update** button and redeploy your site\n\n{% only_dark %}\n![Rendering strategy](/images/docs/sites/dark/build-settings-rendering-static.avif)\n{% /only_dark %}\n{% only_light %}\n![Rendering strategy](/images/docs/sites/build-settings-rendering-static.avif)\n{% /only_light %}\n\n## Enabling static builds on your web app {% #enabling-static-builds %}\n\nTo enable static builds for your web app, you may need to make some additional updates in case of the following frameworks:\n\n{% tabs %}\n\n{% tabsitem #analog title=\"Analog\" %}\nSet `static: true` in `analog` plugin in the `vite.config.ts` file.\n{% /tabsitem %}\n\n{% tabsitem #nextjs title=\"Next.js\" %}\nSet `output: export` in the `next.config.js`.\n{% /tabsitem %}\n\n{% tabsitem #nuxt title=\"Nuxt\" %}\nSet build command to `npm run generate` in site settings.\n{% /tabsitem %}\n\n{% tabsitem #sveltekit title=\"SvelteKit\" %}\nUse `@sveltejs/adapter-static` adapter in the `svelte.config.js` file.\n{% /tabsitem %}\n\n{% tabsitem #astro title=\"Astro\" %}\nEnsure you don't set `adapter` in the `astro.config.mjs` file.\n{% /tabsitem %}\n\n{% tabsitem #remix title=\"Remix\" %}\nSet `ssr: false` in `remix` plugin in the `vite.config.ts` file.\n{% /tabsitem %}\n\n{% tabsitem #tanstack-start title=\"TanStack Start\" %}\nAdd [`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.\n{% /tabsitem %}\n\n{% /tabs %}\n\n# Running SPAs on Appwrite Sites\n\nSingle 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.\n\nAppwrite 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.\n\n## Configuring an Appwrite Site to run as an SPA\n\nTo configure an Appwrite Site to run as an SPA, you must do the following:\n\n1. Navigate to your site in the Appwrite Console and head to the **Settings** tab > **Build settings** section\n2. Select the **Static site** checkbox (you may need to update your project codebase depending on your framework option)\n3. Confirm that the appropriate install command, build command, and output directory are set\n4. Provide a fallback file for advanced routing and proper page handling.\n5. Click on the **Update** button and redeploy your site."}, {"path": "docs/products/sites/templates", "title": "Templates", "description": "Learn about Appwrite Sites' templates that let you jump start site development.", "content": "Appwrite provides a variety of Site Templates to help you jump-start your web app development.\n\n# Find templates\n\nYou can find all available templates by navigating to the Appwrite Console under your project > **Sites** > **Templates**.\n\n{% only_dark %}\n![Site templates](/images/docs/sites/dark/site-templates.avif)\n{% /only_dark %}\n{% only_light %}\n![Site templates](/images/docs/sites/site-templates.avif)\n{% /only_light %}\n\nYou can filter sites by searching, filter by use case, or filter by framework. Click **Create site** to create a site from a template.\n\n# Create with templates\n\nThe create site wizard for templates will include the following steps:\n\n## Configure site details\n\nPick a display name for your site and an ID. You will later use the ID to programmatically configure the site.\n\n## Setup a GitHub repo\n\nYou can choose to clone a new repository to your GitHub profile or organization or to connect to an existing repository.\n\nIf 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.\n\n## Configure repository\n\nAdd 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).\n\nThe connected repository will hold the source code for your site. When the code in this repository is updated, new deployments will be created.\n\n## Select production branch\n\nSelect the production branch, root directory, and the silent mode setting for the repo you selected.\n\nThe 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.\n\nThe root directory specifies the folder containing the source code of your your site.\n\nWhen 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**.\n\n## Add required environment variables\n\nAppwrite 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.\n\n## Configure domain name\n\nConfigure 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."}, {"path": "docs/products/storage", "title": "Storage", "description": "Unlock the power of cloud storage with Appwrite Storage. Learn how to store, manage, and retrieve files and media assets securely in your applications.", "content": "Appwrite Storage allows you to manage files in your project. \nYou can use it to store images, videos, rows, and other files for your projects. \nIt provides APIs to upload, download, delete, and list files, with many added utilities.\n\n{% info title=\"Looking for a database?\" %}\nAppwrite 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).\n{% /info %}\n\n# Get started {% #get-started %}\nGet started with Appwrite Storage. Learn to setup up a bucket, upload, and download your first file.\n\n{% arrow_link href=\"/docs/products/storage/quick-start\" %}\nQuick start \n{% /arrow_link %}"}, {"path": "docs/products/storage/buckets", "title": "Buckets", "description": "Organize and manage your files effectively with Appwrite Storage Buckets. Explore how to create, configure, and use storage buckets for seamless file organization.", "content": "Storage buckets are a group of files, similar to tables in Appwrite Databases.\nBuckets let you limit file size and extensions, whether or not to encrypt the files, and more.\n\n# Create Bucket {% #create-bucket %}\nYou can create your bucket from the Appwrite Console, a [Server SDK](/docs/sdks#server), or the [CLI](/docs/tooling/command-line/buckets).\n\n{% tabs %}\n{% tabsitem #console title=\"Console\" %}\nYou can create a bucket by heading to the **Storage** page and clicking **Create bucket**.\n\n{% only_dark %}\n![Create bucket on console](/images/docs/storage/dark/create-bucket.avif)\n{% /only_dark %}\n{% only_light %}\n![Create bucket on console](/images/docs/storage/create-bucket.avif)\n{% /only_light %}\n{% /tabsitem %}\n\n{% tabsitem #server-sdk title=\"Server SDK\" %}\nYou can also create tables programmatically using a [Server SDK](/docs/sdks#server). Appwrite [Server SDKs](/docs/sdks#server) require an [API key](/docs/advanced/security/api-keys).\n\n{% multicode %}\n\n```server-nodejs\nconst sdk = require('node-appwrite');\n\n// Init SDK\nconst client = new sdk.Client();\n\nconst storage = new sdk.Storage(client);\n\nclient\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n;\n\nconst promise = storage.createBucket({\n bucketId: '<BUCKET_ID>',\n name: '<NAME>'\n});\n\npromise.then(function (response) {\n console.log(response);\n}, function (error) {\n console.log(error);\n});\n```\n```deno\nimport * as sdk from \"npm:node-appwrite\";\n\n// Init SDK\nlet client = new sdk.Client();\n\nlet storage = new sdk.Storage(client);\n\nclient\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n;\n\n\nlet promise = storage.createBucket({\n bucketId: '<BUCKET_ID>',\n name: '<NAME>'\n});\n\npromise.then(function (response) {\n console.log(response);\n}, function (error) {\n console.log(error);\n});\n```\n```php\n<?php\n\nuse Appwrite\\Client;\nuse Appwrite\\Services\\Storage;\n\n$client = new Client();\n\n$client\n ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n ->setProject('<PROJECT_ID>') // Your project ID\n ->setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n;\n\n$storage = new Storage($client);\n\n$result = $storage->createBucket('<BUCKET_ID>', '<NAME>');\n```\n```python\nfrom appwrite.client import Client\nfrom appwrite.services.storage import Storage\n\nclient = Client()\n\n(client\n .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('<PROJECT_ID>') # Your project ID\n .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key\n)\n\nstorage = Storage(client)\n\nresult = storage.create_bucket('<BUCKET_ID>', '<NAME>')\n```\n```ruby\nrequire 'Appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('<PROJECT_ID>') # Your project ID\n .set_key('919c2d18fb5d4...a2ae413da83346ad2') # Your secret API key\n\nstorage = Storage.new(client)\n\nresponse = storage.create_bucket(bucket_id: '<BUCKET_ID>', name: '<NAME>')\n\nputs response.inspect\n```\n```csharp\nusing Appwrite;\nusing Appwrite.Services;\nusing Appwrite.Models;\n\nvar client = new Client()\n .SetEndPoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .SetProject(\"<PROJECT_ID>\") // Your project ID\n .SetKey(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\nvar storage = new Storage(client);\n\nBucket result = await storage.CreateBucket(\n bucketId: \"<BUCKET_ID>\",\n name: \"<NAME>\");\n```\n```dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\n\nvoid main() { // Init SDK\n Client client = Client();\n Storage storage = Storage(client);\n\n client\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key\n ;\n\n Future result = storage.createBucket(\n bucketId: '<BUCKET_ID>',\n name: '<NAME>',\n );\n\n result\n .then((response) {\n print(response);\n }).catchError((error) {\n print(error.response);\n });\n}\n```\n```kotlin\nimport io.appwrite.Client\nimport io.appwrite.services.Storage\n\nval client = Client(context)\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\") // Your secret API key\n\nval storage = Storage(client)\n\nval response = storage.createBucket(\n bucketId = \"<BUCKET_ID>\",\n name = \"<NAME>\",\n)\n```\n```java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Storage;\n\nClient client = new Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\nStorage storage = new Storage(client);\n\nstorage.createBucket(\n \"<BUCKET_ID>\",\n \"<NAME>\",\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(result);\n })\n);\n```\n```swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n .setKey(\"919c2d18fb5d4...a2ae413da83346ad2\") // Your secret API key\n\nlet storage = Storage(client)\n\nlet bucket = try await storage.createBucket(\n bucketId: \"<BUCKET_ID>\",\n name: \"<NAME>\"\n)\n\n```\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::storage::Storage;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n let client = Client::new()\n .set_endpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .set_project(\"<PROJECT_ID>\") // Your project ID\n .set_key(\"919c2d18fb5d4...a2ae413da83346ad2\"); // Your secret API key\n\n let storage = Storage::new(&client);\n\n let bucket = storage.create_bucket(\n \"<BUCKET_ID>\",\n \"<NAME>\",\n None, // permissions (optional)\n None, // file_security (optional)\n None, // enabled (optional)\n None, // maximum_file_size (optional)\n None, // allowed_file_extensions (optional)\n None, // compression (optional)\n None, // encryption (optional)\n None, // antivirus (optional)\n None, // transformations (optional)\n ).await?;\n\n println!(\"{:?}\", bucket);\n Ok(())\n}\n```\n{% /multicode %}\n\nYou 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).\n{% /tabsitem %}\n\n{% tabsitem #cli title=\"CLI\" %}\n\nCreate a bucket using the CLI command `appwrite init buckets`.\n\n```sh\nappwrite init buckets\n```\n\nThis will initialize your bucket in your `appwrite.config.json` file. To push your initialized bucket, use the `appwrite push buckets`.\n\n```sh\nappwrite push buckets\n```\n\nThis will create your bucket in the Console with all of your `appwrite.config.json` configurations.\n\n{% arrow_link href=\"/docs/tooling/command-line/buckets#commands\" %}\nLearn more about the CLI buckets commands\n{% /arrow_link %}\n\n{% /tabsitem %}\n{% /tabs %}\n\n# Permissions {% #permissions %}\nAppwrite uses permissions to control file access.\nFor security, only users that are granted permissions can access a file.\nThis helps prevent accidental data leaks by forcing you to make more concious decisions around permissions.\n\nBy default, Appwrite doesn't grants permissions to any users when a new bucket is created.\nThis means users can't create new files or read, update, and delete existing files.\n\n[Learn about configuring permissions](/docs/products/storage/permissions).\n\n# Encryption {% #encryption %}\nAppwrite provides added security settings for your buckets. Enable encryption under your bucket's **Settings** > **Security settings**.\nYou can enable encryption to encrypt files in your buckets. If your files are leaked, encrypted files cannot be read by the malicious actor.\nFiles bigger than 20MB cannot be encrypted.\n\n# Compression {% #compression %}\nAppwrite allows you to compress your files.\nTwo algorithms are available, which are [gzip](https://www.gzip.org/) and [zstd](https://github.com/facebook/zstd).\nYou can enable compress under your bucket's **Settings** > **Compression**.\nFor files larger than 20MB, compression will be skipped even when enabled.\n\n# Maximum file size {% #max-size %}\nLimit the maximum file size allowed in the bucket to prevent abuse.\nYou can configure maximum file size under your bucket's **Settings** > **Maximum file size**.\n\n# File extensions {% #extensions %}\nLimit 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.\nYou can configure maximum file size under your bucket's **Settings** > **File extensions**."}, {"path": "docs/products/storage/file-tokens", "title": "File tokens", "description": "Easily share files with external users using file tokens.", "content": "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.\n\n# File tokens vs secure cookies\n\nCurrently, 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.\n\nOne 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.\n\n# Create file tokens\n\nTo create a file token, you must [upload a file](/docs/products/storage/upload-download) to a [storage bucket](/docs/products/storage/buckets).\n\n{% tabs %}\n{% tabsitem #console title=\"Console\" %}\n\nHead 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.\n\n{% only_dark %}\n![Create file token](/images/docs/storage/dark/create-file-token.avif)\n{% /only_dark %}\n{% only_light %}\n![Create file token](/images/docs/storage/create-file-token.avif)\n{% /only_light %}\n\nYou 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.\n\n{% only_dark %}\n![Copy file token-based URL](/images/docs/storage/dark/copy-file-token-url.avif)\n{% /only_dark %}\n{% only_light %}\n![Copy file token-based URL](/images/docs/storage/copy-file-token-url.avif)\n{% /only_light %}\n\n{% /tabsitem %}\n\n{% tabsitem #server-sdk title=\"Server SDK\" %}\n\nYou can create file tokens programmatically using a [Server SDK](/docs/references/cloud/server-nodejs/tokens#createFileToken). Appwrite's Server SDKs require an [API key](/docs/advanced/security/api-keys) with the `tokens.write` scope enabled.\n\n{% multicode %}\n```server-nodejs\nconst sdk = require('node-appwrite');\n\nconst client = new sdk.Client()\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<YOUR_PROJECT_ID>') // Your project ID\n .setKey('<YOUR_API_KEY>'); // Your secret API key\n\nconst tokens = new sdk.Tokens(client);\n\nconst result = await tokens.createFileToken(\n '<BUCKET_ID>', // bucketId\n '<FILE_ID>', // fileId\n '' // expire (optional)\n);\n```\n\n```server-python\nfrom appwrite.client import Client\nfrom appwrite.services.tokens import Tokens\n\nclient = Client()\nclient.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\nclient.set_project('<YOUR_PROJECT_ID>') # Your project ID\nclient.set_key('<YOUR_API_KEY>') # Your secret API key\n\ntokens = Tokens(client)\n\nresult = tokens.create_file_token(\n bucket_id = '<BUCKET_ID>',\n file_id = '<FILE_ID>',\n expire = '' # optional\n)\n```\n\n```server-dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\n\nClient client = Client()\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<YOUR_PROJECT_ID>') // Your project ID\n .setKey('<YOUR_API_KEY>'); // Your secret API key\n\nTokens tokens = Tokens(client);\n\nResourceToken result = await tokens.createFileToken(\n bucketId: '<BUCKET_ID>',\n fileId: '<FILE_ID>',\n expire: '', // (optional)\n);\n```\n\n```server-php\n<?php\n\nuse Appwrite\\Client;\nuse Appwrite\\Services\\Tokens;\n\n$client = (new Client())\n ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n ->setProject('<YOUR_PROJECT_ID>') // Your project ID\n ->setKey('<YOUR_API_KEY>'); // Your secret API key\n\n$tokens = new Tokens($client);\n\n$result = $tokens->createFileToken(\n bucketId: '<BUCKET_ID>',\n fileId: '<FILE_ID>',\n expire: '' // optional\n);\n```\n\n```server-ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('<YOUR_PROJECT_ID>') # Your project ID\n .set_key('<YOUR_API_KEY>') # Your secret API key\n\ntokens = Tokens.new(client)\n\nresult = tokens.create_file_token(\n bucket_id: '<BUCKET_ID>',\n file_id: '<FILE_ID>',\n expire: '' # optional\n)\n```\n\n```server-dotnet\nusing Appwrite;\nusing Appwrite.Models;\nusing Appwrite.Services;\n\nClient client = new Client()\n .SetEndPoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .SetProject(\"<YOUR_PROJECT_ID>\") // Your project ID\n .SetKey(\"<YOUR_API_KEY>\"); // Your secret API key\n\nTokens tokens = new Tokens(client);\n\nResourceToken result = await tokens.CreateFileToken(\n bucketId: \"<BUCKET_ID>\",\n fileId: \"<FILE_ID>\",\n expire: \"\" // optional\n);\n```\n\n```server-deno\nimport { Client, Tokens } from \"npm:node-appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<YOUR_PROJECT_ID>') // Your project ID\n .setKey('<YOUR_API_KEY>'); // Your secret API key\n\nconst tokens = new Tokens(client);\n\nconst response = await tokens.createFileToken(\n '<BUCKET_ID>', // bucketId\n '<FILE_ID>', // fileId\n '' // expire (optional)\n);\n```\n\n```server-go\npackage main\n\nimport (\n \"fmt\"\n \"github.com/appwrite/sdk-for-go/client\"\n \"github.com/appwrite/sdk-for-go/tokens\"\n)\n\nfunc main() {\n client := client.NewClient()\n\n client.SetEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n client.SetProject(\"<YOUR_PROJECT_ID>\") // Your project ID\n client.SetKey(\"<YOUR_API_KEY>\") // Your secret API key\n\n service := tokens.NewTokens(client)\n response, error := service.CreateFileToken(\n \"<BUCKET_ID>\",\n \"<FILE_ID>\",\n tokens.WithCreateFileTokenExpire(\"\"),\n )\n\n if error != nil {\n panic(error)\n }\n\n fmt.Println(response)\n}\n```\n\n```server-swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<YOUR_PROJECT_ID>\") // Your project ID\n .setKey(\"<YOUR_API_KEY>\") // Your secret API key\n\nlet tokens = Tokens(client)\n\nlet resourceToken = try await tokens.createFileToken(\n bucketId: \"<BUCKET_ID>\",\n fileId: \"<FILE_ID>\",\n expire: \"\" // optional\n)\n```\n\n```server-kotlin\nimport io.appwrite.Client\nimport io.appwrite.coroutines.CoroutineCallback\nimport io.appwrite.services.Tokens\n\nval client = Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<YOUR_PROJECT_ID>\") // Your project ID\n .setKey(\"<YOUR_API_KEY>\") // Your secret API key\n\nval tokens = Tokens(client)\n\nval response = tokens.createFileToken(\n bucketId = \"<BUCKET_ID>\",\n fileId = \"<FILE_ID>\",\n expire = \"\" // optional\n)\n```\n\n```server-java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Tokens;\n\nClient client = new Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<YOUR_PROJECT_ID>\") // Your project ID\n .setKey(\"<YOUR_API_KEY>\"); // Your secret API key\n\nTokens tokens = new Tokens(client);\n\ntokens.createFileToken(\n \"<BUCKET_ID>\", // bucketId\n \"<FILE_ID>\", // fileId\n \"\", // expire (optional)\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(result);\n })\n);\n```\n\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::tokens::Tokens;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n let client = Client::new()\n .set_endpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .set_project(\"<PROJECT_ID>\") // Your project ID\n .set_key(\"<YOUR_API_KEY>\"); // Your secret API key\n\n let tokens = Tokens::new(&client);\n\n let result = tokens.create_file_token(\n \"<BUCKET_ID>\",\n \"<FILE_ID>\",\n Some(\"\"), // expire (optional)\n ).await?;\n\n println!(\"{:?}\", result);\n Ok(())\n}\n```\n\n```server-graphql\nmutation {\n tokensCreateFileToken(\n bucketId: \"<BUCKET_ID>\",\n fileId: \"<FILE_ID>\",\n expire: \"\"\n ) {\n _id\n _createdAt\n resourceId\n resourceType\n expire\n secret\n accessedAt\n }\n}\n```\n\n```server-rest\nPOST /v1/tokens/buckets/{bucketId}/files/{fileId} HTTP/1.1\nHost: cloud.appwrite.io\nContent-Type: application/json\nX-Appwrite-Response-Format: 1.7.0\nX-Appwrite-Project: <YOUR_PROJECT_ID>\nX-Appwrite-Key: <YOUR_API_KEY>\n\n{\n \"expire\": \n}\n```\n{% /multicode %}\n\nThe 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.\n{% /tabsitem %}\n{% /tabs %}\n\n# List all file tokens\n\nYou 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.\n\n{% tabs %}\n{% tabsitem #console title=\"Console\" %}\n\nHead to the **Storage** page, open a file inside a bucket, and scroll down to the **File tokens** section.\n\n{% only_dark %}\n![List of file tokens](/images/docs/storage/dark/list-file-tokens.avif)\n{% /only_dark %}\n{% only_light %}\n![List of file tokens](/images/docs/storage/list-file-tokens.avif)\n{% /only_light %}\n\n{% /tabsitem %}\n\n{% tabsitem #server-sdk title=\"Server SDK\" %}\n\nYou can list all file tokens programmatically using a [Server SDK](/docs/references/cloud/server-nodejs/tokens#list). Appwrite's Server SDKs require an [API key](/docs/advanced/security/api-keys) with the `tokens.read` scope enabled.\n\n{% multicode %}\n```server-nodejs\nconst sdk = require('node-appwrite');\n\nconst client = new sdk.Client()\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<YOUR_PROJECT_ID>') // Your project ID\n .setKey('<YOUR_API_KEY>'); // Your secret API key\n\nconst tokens = new sdk.Tokens(client);\n\nconst result = await tokens.list(\n '<BUCKET_ID>', // bucketId\n '<FILE_ID>', // fileId\n [] // queries (optional)\n);\n```\n\n```server-python\nfrom appwrite.client import Client\nfrom appwrite.services.tokens import Tokens\n\nclient = Client()\nclient.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\nclient.set_project('<YOUR_PROJECT_ID>') # Your project ID\nclient.set_key('<YOUR_API_KEY>') # Your secret API key\n\ntokens = Tokens(client)\n\nresult = tokens.list(\n bucket_id = '<BUCKET_ID>',\n file_id = '<FILE_ID>',\n queries = [] # optional\n)\n```\n\n```server-dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\n\nClient client = Client()\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<YOUR_PROJECT_ID>') // Your project ID\n .setKey('<YOUR_API_KEY>'); // Your secret API key\n\nTokens tokens = Tokens(client);\n\nResourceTokenList result = await tokens.list(\n bucketId: '<BUCKET_ID>',\n fileId: '<FILE_ID>',\n queries: [], // (optional)\n);\n```\n\n```server-php\n<?php\n\nuse Appwrite\\Client;\nuse Appwrite\\Services\\Tokens;\n\n$client = (new Client())\n ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n ->setProject('<YOUR_PROJECT_ID>') // Your project ID\n ->setKey('<YOUR_API_KEY>'); // Your secret API key\n\n$tokens = new Tokens($client);\n\n$result = $tokens->list(\n bucketId: '<BUCKET_ID>',\n fileId: '<FILE_ID>',\n queries: [] // optional\n);\n```\n\n```server-ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('<YOUR_PROJECT_ID>') # Your project ID\n .set_key('<YOUR_API_KEY>') # Your secret API key\n\ntokens = Tokens.new(client)\n\nresult = tokens.list(\n bucket_id: '<BUCKET_ID>',\n file_id: '<FILE_ID>',\n queries: [] # optional\n)\n```\n\n```server-dotnet\nusing Appwrite;\nusing Appwrite.Models;\nusing Appwrite.Services;\n\nClient client = new Client()\n .SetEndPoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .SetProject(\"<YOUR_PROJECT_ID>\") // Your project ID\n .SetKey(\"<YOUR_API_KEY>\"); // Your secret API key\n\nTokens tokens = new Tokens(client);\n\nResourceTokenList result = await tokens.List(\n bucketId: \"<BUCKET_ID>\",\n fileId: \"<FILE_ID>\",\n queries: new List<string>() // optional\n);\n```\n\n```server-deno\nimport { Client, Tokens } from \"npm:node-appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<YOUR_PROJECT_ID>') // Your project ID\n .setKey('<YOUR_API_KEY>'); // Your secret API key\n\nconst tokens = new Tokens(client);\n\nconst response = await tokens.list(\n '<BUCKET_ID>', // bucketId\n '<FILE_ID>', // fileId\n [] // queries (optional)\n);\n```\n\n```server-go\npackage main\n\nimport (\n \"fmt\"\n \"github.com/appwrite/sdk-for-go/client\"\n \"github.com/appwrite/sdk-for-go/tokens\"\n)\n\nfunc main() {\n client := client.NewClient()\n\n client.SetEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n client.SetProject(\"<YOUR_PROJECT_ID>\") // Your project ID\n client.SetKey(\"<YOUR_API_KEY>\") // Your secret API key\n\n service := tokens.NewTokens(client)\n response, error := service.List(\n \"<BUCKET_ID>\",\n \"<FILE_ID>\",\n tokens.WithListQueries([]interface{}{}),\n )\n\n if error != nil {\n panic(error)\n }\n\n fmt.Println(response)\n}\n```\n\n```server-swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<YOUR_PROJECT_ID>\") // Your project ID\n .setKey(\"<YOUR_API_KEY>\") // Your secret API key\n\nlet tokens = Tokens(client)\n\nlet resourceTokenList = try await tokens.list(\n bucketId: \"<BUCKET_ID>\",\n fileId: \"<FILE_ID>\",\n queries: [] // optional\n)\n```\n\n```server-kotlin\nimport io.appwrite.Client\nimport io.appwrite.coroutines.CoroutineCallback\nimport io.appwrite.services.Tokens\n\nval client = Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<YOUR_PROJECT_ID>\") // Your project ID\n .setKey(\"<YOUR_API_KEY>\") // Your secret API key\n\nval tokens = Tokens(client)\n\nval response = tokens.list(\n bucketId = \"<BUCKET_ID>\",\n fileId = \"<FILE_ID>\",\n queries = listOf() // optional\n)\n```\n\n```server-java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Tokens;\n\nClient client = new Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<YOUR_PROJECT_ID>\") // Your project ID\n .setKey(\"<YOUR_API_KEY>\"); // Your secret API key\n\nTokens tokens = new Tokens(client);\n\ntokens.list(\n \"<BUCKET_ID>\", // bucketId\n \"<FILE_ID>\", // fileId\n listOf(), // queries (optional)\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(result);\n })\n);\n```\n\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::tokens::Tokens;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n let client = Client::new()\n .set_endpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .set_project(\"<PROJECT_ID>\") // Your project ID\n .set_key(\"<YOUR_API_KEY>\"); // Your secret API key\n\n let tokens = Tokens::new(&client);\n\n let result = tokens.list(\n \"<BUCKET_ID>\",\n \"<FILE_ID>\",\n None, // queries (optional)\n None, // total (optional)\n ).await?;\n\n println!(\"{:?}\", result);\n Ok(())\n}\n```\n\n```server-rest\nGET /v1/tokens/buckets/{bucketId}/files/{fileId} HTTP/1.1\nHost: cloud.appwrite.io\nX-Appwrite-Response-Format: 1.7.0\nX-Appwrite-Project: <YOUR_PROJECT_ID>\nX-Appwrite-Key: <YOUR_API_KEY>\n```\n{% /multicode %}\n\nUsing the token IDs, you can also get data pertaining to an [individual file token](/docs/references/cloud/server-nodejs/tokens#get).\n\n{% multicode %}\n```server-nodejs\nconst token = await tokens.get(\n '<TOKEN_ID>' // tokenId\n);\n```\n\n```server-python\ntoken = tokens.get(\n token_id = '<TOKEN_ID>'\n)\n```\n\n```server-dart\nResourceToken token = await tokens.get(\n tokenId: '<TOKEN_ID>',\n);\n```\n\n```server-php\n$token = $tokens->get(\n tokenId: '<TOKEN_ID>'\n);\n```\n\n```server-ruby\ntoken = tokens.get(\n token_id: '<TOKEN_ID>'\n)\n```\n\n```server-dotnet\nResourceToken token = await tokens.Get(\n tokenId: \"<TOKEN_ID>\"\n);\n```\n\n```server-deno\nconst token = await tokens.get(\n '<TOKEN_ID>' // tokenId\n);\n```\n\n```server-go\ntoken, error := service.Get(\n \"<TOKEN_ID>\",\n)\n\nif error != nil {\n panic(error)\n}\n\nfmt.Println(token)\n ```\n\n```server-swift\nlet resourceToken = try await tokens.get(\n tokenId: \"<TOKEN_ID>\"\n)\n```\n\n```server-kotlin\nval token = tokens.get(\n tokenId = \"<TOKEN_ID>\"\n)```\n\n```server-java\ntokens.get(\n \"<TOKEN_ID>\", // tokenId\n new CoroutineCallback<>((token, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(token);\n })\n);```\n\n```server-rust\nlet token = tokens.get(\n \"<TOKEN_ID>\",\n).await?;\n```\n\n```server-rest\nGET /v1/tokens/{tokenId} HTTP/1.1\nHost: cloud.appwrite.io\nX-Appwrite-Response-Format: 1.7.0\nX-Appwrite-Project: <YOUR_PROJECT_ID>\nX-Appwrite-Key: <YOUR_API_KEY>\n```\n{% /multicode %}\n\n{% /tabsitem %}\n{% /tabs %}\n\n# Update file token expiry\n\nFile 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.\n\nWhile the expiry of a file token is set at the time of creation, you can update it later.\n\n{% tabs %}\n{% tabsitem #console title=\"Console\" %}\n\nHead 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**.\n\n{% only_dark %}\n![Update file token expiry](/images/docs/storage/dark/update-file-token-expiry.avif)\n{% /only_dark %}\n{% only_light %}\n![Update file token expiry](/images/docs/storage/update-file-token-expiry.avif)\n{% /only_light %}\n\n{% /tabsitem %}\n\n{% tabsitem #server-sdk title=\"Server SDK\" %}\n\nYou can update file token expiry programmatically using a [Server SDK](/docs/references/cloud/server-nodejs/tokens#update). Appwrite's Server SDKs require an [API key](/docs/advanced/security/api-keys) with the `tokens.write` scope enabled.\n\n{% multicode %}\n```server-nodejs\nconst sdk = require('node-appwrite');\n\nconst client = new sdk.Client()\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<YOUR_PROJECT_ID>') // Your project ID\n .setKey('<YOUR_API_KEY>'); // Your secret API key\n\nconst tokens = new sdk.Tokens(client);\n\nconst result = await tokens.update(\n '<TOKEN_ID>', // tokenId\n '' // expire (optional)\n);\n```\n\n```server-python\nfrom appwrite.client import Client\nfrom appwrite.services.tokens import Tokens\n\nclient = Client()\nclient.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\nclient.set_project('<YOUR_PROJECT_ID>') # Your project ID\nclient.set_key('<YOUR_API_KEY>') # Your secret API key\n\ntokens = Tokens(client)\n\nresult = tokens.update(\n token_id = '<TOKEN_ID>',\n expire = '' # optional\n)\n```\n\n```server-dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\n\nClient client = Client()\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<YOUR_PROJECT_ID>') // Your project ID\n .setKey('<YOUR_API_KEY>'); // Your secret API key\n\nTokens tokens = Tokens(client);\n\nResourceToken result = await tokens.update(\n tokenId: '<TOKEN_ID>',\n expire: '', // (optional)\n);\n```\n\n```server-php\n<?php\n\nuse Appwrite\\Client;\nuse Appwrite\\Services\\Tokens;\n\n$client = (new Client())\n ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n ->setProject('<YOUR_PROJECT_ID>') // Your project ID\n ->setKey('<YOUR_API_KEY>'); // Your secret API key\n\n$tokens = new Tokens($client);\n\n$result = $tokens->update(\n tokenId: '<TOKEN_ID>',\n expire: '' // optional\n);\n```\n\n```server-ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('<YOUR_PROJECT_ID>') # Your project ID\n .set_key('<YOUR_API_KEY>') # Your secret API key\n\ntokens = Tokens.new(client)\n\nresult = tokens.update(\n token_id: '<TOKEN_ID>',\n expire: '' # optional\n)\n```\n\n```server-dotnet\nusing Appwrite;\nusing Appwrite.Models;\nusing Appwrite.Services;\n\nClient client = new Client()\n .SetEndPoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .SetProject(\"<YOUR_PROJECT_ID>\") // Your project ID\n .SetKey(\"<YOUR_API_KEY>\"); // Your secret API key\n\nTokens tokens = new Tokens(client);\n\nResourceToken result = await tokens.Update(\n tokenId: \"<TOKEN_ID>\",\n expire: \"\" // optional\n);\n```\n\n```server-deno\nimport { Client, Tokens } from \"npm:node-appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<YOUR_PROJECT_ID>') // Your project ID\n .setKey('<YOUR_API_KEY>'); // Your secret API key\n\nconst tokens = new Tokens(client);\n\nconst response = await tokens.update(\n '<TOKEN_ID>', // tokenId\n '' // expire (optional)\n);\n```\n\n```server-go\npackage main\n\nimport (\n \"fmt\"\n \"github.com/appwrite/sdk-for-go/client\"\n \"github.com/appwrite/sdk-for-go/tokens\"\n)\n\nfunc main() {\n client := client.NewClient()\n\n client.SetEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n client.SetProject(\"<YOUR_PROJECT_ID>\") // Your project ID\n client.SetKey(\"<YOUR_API_KEY>\") // Your secret API key\n\n service := tokens.NewTokens(client)\n response, error := service.Update(\n \"<TOKEN_ID>\",\n tokens.WithUpdateExpire(\"\"),\n )\n\n if error != nil {\n panic(error)\n }\n\n fmt.Println(response)\n}\n```\n\n```server-swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<YOUR_PROJECT_ID>\") // Your project ID\n .setKey(\"<YOUR_API_KEY>\") // Your secret API key\n\nlet tokens = Tokens(client)\n\nlet resourceToken = try await tokens.update(\n tokenId: \"<TOKEN_ID>\",\n expire: \"\" // optional\n)\n```\n\n```server-kotlin\nimport io.appwrite.Client\nimport io.appwrite.coroutines.CoroutineCallback\nimport io.appwrite.services.Tokens\n\nval client = Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<YOUR_PROJECT_ID>\") // Your project ID\n .setKey(\"<YOUR_API_KEY>\") // Your secret API key\n\nval tokens = Tokens(client)\n\nval response = tokens.update(\n tokenId = \"<TOKEN_ID>\",\n expire = \"\" // optional\n)\n```\n\n```server-java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Tokens;\n\nClient client = new Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<YOUR_PROJECT_ID>\") // Your project ID\n .setKey(\"<YOUR_API_KEY>\"); // Your secret API key\n\nTokens tokens = new Tokens(client);\n\ntokens.update(\n \"<TOKEN_ID>\", // tokenId\n \"\", // expire (optional)\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(result);\n })\n);\n```\n\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::tokens::Tokens;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n let client = Client::new()\n .set_endpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .set_project(\"<PROJECT_ID>\") // Your project ID\n .set_key(\"<YOUR_API_KEY>\"); // Your secret API key\n\n let tokens = Tokens::new(&client);\n\n let result = tokens.update(\n \"<TOKEN_ID>\",\n Some(\"\"), // expire (optional)\n ).await?;\n\n println!(\"{:?}\", result);\n Ok(())\n}\n```\n\n```server-graphql\nmutation {\n tokensUpdate(\n tokenId: \"<TOKEN_ID>\",\n expire: \"\"\n ) {\n _id\n _createdAt\n resourceId\n resourceType\n expire\n secret\n accessedAt\n }\n}\n```\n\n```server-rest\nPATCH /v1/tokens/{tokenId} HTTP/1.1\nHost: cloud.appwrite.io\nContent-Type: application/json\nX-Appwrite-Response-Format: 1.7.0\nX-Appwrite-Project: <YOUR_PROJECT_ID>\nX-Appwrite-Key: <YOUR_API_KEY>\n\n{\n \"expire\": \n}\n```\n{% /multicode %}\n\n{% /tabsitem %}\n{% /tabs %}\n\n# Delete file tokens\n\nYou can use the Appwrite Console or one of the Server SDKs to delete a file token.\n\n{% tabs %}\n{% tabsitem #console title=\"Console\" %}\n\nHead 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**.\n\n{% only_dark %}\n![Delete file token](/images/docs/storage/dark/delete-file-token.avif)\n{% /only_dark %}\n{% only_light %}\n![Delete file token](/images/docs/storage/delete-file-token.avif)\n{% /only_light %}\n\n{% /tabsitem %}\n\n{% tabsitem #server-sdk title=\"Server SDK\" %}\n\nYou can delete a file token programmatically using a [Server SDK](/docs/references/cloud/server-nodejs/tokens#delete). Appwrite's Server SDKs require an [API key](/docs/advanced/security/api-keys) with the `tokens.write` scope enabled.\n\n{% multicode %}\n```server-nodejs\nconst sdk = require('node-appwrite');\n\nconst client = new sdk.Client()\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<YOUR_PROJECT_ID>') // Your project ID\n .setKey('<YOUR_API_KEY>'); // Your secret API key\n\nconst tokens = new sdk.Tokens(client);\n\nconst result = await tokens.delete(\n '<TOKEN_ID>' // tokenId\n);\n```\n\n```server-python\nfrom appwrite.client import Client\nfrom appwrite.services.tokens import Tokens\n\nclient = Client()\nclient.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\nclient.set_project('<YOUR_PROJECT_ID>') # Your project ID\nclient.set_key('<YOUR_API_KEY>') # Your secret API key\n\ntokens = Tokens(client)\n\nresult = tokens.delete(\n token_id = '<TOKEN_ID>'\n)\n```\n\n```server-dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\n\nClient client = Client()\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<YOUR_PROJECT_ID>') // Your project ID\n .setKey('<YOUR_API_KEY>'); // Your secret API key\n\nTokens tokens = Tokens(client);\n\nawait tokens.delete(\n tokenId: '<TOKEN_ID>',\n);\n```\n\n```server-php\n<?php\n\nuse Appwrite\\Client;\nuse Appwrite\\Services\\Tokens;\n\n$client = (new Client())\n ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n ->setProject('<YOUR_PROJECT_ID>') // Your project ID\n ->setKey('<YOUR_API_KEY>'); // Your secret API key\n\n$tokens = new Tokens($client);\n\n$result = $tokens->delete(\n tokenId: '<TOKEN_ID>'\n);\n```\n\n```server-ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('<YOUR_PROJECT_ID>') # Your project ID\n .set_key('<YOUR_API_KEY>') # Your secret API key\n\ntokens = Tokens.new(client)\n\nresult = tokens.delete(\n token_id: '<TOKEN_ID>'\n)\n```\n\n```server-dotnet\nusing Appwrite;\nusing Appwrite.Models;\nusing Appwrite.Services;\n\nClient client = new Client()\n .SetEndPoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .SetProject(\"<YOUR_PROJECT_ID>\") // Your project ID\n .SetKey(\"<YOUR_API_KEY>\"); // Your secret API key\n\nTokens tokens = new Tokens(client);\n\nawait tokens.Delete(\n tokenId: \"<TOKEN_ID>\"\n);\n```\n\n```server-deno\nimport { Client, Tokens } from \"npm:node-appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<YOUR_PROJECT_ID>') // Your project ID\n .setKey('<YOUR_API_KEY>'); // Your secret API key\n\nconst tokens = new Tokens(client);\n\nconst response = await tokens.delete(\n '<TOKEN_ID>' // tokenId\n);\n```\n\n```server-go\npackage main\n\nimport (\n \"fmt\"\n \"github.com/appwrite/sdk-for-go/client\"\n \"github.com/appwrite/sdk-for-go/tokens\"\n)\n\nfunc main() {\n client := client.NewClient()\n\n client.SetEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n client.SetProject(\"<YOUR_PROJECT_ID>\") // Your project ID\n client.SetKey(\"<YOUR_API_KEY>\") // Your secret API key\n\n service := tokens.NewTokens(client)\n response, error := service.Delete(\n \"<TOKEN_ID>\",\n )\n\n if error != nil {\n panic(error)\n }\n\n fmt.Println(response)\n}\n```\n\n```server-swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<YOUR_PROJECT_ID>\") // Your project ID\n .setKey(\"<YOUR_API_KEY>\") // Your secret API key\n\nlet tokens = Tokens(client)\n\nlet result = try await tokens.delete(\n tokenId: \"<TOKEN_ID>\"\n)\n```\n\n```server-kotlin\nimport io.appwrite.Client\nimport io.appwrite.coroutines.CoroutineCallback\nimport io.appwrite.services.Tokens\n\nval client = Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<YOUR_PROJECT_ID>\") // Your project ID\n .setKey(\"<YOUR_API_KEY>\") // Your secret API key\n\nval tokens = Tokens(client)\n\nval response = tokens.delete(\n tokenId = \"<TOKEN_ID>\"\n)\n```\n\n```server-java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.services.Tokens;\n\nClient client = new Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<YOUR_PROJECT_ID>\") // Your project ID\n .setKey(\"<YOUR_API_KEY>\"); // Your secret API key\n\nTokens tokens = new Tokens(client);\n\ntokens.delete(\n \"<TOKEN_ID>\", // tokenId\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n error.printStackTrace();\n return;\n }\n\n System.out.println(result);\n })\n);\n```\n\n```server-rust\nuse appwrite::Client;\nuse appwrite::services::tokens::Tokens;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n let client = Client::new()\n .set_endpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .set_project(\"<PROJECT_ID>\") // Your project ID\n .set_key(\"<YOUR_API_KEY>\"); // Your secret API key\n\n let tokens = Tokens::new(&client);\n\n tokens.delete(\n \"<TOKEN_ID>\",\n ).await?;\n\n println!(\"Token deleted successfully\");\n Ok(())\n}\n```\n\n```server-graphql\nmutation {\n tokensDelete(\n tokenId: \"<TOKEN_ID>\"\n ) {\n status\n }\n}\n```\n\n```server-rest\nDELETE /v1/tokens/{tokenId} HTTP/1.1\nHost: cloud.appwrite.io\nContent-Type: application/json\nX-Appwrite-Response-Format: 1.7.0\nX-Appwrite-Project: <YOUR_PROJECT_ID>\nX-Appwrite-Key: <YOUR_API_KEY>\n```\n{% /multicode %}\n\n{% /tabsitem %}\n{% /tabs %}"}, {"path": "docs/products/storage/images", "title": "Image transformations", "description": "Optimize image storage and processing with Appwrite. Explore image resizing, transformations, and manipulation to deliver rich media experiences in your apps.", "content": "Appwrite provides utilities to manipulate images for previewing images in your apps.\n\nAppwrite 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.\n\nYou 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.\nThe 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.\n\n{% info title=\"Caching\" %}\nWhen manipulating images in Appwrite, the resulting images are cached by Appwrite and your browser.\nWhen you repeatedly use the same transformed images, the performance impact will be minimal.\n{% /info %}\n\n## Options {% #options %}\n\nBelow you can find all the different parameters offered by the preview endpoint to manipulate the image.\n\n| Parameter | Description |\n| --------------| --------------------------------------------------------------------------------------------------------------- |\n| 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` |\n| 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` |\n| 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`. |\n| quality | Set the quality of the output image. Accepts integer between `0-100`, where `100` is the highest quality. |\n| borderWidth | Set a border with the given width in pixels to the output image. Accepts integer between `0-100`. |\n| borderColor | Set a border-color for the output image. Accepts any valid hex color value without the leading `#`. |\n| borderRadius | Set a border-radius in pixels. Accepts an integer between `0-4000`. |\n| 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`. |\n| rotation | Rotate the output image by a degree. Accepts an integer between `-360` to `360`. |\n| background | Set a background-color. Accepts any valid hex color value without the leading `#`. Only works with output formats supporting alpha channels like `png`. |\n| 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` |\n\n# Examples {% #examples %}\nHere are some examples using [Client SDKs](/docs/sdks#client).\n{% multicode %}\n```client-web\nimport { Client, Storage } from \"appwrite\";\n\nconst client = new Client();\n\nconst storage = new Storage(client);\n\nclient\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n;\n\nconst result = storage.getFilePreview({\n bucketId: 'photos',\n fileId: 'sunset.png',\n width: 1800,\n gravity: 'center',\n quality: '90',\n borderWidth: 5,\n borderColor: 'CDCA30',\n borderRadius: 15,\n background: 'FFFFFF'\n});\n\nconsole.log(result.href);\n```\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nvoid main() { // Init SDK\n Client client = Client();\n Storage storage = Storage(client);\n\n client\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n ;\n // downloading file\n Future result = storage.getFilePreview(\n bucketId: 'photos',\n fileId: 'sunset.png',\n width: 1800,\n height: 0,\n gravity: 'center',\n quality: '90',\n borderWidth: 5,\n borderColor: 'CDCA30',\n borderRadius: 15,\n opacity: 1,\n rotation: 0,\n background: \"FFFFFF\",\n output:'jpg'\n ).then((bytes) {\n final file = File('path_to_file/filename.ext');\n file.writeAsBytesSync(bytes)\n }).catchError((error) {\n print(error.response);\n })\n}\n\n//displaying image preview\nFutureBuilder(\n future: storage.getFilePreview(\n bucketId: '<BUCKET_ID>',\n fileId: '<FILE_ID>',\n ), //works for both public file and private file, for private files you need to be logged in\n builder: (context, snapshot) {\n return snapshot.hasData && snapshot.data != null\n ? Image.memory(\n snapshot.data,\n )\n : CircularProgressIndicator();\n },\n);\n```\n```client-apple\nimport Appwrite\n\nfunc main() async throws {\n let client = Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n let storage = Storage(client)\n let byteBuffer = try await storage.getFilePreview(\n bucketId: \"photos\",\n fileId: \"sunset.png\",\n width: 1800,\n height: 0,\n gravity: \"center\",\n quality: \"90\",\n borderWidth: 5,\n borderColor: \"CDCA30\",\n borderRadius: 15,\n opacity: 1,\n rotation: 0,\n background: \"FFFFFF\",\n output:\"jpg\"\n )\n\n print(String(describing: byteBuffer)\n}\n```\n```client-android-java\nimport androidx.appcompat.app.AppCompatActivity\nimport android.os.Bundle\nimport kotlinx.coroutines.GlobalScope\nimport kotlinx.coroutines.launch\nimport io.appwrite.Client\nimport io.appwrite.services.Storage\n\nclass MainActivity : AppCompatActivity() {\n override fun onCreate(savedInstanceState: Bundle?) {\n super.onCreate(savedInstanceState)\n setContentView(R.layout.activity_main)\n\n val client = Client(applicationContext)\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n\n val storage = Storage(client)\n\n val result = storage.getFilePreview(\n bucketId = \"photos\", // bucket ID\n fileId = \"sunset.png\", // file ID\n width = 1800, // width, will be resized using this value.\n height = 0, // height, ignored when 0\n gravity = \"center\", // crop center\n quality = \"90\", // slight compression\n borderWidth = 5, // border width\n borderColor = \"CDCA30\", // border color\n borderRadius = 15, // border radius\n opacity = 1, // full opacity\n rotation = 0, // no rotation\n background = \"FFFFFF\", // background color\n output =\"jpg\" // output jpg format\n )\n println(result); // Resource URL\n }\n}\n```\n```client-react-native\nimport { Client, Storage, ImageGravity } from 'react-native-appwrite';\nimport { Image } from 'react-native';\n\nconst client = new Client()\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')\n .setProject('<PROJECT_ID>');\n\nconst storage = new Storage(client);\n\n// Get image with transformations\nconst result = storage.getFilePreview(\n 'photos', // bucket ID\n 'sunset.png', // file ID\n 1800, // width, will be resized using this value\n 0, // height, ignored when 0\n ImageGravity.Center,// crop center\n 90, // slight compression\n 5, // border width\n 'CDCA30', // border color\n 15, // border radius\n 1, // full opacity\n 0, // no rotation\n 'FFFFFF', // background color\n 'jpg' // output jpg format\n);\n\nconsole.log(imageUrl); // URL object\n\n// Usage in a component\nconst ImagePreview = () => (\n <Image\n source={{ uri: imageUrl.toString() }}\n style={{ width: 300, height: 200 }}\n resizeMode=\"contain\"\n />\n);\n```\n\n{% /multicode %}"}, {"path": "docs/products/storage/permissions", "title": "Storage permissions", "description": "Enhance data security and control with Appwrite Storage Permissions. Learn how to set access rules, permissions, and restrictions for your stored files.", "content": "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.\nPermissions exist at two levels, bucket level and file level permissions. \n\nIn Appwrite, permissions are **granted**, meaning a user has no access by default and receive access when granted. \nA user with access granted at either bucket level or file level will be able to access a file.\nUsers **don't need access at both levels** to access files.\n\n# Bucket level {% #bucket-level %}\nBucket level permissions apply to every file in the bucket. \nIf a user has read, create, update, or delete permissions at the bucket level, the user can access **all files** inside the bucket.\n\nConfigure bucket level permissions by navigating to **Your bucket** > **Settings** > **Permissions**.\n\n[Learn more about permissions and roles](/docs/advanced/security/permissions)\n\n# File level {% #file-level %}\nFile level permissions grant access to individual files.\nIf a user has read, create, update, or delete permissions at the file level, the user can access the **individual file**.\n\nFile level permissions are only applied if File Security is enabled in the settings of your bucket.\nEnable file level permissions by navigating to **Your bucket** > **Settings** > **File security**.\n\nFile level permissions are configured in individual [files](/docs/products/storage/permissions#file-level).\n\n[Learn more about permissions and roles](/docs/advanced/security/permissions)"}, {"path": "docs/products/storage/quick-start", "title": "Start with Storage", "description": "Get started quickly with Appwrite Storage. Follow step-by-step instructions to set up storage, upload files, and integrate cloud storage into your projects", "content": "You can create your first bucket, upload, and download your first file in minutes.\n\n# Create bucket {% #create-bucket %}\nYou can create a bucket in the Appwrite Console by navigating to **Storage** > **Create bucket**.\n\nIn your bucket, navigate to **Settings** > **Permissions**, then add a new **Any** role with **CREATE** and **READ** permissions.\nThis allows anyone to create and read files in this bucket.\n\n# Create file {% #create-file %}\n\nTo 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.\n\n{% multicode %}\n ```client-web\n import { Client, Storage, ID } from \"appwrite\";\n\n const client = new Client()\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')\n .setProject('<PROJECT_ID>');\n\n const storage = new Storage(client);\n\n const promise = storage.createFile({\n bucketId: '<BUCKET_ID>',\n fileId: ID.unique(),\n file: document.getElementById('uploader').files[0]\n });\n\n promise.then(function (response) {\n console.log(response); // Success\n }, function (error) {\n console.log(error); // Failure\n });\n ```\n\n ```server-nodejs\n const sdk = require('node-appwrite');\n const { InputFile } = require('node-appwrite/file');\n\n const client = new sdk.Client()\n .setEndpoint('https://cloud.appwrite.io/v1')\n .setProject('<PROJECT_ID>')\n .setKey('<API_KEY>');\n\n const storage = new sdk.Storage(client);\n\n const nodeFile = InputFile.fromPath('/path/to/file.jpg', 'file.jpg');\n await storage.createFile({\n bucketId: '<BUCKET_ID>',\n fileId: sdk.ID.unique(),\n file: nodeFile\n });\n ```\n\n ```client-flutter\n import 'package:appwrite/appwrite.dart';\n\n void main() { // Init SDK\n final client = Client()\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')\n .setProject('<PROJECT_ID>');\n\n final storage = Storage(client);\n\n final file = await storage.createFile(\n bucketId: '<BUCKET_ID>',\n fileId: ID.unique(),\n file: InputFile.fromPath(path: './path-to-files/image.jpg', filename: 'image.jpg'),\n );\n }\n ```\n ```client-apple\n import Appwrite\n\n func main() async throws {\n let client = Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\")\n .setProject(\"<PROJECT_ID>\")\n\n let storage = Storage(client)\n\n let file = try await storage.createFile(\n bucketId: \"<BUCKET_ID>\",\n fileId: ID.unique(),\n file: InputFile.fromBuffer(yourByteBuffer,\n filename: \"image.jpg\",\n mimeType: \"image/jpeg\"\n )\n )\n }\n ```\n ```client-android-kotlin\n import io.appwrite.Client\n import io.appwrite.services.Storage\n\n suspend fun main() {\n val client = Client(applicationContext)\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n\n val storage = Storage(client)\n\n val file = storage.createFile(\n bucketId = \"<BUCKET_ID>\",\n fileId = ID.unique(),\n file = File(\"./path-to-files/image.jpg\"),\n )\n }\n ```\n\n ```client-react-native\n import { Client, Storage, ID } from 'react-native-appwrite';\n\n const client = new Client()\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')\n .setProject('<PROJECT_ID>');\n\n const storage = new Storage(client);\n\n const promise = storage.createFile(\n '<BUCKET_ID>',\n ID.unique(),\n {\n name: 'image.jpg',\n type: 'image/jpeg',\n size: 1234567,\n uri: 'file:///path/to/file.jpg',\n }\n );\n\n promise.then(function (response) {\n console.log(response); // Success\n }, function (error) {\n console.log(error); // Failure\n });\n ```\n\n ```server-rust\n use appwrite::Client;\n use appwrite::services::storage::Storage;\n use appwrite::input_file::InputFile;\n use appwrite::id::ID;\n\n #[tokio::main]\n async fn main() -> Result<(), Box<dyn std::error::Error>> {\n let client = Client::new()\n .set_endpoint(\"https://cloud.appwrite.io/v1\")\n .set_project(\"<PROJECT_ID>\")\n .set_key(\"<API_KEY>\");\n\n let storage = Storage::new(&client);\n\n let file = InputFile::from_path(\"/path/to/file.jpg\", None).await?;\n let result = storage.create_file(\n \"<BUCKET_ID>\",\n ID::unique(),\n file,\n None, // permissions (optional)\n ).await?;\n\n println!(\"{:?}\", result);\n Ok(())\n }\n ```\n\n ```http\n POST /v1/storage/buckets/{bucketId}/files HTTP/1.1\n Content-Type: multipart/form-data; boundary=\"cec8e8123c05ba25\"\n Content-Length: *Length of your entity body in bytes*\n X-Appwrite-Project: <PROJECT_ID>\n\n --cec8e8123c05ba25\n Content-Disposition: form-data; name=\"operations\"\n\n { \"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 } }\n --cec8e8123c05ba25\n Content-Disposition: form-data; name=\"map\"\n\n { \"0\": [\"variables.file\"] }\n --cec8e8123c05ba25\n Content-Disposition: form-data; name=\"0\"; filename=\"file.txt\"\n Content-Type: text/plain\n\n File content.\n\n --cec8e8123c05ba25--\n ```\n\n{% /multicode %}\n\n\n# Download file {% #download-file %}\nTo download a file, use the `getFileDownload` method.\n\n{% multicode %}\n```client-web\nimport { Client, Storage } from \"appwrite\";\n\nconst client = new Client();\n\nconst storage = new Storage(client);\n\nclient\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n;\n\nconst result = storage.getFileDownload({\n bucketId: '<BUCKET_ID>',\n fileId: '<FILE_ID>'\n});\n\nconsole.log(result); // Resource URL\n```\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nvoid main() { // Init SDK\n Client client = Client();\n Storage storage = Storage(client);\n\n client\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n ;\n // downloading file\n Future result = storage.getFileDownload(\n bucketId: '<BUCKET_ID>',\n fileId: '<FILE_ID>',\n ).then((bytes) {\n final file = File('path_to_file/filename.ext');\n file.writeAsBytesSync(bytes)\n }).catchError((error) {\n print(error.response);\n })\n}\n\n//displaying image preview\nFutureBuilder(\n future: storage.getFileDownload(\n bucketId: '<BUCKET_ID>',\n fileId: '<FILE_ID>',\n ), //works for both public file and private file, for private files you need to be logged in\n builder: (context, snapshot) {\n return snapshot.hasData && snapshot.data != null\n ? Image.memory(\n snapshot.data,\n )\n : CircularProgressIndicator();\n },\n);\n```\n```client-apple\nimport Appwrite\n\nfunc main() async throws {\n let client = Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n let storage = Storage(client)\n let byteBuffer = try await storage.getFileDownload(\n bucketId: \"<BUCKET_ID>\",\n fileId: \"<FILE_ID>\"\n )\n\n print(String(describing: byteBuffer))\n}\n```\n```client-android-kotlin\nimport androidx.appcompat.app.AppCompatActivity\nimport android.os.Bundle\nimport kotlinx.coroutines.GlobalScope\nimport kotlinx.coroutines.launch\nimport io.appwrite.Client\nimport io.appwrite.services.Storage\n\nclass MainActivity : AppCompatActivity() {\n override fun onCreate(savedInstanceState: Bundle?) {\n super.onCreate(savedInstanceState)\n setContentView(R.layout.activity_main)\n\n val client = Client(applicationContext)\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n\n val storage = Storage(client)\n\n val result = storage.getFileDownload(\n bucketId = \"<BUCKET_ID>\",\n fileId = \"<FILE_ID>\"\n )\n println(result); // Resource URL\n }\n}\n```\n\n```client-react-native\nimport { Client, Storage } from 'react-native-appwrite';\n\nconst client = new Client()\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')\n .setProject('<PROJECT_ID>');\n\nconst storage = new Storage(client);\n\nconst result = storage.getFileDownload({\n bucketId: '<BUCKET_ID>',\n fileId: '<FILE_ID>'\n});\n\nconsole.log(result); // Resource URL\n```\n{% /multicode %}"}, {"path": "docs/products/storage/upload-download", "title": "Upload and download", "description": "Effortlessly upload and download files with Appwrite Storage. Learn how to handle file uploads, manage file versions, and ensure secure downloads in your applications.", "content": "You can upload and download files both programmatically using SDKs or through the Appwrite Console.\n\n# Create file {% #create-file %}\n\nAfter 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.\n\nWhen 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.\n\nYou can also upload files programmatically using our SDKs:\n\n{% multicode %}\n ```client-web\n import { Client, Storage, ID } from \"appwrite\";\n\n const client = new Client()\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')\n .setProject('<PROJECT_ID>');\n\n const storage = new Storage(client);\n\n const promise = storage.createFile({\n bucketId: '<BUCKET_ID>',\n fileId: ID.unique(),\n file: document.getElementById('uploader').files[0]\n });\n\n promise.then(function (response) {\n console.log(response); // Success\n }, function (error) {\n console.log(error); // Failure\n });\n ```\n\n ```server-nodejs\n const sdk = require('node-appwrite');\n const { InputFile } = require('node-appwrite/file');\n\n const client = new sdk.Client()\n .setEndpoint('https://cloud.appwrite.io/v1')\n .setProject('<PROJECT_ID>')\n .setKey('<API_KEY>');\n\n const storage = new sdk.Storage(client);\n\n // If running in a browser environment, you can use File directly\n const browserFile = new File(['hello'], 'hello.txt');\n await storage.createFile({\n bucketId: '<BUCKET_ID>',\n fileId: ID.unique(),\n file: browserFile\n });\n\n // If running in Node.js, use InputFile\n const nodeFile = InputFile.fromPath('/path/to/file.jpg', 'file.jpg');\n await storage.createFile({\n bucketId: '<BUCKET_ID>',\n fileId: ID.unique(),\n file: nodeFile\n });\n ```\n\n ```client-flutter\n import 'package:appwrite/appwrite.dart';\n\n void main() { // Init SDK\n final client = Client()\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')\n .setProject('<PROJECT_ID>');\n\n final storage = Storage(client);\n\n final file = await storage.createFile(\n bucketId: '<BUCKET_ID>',\n fileId: ID.unique(),\n file: InputFile.fromPath(path: './path-to-files/image.jpg', filename: 'image.jpg'),\n );\n }\n ```\n\n ```client-android-kotlin\n import io.appwrite.Client\n import io.appwrite.services.Storage\n\n suspend fun main() {\n val client = Client(applicationContext)\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n\n val storage = Storage(client)\n\n val file = storage.createFile(\n bucketId = \"<BUCKET_ID>\",\n fileId = ID.unique(),\n file = File(\"./path-to-files/image.jpg\"),\n )\n }\n ```\n\n ```client-react-native\n import { Client, Storage, ID } from 'react-native-appwrite';\n\n const client = new Client()\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')\n .setProject('<PROJECT_ID>');\n\n const storage = new Storage(client);\n\n const promise = storage.createFile({\n bucketId: '<BUCKET_ID>',\n fileId: ID.unique(),\n file: {\n name: 'image.jpg',\n type: 'image/jpeg',\n size: 1234567,\n uri: 'file:///path/to/file.jpg',\n }\n });\n\n promise.then(function (response) {\n console.log(response); // Success\n }, function (error) {\n console.log(error); // Failure\n });\n ```\n\n ```client-apple\n import Appwrite\n\n func main() async throws {\n let client = Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\")\n .setProject(\"<PROJECT_ID>\")\n\n let storage = Storage(client)\n\n let file = try await storage.createFile(\n bucketId: \"<BUCKET_ID>\",\n fileId: ID.unique(),\n file: InputFile.fromBuffer(yourByteBuffer,\n filename: \"image.jpg\",\n mimeType: \"image/jpeg\"\n )\n )\n }\n ```\n\n ```server-rust\n use appwrite::Client;\n use appwrite::services::storage::Storage;\n use appwrite::input_file::InputFile;\n use appwrite::id::ID;\n\n #[tokio::main]\n async fn main() -> Result<(), Box<dyn std::error::Error>> {\n let client = Client::new()\n .set_endpoint(\"https://cloud.appwrite.io/v1\")\n .set_project(\"<PROJECT_ID>\")\n .set_key(\"<API_KEY>\");\n\n let storage = Storage::new(&client);\n\n let file = InputFile::from_path(\"/path/to/file.jpg\", None).await?;\n let result = storage.create_file(\n \"<BUCKET_ID>\",\n ID::unique(),\n file,\n None, // permissions (optional)\n ).await?;\n\n println!(\"{:?}\", result);\n Ok(())\n }\n ```\n\n ```http\n POST /v1/storage/buckets/{bucketId}/files HTTP/1.1\n Content-Type: multipart/form-data; boundary=\"cec8e8123c05ba25\"\n Content-Length: *Length of your entity body in bytes*\n X-Appwrite-Project: <PROJECT_ID>\n\n --cec8e8123c05ba25\n Content-Disposition: form-data; name=\"operations\"\n\n { \"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 } }\n --cec8e8123c05ba25\n Content-Disposition: form-data; name=\"map\"\n\n { \"0\": [\"variables.file\"] }\n --cec8e8123c05ba25\n Content-Disposition: form-data; name=\"0\"; filename=\"file.txt\"\n Content-Type: text/plain\n\n File content.\n\n --cec8e8123c05ba25--\n ```\n\n{% /multicode %}\n\n# Large files {% #large-files %}\nWhen you are trying to upload any files above 5MB, you will need to upload them in chunks for better reliability and performance.\nIf you're using an Appwrite SDK, this is handled automatically.\nIf you're not using an SDK, you can [learn more about REST API file handling](/docs/apis/rest#files).\n\n# InputFile {% #input-file %}\nEvery 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.\n\n# Client SDKs {% #client-sdks %}\n\n{% tabs %}\n{% tabsitem #web title=\"Web\" %}\nThe 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.\n\nFor example, for the input tag `<input type=\"file\" id=\"uploader\">`, you would call create file like this:\n\n```js\nconst promise = storage.createFile({\n bucketId: '<BUCKET_ID>',\n fileId: ID.unique(),\n file: document.getElementById('uploader').files[0]\n});\n```\n{% /tabsitem %}\n\n{% tabsitem #flutter title=\"Flutter\" %}\n\nThe Appwrite Flutter SDK expects an `InputFile` class for file inputs.\n\n| Method | Description |\n| ------------------------------------------ | ------------------------------------------------------------ |\n| `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. |\n| `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. |\n{% /tabsitem %}\n\n{% tabsitem #android title=\"Android\" %}\n\nThe Appwrite Android SDK expects an `InputFile` class for file inputs.\n\n| Method | Description |\n| ------------------------------ | ------------------------------------------------ |\n| `InputFile.fromPath(path: String)` | Used to upload files from a provided path. |\n| `InputFile.fromFile(file: File)` | Used to upload files from a [File](https://docs.oracle.com/javase/8/docs/api/java/io/File.html) object. |\n| `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. |\n\n{% /tabsitem %}\n\n{% tabsitem #apple title=\"Apple\" %}\nThe Appwrite Apple SDK expects an `InputFile` class for file inputs.\n\n| Method | Description |\n| ------------------------------------------ | ------------------------------------------------------------ |\n| `InputFile.fromPath(_ path: String)` | Used to upload files from a provided path. |\n| `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. |\n| `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. |\n{% /tabsitem %}\n\n{% tabsitem #react-native title=\"React Native\" %}\nThe Appwrite React Native SDK expects a file object with the following properties for file inputs:\n\n| Property | Description |\n| -------- | ----------- |\n| `name` | The name of the file. |\n| `type` | The MIME type of the file. |\n| `size` | The size of the file in bytes. |\n| `uri` | The URI of the file on the device. |\n\nThis object structure aligns with what is typically returned from image picker libraries such as `react-native-image-picker`:\n\n```js\n// Example with react-native-image-picker\nimport { launchImageLibrary } from 'react-native-image-picker';\n\nconst pickImage = async () => {\n const result = await launchImageLibrary({\n mediaType: 'photo',\n });\n\n if (result.assets && result.assets[0]) {\n const fileInfo = result.assets[0];\n\n return {\n name: fileInfo.fileName,\n type: fileInfo.type,\n size: fileInfo.fileSize,\n uri: fileInfo.uri,\n };\n }\n};\n```\n\nYou can also use the file picker or row picker from Expo:\n\n```js\n// Example with expo-row-picker\nimport * as DocumentPicker from 'expo-row-picker';\n\nconst pickDocument = async () => {\n const result = await DocumentPicker.getRowAsync();\n\n if (result.assets && result.assets[0]) {\n return {\n name: result.assets[0].name,\n type: result.assets[0].mimeType,\n size: result.assets[0].size,\n uri: result.assets[0].uri,\n };\n }\n};\n```\n{% /tabsitem %}\n{% /tabs %}\n\n# Server SDKs {% #server-sdks %}\n\n{% tabs %}\n{% tabsitem #nodejs title=\"Node.js\" %}\nIn browser environments, you can use the `File` object directly. For Node.js environments, import the `InputFile` class from 'node-appwrite/file'.\n\nWhen using `InputFile`, the following methods are available:\n\n| Method | Description |\n| ------------------------------------------ | ------------------------------------------------------------ |\n| `InputFile.fromPath(filePath, filename)` | Used to upload files from a provided path. |\n| `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. |\n| `InputFile.fromPlainText(content, filename)` | Used to upload files in plain text. Expects a string encoded in UTF-8. |\n{% /tabsitem %}\n\n{% tabsitem #php title=\"PHP\" %}\n\nThe Appwrite PHP SDK expects an `InputFile` class for file inputs.\n\n| Method | Description |\n| ------------------------------------------ | ------------------------------------------------------------ |\n| `InputFile.withPath(string $path, ?string $mimeType = null, ?string $filename = null)` | Used to upload files from a provided path. |\n| `InputFile.withData(string $data, ?string $mimeType = null, ?string $filename = null)` | Used to upload files from a string. |\n{% /tabsitem %}\n\n{% tabsitem #python title=\"Python\" %}\nThe Appwrite Python SDK expects an `InputFile` class for file inputs.\n\n| Method | Description |\n| ------------------------------------------ | ------------------------------------------------------------ |\n| `InputFile.from_path(path)` | Used to upload files from a provided path. |\n| `InputFile.from_bytes(bytes)` | Used to upload files from an array of bytes. |\n{% /tabsitem %}\n\n{% tabsitem #ruby title=\"Ruby\" %}\nThe Appwrite Ruby SDK expects an `InputFile` class for file inputs.\n\n| Method | Description |\n| ------------------------------------------ | ------------------------------------------------------------ |\n| `InputFile.from_path(path)` | Used to upload files from a provided path. |\n| `InputFile.from_string(string)` | Used to upload files from a String. |\n| `InputFile.from_bytes(bytes)` | Used to upload files from an array of bytes. |\n\n{% /tabsitem %}\n\n{% tabsitem #deno title=\"Deno\" %}\nThe Appwrite Deno SDK expects an `InputFile` class for file inputs.\n\n| Method | Description |\n| ------------------------------------------ | ------------------------------------------------------------ |\n| `InputFile.fromPath(filePath, filename)` | Used to upload files from a provided path. |\n| `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. |\n| `InputFile.fromPlainText(content, filename)` | Used to upload files in plain text. Expects a string encoded in UTF-8. |\n{% /tabsitem %}\n\n{% tabsitem #dart title=\"Dart\" %}\nThe Appwrite Dart SDK expects an `InputFile` class for file inputs.\n\n| Method | Description |\n| ------------------------------------------ | ------------------------------------------------------------ |\n| `InputFile.fromPath(path: [PATH], filename: [NAME], contentType: [MIME TYPE])` | Used to upload files from a provided path, `filename` and `contentType` are optional. |\n| `InputFile.fromBytes(bytes: [BYTE LIST], filename: [NAME], contentType: [MIME TYPE])` | Used to upload files from a byte list, `contentType` is optional. |\n{% /tabsitem %}\n\n{% tabsitem #kotlin title=\"Kotlin\" %}\n\nThe Appwrite Kotlin SDK expects an `InputFile` class for file inputs.\n\n| Method | Description |\n| ------------------------------------------ | ------------------------------------------------------------ |\n| `InputFile.fromPath(path: String)` | Used to upload files from a provided path. |\n| `InputFile.fromFile(file: File)` | Used to upload files from a [File](https://docs.oracle.com/javase/8/docs/api/java/io/File.html) object. |\n| `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. |\n{% /tabsitem %}\n\n{% tabsitem #swift title=\"Swift\" %}\nThe Appwrite Swift SDK expects an `InputFile` class for file inputs.\n\n| Method | Description |\n| ------------------------------------------ | ------------------------------------------------------------ |\n| `InputFile.fromPath(_ path: String)` | Used to upload files from a provided path. |\n| `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. |\n| `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. |\n{% /tabsitem %}\n\n{% tabsitem #dotnet title=\".NET\" %}\nThe Appwrite .NET SDK expects an `InputFile` class for file inputs.\n\n| Method | Description |\n| ------------------------------------------ | ------------------------------------------------------------ |\n| `InputFile.FromPath(string path)` | Used to upload files from a provided path. |\n| `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. |\n{% /tabsitem %}\n{% /tabs %}\n\n# Get file {% #get-file %}\nTo get a metadata about a file, use the `getFile` method.\n\n{% multicode %}\n```client-web\nimport { Client, Storage } from \"appwrite\";\n\nconst client = new Client();\n\nconst storage = new Storage(client);\n\nclient\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n;\n\nconst promise = storage.getFile({\n bucketId: '<BUCKET_ID>',\n fileId: '<FILE_ID>'\n});\n\npromise.then(function (response) {\n console.log(response); // Success\n}, function (error) {\n console.log(error); // Failure\n});\n```\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nvoid main() { // Init SDK\n Client client = Client();\n Storage storage = Storage(client);\n\n client\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n ;\n // downloading file\n Future result = storage.getFile(\n bucketId: '<BUCKET_ID>',\n fileId: '<FILE_ID>',\n ).then((bytes) {\n final file = File('path_to_file/filename.ext');\n file.writeAsBytesSync(bytes)\n }).catchError((error) {\n print(error.response);\n })\n}\n\n//displaying image preview\nFutureBuilder(\n future: storage.getFile(\n bucketId: '<BUCKET_ID>',\n fileId: '<FILE_ID>',\n ), //works for both public file and private file, for private files you need to be logged in\n builder: (context, snapshot) {\n return snapshot.hasData && snapshot.data != null\n ? Image.memory(\n snapshot.data,\n )\n : CircularProgressIndicator();\n },\n);\n```\n```client-apple\nimport Appwrite\n\nfunc main() async throws {\n let client = Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\")\n .setProject(\"<PROJECT_ID>\")\n\n let storage = Storage(client)\n\n let byteBuffer = try await storage.getFile(\n bucketId: \"<BUCKET_ID>\",\n fileId: \"<FILE_ID>\"\n )\n\n print(String(describing: byteBuffer)\n}\n```\n```client-android-kotlin\nimport androidx.appcompat.app.AppCompatActivity\nimport android.os.Bundle\nimport kotlinx.coroutines.GlobalScope\nimport kotlinx.coroutines.launch\nimport io.appwrite.Client\nimport io.appwrite.services.Storage\n\nclass MainActivity : AppCompatActivity() {\n override fun onCreate(savedInstanceState: Bundle?) {\n super.onCreate(savedInstanceState)\n setContentView(R.layout.activity_main)\n\n val client = Client(applicationContext)\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n\n val storage = Storage(client)\n\n val result = storage.getFile(\n bucketId = \"<BUCKET_ID>\",\n fileId = \"<FILE_ID>\"\n )\n println(result); // Resource URL\n }\n}\n```\n```client-react-native\nimport { Client, Storage } from 'react-native-appwrite';\n\nconst client = new Client()\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')\n .setProject('<PROJECT_ID>');\n\nconst storage = new Storage(client);\n\nconst promise = storage.getFile({\n bucketId: '<BUCKET_ID>',\n fileId: '<FILE_ID>'\n});\n\npromise.then(function (response) {\n console.log(response); // Success\n}, function (error) {\n console.log(error); // Failure\n});\n```\n{% /multicode %}\n\n# Download file {% #download-file %}\nTo download a file, use the `getFileDownload` method.\n\n{% multicode %}\n```client-web\nimport { Client, Storage } from \"appwrite\";\n\nconst client = new Client();\n\nconst storage = new Storage(client);\n\nclient\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n;\n\nconst result = storage.getFileDownload({\n bucketId: '<BUCKET_ID>',\n fileId: '<FILE_ID>'\n});\n\nconsole.log(result); // Resource URL\n```\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nvoid main() { // Init SDK\n Client client = Client();\n Storage storage = Storage(client);\n\n client\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n ;\n // downloading file\n Future result = storage.getFileDownload(\n bucketId: '<BUCKET_ID>',\n fileId: '<FILE_ID>',\n ).then((bytes) {\n final file = File('path_to_file/filename.ext');\n file.writeAsBytesSync(bytes)\n }).catchError((error) {\n print(error.response);\n })\n}\n\n//displaying image preview\nFutureBuilder(\n future: storage.getFileDownload(\n bucketId: '<BUCKET_ID>',\n fileId: '<FILE_ID>',\n ), //works for both public file and private file, for private files you need to be logged in\n builder: (context, snapshot) {\n return snapshot.hasData && snapshot.data != null\n ? Image.memory(\n snapshot.data,\n )\n : CircularProgressIndicator();\n },\n);\n```\n```client-apple\nimport Appwrite\n\nfunc main() async throws {\n let client = Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n let storage = Storage(client)\n let byteBuffer = try await storage.getFileDownload(\n bucketId: \"<BUCKET_ID>\",\n fileId: \"<FILE_ID>\"\n )\n\n print(String(describing: byteBuffer))\n}\n```\n```client-android-kotlin\nimport androidx.appcompat.app.AppCompatActivity\nimport android.os.Bundle\nimport kotlinx.coroutines.GlobalScope\nimport kotlinx.coroutines.launch\nimport io.appwrite.Client\nimport io.appwrite.services.Storage\n\nclass MainActivity : AppCompatActivity() {\n override fun onCreate(savedInstanceState: Bundle?) {\n super.onCreate(savedInstanceState)\n setContentView(R.layout.activity_main)\n\n val client = Client(applicationContext)\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n\n val storage = Storage(client)\n\n val result = storage.getFileDownload(\n bucketId = \"<BUCKET_ID>\",\n fileId = \"<FILE_ID>\"\n )\n println(result); // Resource URL\n }\n}\n```\n```client-react-native\nimport { Client, Storage } from 'react-native-appwrite';\n\nconst client = new Client()\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')\n .setProject('<PROJECT_ID>');\n\nconst storage = new Storage(client);\n\n// Downloads the file data as ArrayBuffer\nconst result = await storage.getFileDownload({\n bucketId: '<BUCKET_ID>',\n fileId: '<FILE_ID>'\n});\n\nconsole.log(result); // ArrayBuffer with file data\n\n// To get just the download URL without downloading:\nconst downloadUrl = storage.getFileDownloadURL({\n bucketId: '<BUCKET_ID>',\n fileId: '<FILE_ID>'\n});\n\nconsole.log(downloadUrl); // URL object\n```\n{% /multicode %}\n\n# Get File Preview {% #get-file-preview %}\nTo get a file preview image , use the `getFilePreview` method.\n\n{% multicode %}\n```client-web\nimport { Client, Storage } from \"appwrite\";\n\nconst client = new Client();\n\nconst storage = new Storage(client);\n\nclient\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n;\n\nconst result = storage.getFilePreview({\n bucketId: '<BUCKET_ID>',\n fileId: '<FILE_ID>'\n});\n\nconsole.log(result); // Resource URL\n```\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nvoid main() { // Init SDK\n Client client = Client();\n Storage storage = Storage(client);\n\n client\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n ;\n // downloading file\n Future result = storage.getFilePreview(\n bucketId: '<BUCKET_ID>',\n fileId: '<FILE_ID>',\n ).then((bytes) {\n final file = File('path_to_file/filename.ext');\n file.writeAsBytesSync(bytes)\n }).catchError((error) {\n print(error.response);\n })\n}\n\n//displaying image preview\nFutureBuilder(\n future: storage.getFilePreview(\n bucketId: '<BUCKET_ID>',\n fileId: '<FILE_ID>',\n ), //works for both public file and private file, for private files you need to be logged in\n builder: (context, snapshot) {\n return snapshot.hasData && snapshot.data != null\n ? Image.memory(\n snapshot.data,\n )\n : CircularProgressIndicator();\n },\n);\n```\n```client-apple\nimport Appwrite\n\nfunc main() async throws {\n let client = Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n let storage = Storage(client)\n let byteBuffer = try await storage.getFilePreview(\n bucketId: \"<BUCKET_ID>\",\n fileId: \"<FILE_ID>\"\n )\n\n print(String(describing: byteBuffer))\n}\n```\n```client-android-kotlin\nimport androidx.appcompat.app.AppCompatActivity\nimport android.os.Bundle\nimport kotlinx.coroutines.GlobalScope\nimport kotlinx.coroutines.launch\nimport io.appwrite.Client\nimport io.appwrite.services.Storage\n\nclass MainActivity : AppCompatActivity() {\n override fun onCreate(savedInstanceState: Bundle?) {\n super.onCreate(savedInstanceState)\n setContentView(R.layout.activity_main)\n\n val client = Client(applicationContext)\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n\n val storage = Storage(client)\n\n val result = storage.getFilePreview(\n bucketId = \"<BUCKET_ID>\",\n fileId = \"<FILE_ID>\"\n )\n println(result); // Resource URL\n }\n}\n```\n```client-react-native\nimport { Client, Storage, ImageGravity } from 'react-native-appwrite';\n\nconst client = new Client()\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')\n .setProject('<PROJECT_ID>');\n\nconst storage = new Storage(client);\n\n// Downloads the preview image data as ArrayBuffer\nconst result = await storage.getFilePreview({\n bucketId: '<BUCKET_ID>',\n fileId: '<FILE_ID>',\n width: 200,\n height: 200,\n gravity: ImageGravity.Center,\n quality: 100\n});\n\nconsole.log(result); // ArrayBuffer with image data\n\n// To get just the preview URL without downloading:\nconst previewUrl = storage.getFilePreviewURL({\n bucketId: '<BUCKET_ID>',\n fileId: '<FILE_ID>',\n width: 200,\n height: 200,\n gravity: ImageGravity.Center,\n quality: 100\n});\n\nconsole.log(previewUrl); // URL object\n```\n{% /multicode %}\n\n# View File {% #view-file%}\n\nTo view a file, use the `getFileView` method.\n\n{% multicode %}\n```client-web\nimport { Client, Storage } from \"appwrite\";\n\nconst client = new Client();\n\nconst storage = new Storage(client);\n\nclient\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n;\n\nconst result = storage.getFileView({\n bucketId: '<BUCKET_ID>',\n fileId: '<FILE_ID>'\n});\n\nconsole.log(result); // Resource URL\n```\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nvoid main() { // Init SDK\n Client client = Client();\n Storage storage = Storage(client);\n\n client\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('<PROJECT_ID>') // Your project ID\n ;\n // downloading file\n Future result = storage.getFileView(\n bucketId: '<BUCKET_ID>',\n fileId: '<FILE_ID>',\n ).then((bytes) {\n final file = File('path_to_file/filename.ext');\n file.writeAsBytesSync(bytes)\n }).catchError((error) {\n print(error.response);\n })\n}\n\n//displaying image preview\nFutureBuilder(\n future: storage.getFileView(\n bucketId: '<BUCKET_ID>',\n fileId: '<FILE_ID>',\n ), //works for both public file and private file, for private files you need to be logged in\n builder: (context, snapshot) {\n return snapshot.hasData && snapshot.data != null\n ? Image.memory(\n snapshot.data,\n )\n : CircularProgressIndicator();\n },\n);\n```\n```client-apple\nimport Appwrite\n\nfunc main() async throws {\n let client = Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n let storage = Storage(client)\n let byteBuffer = try await storage.getFileView(\n bucketId: \"<BUCKET_ID>\",\n fileId: \"<FILE_ID>\"\n )\n\n print(String(describing: byteBuffer))\n}\n```\n```client-android-kotlin\nimport androidx.appcompat.app.AppCompatActivity\nimport android.os.Bundle\nimport kotlinx.coroutines.GlobalScope\nimport kotlinx.coroutines.launch\nimport io.appwrite.Client\nimport io.appwrite.services.Storage\n\nclass MainActivity : AppCompatActivity() {\n override fun onCreate(savedInstanceState: Bundle?) {\n super.onCreate(savedInstanceState)\n setContentView(R.layout.activity_main)\n\n val client = Client(applicationContext)\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"<PROJECT_ID>\") // Your project ID\n\n val storage = Storage(client)\n\n val result = storage.getFileView(\n bucketId = \"<BUCKET_ID>\",\n fileId = \"<FILE_ID>\"\n )\n println(result); // Resource URL\n }\n}\n```\n```client-react-native\nimport { Client, Storage } from 'react-native-appwrite';\n\nconst client = new Client()\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')\n .setProject('<PROJECT_ID>');\n\nconst storage = new Storage(client);\n\n// Downloads the file data as ArrayBuffer\nconst result = await storage.getFileView({\n bucketId: '<BUCKET_ID>',\n fileId: '<FILE_ID>'\n});\n\nconsole.log(result); // ArrayBuffer with file data\n\n// To get just the view URL without downloading:\nconst viewUrl = storage.getFileViewURL({\n bucketId: '<BUCKET_ID>',\n fileId: '<FILE_ID>'\n});\n\nconsole.log(viewUrl); // URL object\n```\n{% /multicode %}"}, {"path": "docs/quick-starts/android", "title": "Start with Android (Kotlin)", "description": "Get started with Appwrite on Android and learn how to build secure and scalable apps using our powerful backend.", "content": "Learn how to setup your first Android project powered by Appwrite and the [Appwrite Android SDK](https://github.com/appwrite/sdk-for-android).\n\n{% info title=\"Using Java?\" %}\nCheck out the [Start with Android (Java)](/docs/quick-starts/android-java) guide.\n{% /info %}\n\n{% section #step-1 step=1 title=\"Create Android project\" %}\nOpen Android Studio and click **New Project** to create a new project.\n\nChoose your desired project template, for example **Empty Activity**, and click **Next**.\n\nNow 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.\n\n{% /section %}\n\n{% section #step-2 step=2 title=\"Create Appwrite project\" %}\nHead to the [Appwrite Console](https://cloud.appwrite.io/console).\n\n{% only_dark %}\n![Create project screen](/images/docs/quick-starts/dark/create-project.avif)\n{% /only_dark %}\n{% only_light %}\n![Create project screen](/images/docs/quick-starts/create-project.avif)\n{% /only_light %}\n\nIf this is your first time using Appwrite, create an account and create your first project.\n\nThen, under **Add a platform**, add an **Android app**.\n\nAdd 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.\n\n{% only_dark %}\n![Add a platform](/images/docs/quick-starts/dark/add-platform.avif)\n{% /only_dark %}\n{% only_light %}\n![Add a platform](/images/docs/quick-starts/add-platform.avif)\n{% /only_light %}\n\nYou can skip optional steps.\n\n{% /section %}\n\n{% section #step-3 step=3 title=\"Add the Appwrite SDK\" %}\nTo add the Appwrite SDK for Android as a dependency, add the following to your app-level **build.gradle.kts** file inside the **dependencies** block.\n\n```kotlin\nimplementation(\"io.appwrite:sdk-for-android:8.1.0\")\n```\n\nIn 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).\nBe sure to replace the **<PROJECT_ID>** string with your actual Appwrite project ID.\nYou can find your Appwrite project ID in you project settings screen in your Appwrite Console.\n\n```xml\n<manifest ...>\n ...\n <application ...>\n ...\n <!-- Add this inside the `<application>` tag, along side the existing `<activity>` tags -->\n <activity android:name=\"io.appwrite.views.CallbackActivity\" android:exported=\"true\">\n <intent-filter android:label=\"android_web_auth\">\n <action android:name=\"android.intent.action.VIEW\" />\n <category android:name=\"android.intent.category.DEFAULT\" />\n <category android:name=\"android.intent.category.BROWSABLE\" />\n <data android:scheme=\"appwrite-callback-<PROJECT_ID>\" />\n </intent-filter>\n </activity>\n </application>\n</manifest>\n```\n{% /section %}\n\n{% section #step-4 step=4 title=\"Create Appwrite Singleton\" %}\nFind your project's ID in the **Settings** page.\n\n{% only_dark %}\n![Project settings screen](/images/docs/quick-starts/dark/project-id.avif)\n{% /only_dark %}\n{% only_light %}\n![Project settings screen](/images/docs/quick-starts/project-id.avif)\n{% /only_light %}\n\nCreate a new file `Appwrite.kt` and add the following code to it, replacing `<PROJECT_ID>` with your project ID.\n\n```kotlin\npackage <YOUR_ROOT_PACKAGE_HERE>\n\nimport android.content.Context\nimport io.appwrite.Client\nimport io.appwrite.ID\nimport io.appwrite.models.*\nimport io.appwrite.services.*\n\nobject Appwrite {\n lateinit var client: Client\n lateinit var account: Account\n\n fun init(context: Context) {\n client = Client(context)\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\")\n .setProject(\"<PROJECT_ID>\")\n\n account = Account(client)\n }\n\n suspend fun onLogin(\n email: String,\n password: String,\n ): Session {\n return account.createEmailPasswordSession(\n email,\n password,\n )\n }\n\n suspend fun onRegister(\n email: String,\n password: String,\n ): User<Map<String, Any>> {\n return account.create(\n userId = ID.unique(),\n email,\n password,\n )\n }\n\n suspend fun onLogout() {\n account.deleteSession(\"current\")\n }\n}\n```\n{% /section %}\n{% section #step-5 step=5 title=\"Create a login page\" %}\nAdd the following code to `MainActivity.kt`.\n\n```kotlin\npackage <YOUR_ROOT_PACKAGE_HERE>\n\nimport android.os.Bundle\nimport androidx.activity.ComponentActivity\nimport androidx.activity.compose.setContent\nimport androidx.compose.foundation.layout.*\nimport androidx.compose.foundation.text.*\nimport androidx.compose.material3.*\nimport androidx.compose.runtime.*\nimport androidx.compose.ui.*\nimport androidx.compose.ui.text.input.*\nimport androidx.compose.ui.unit.*\nimport <YOUR_ROOT_PACKAGE_HERE>.ui.theme.MyApplicationTheme\nimport kotlinx.coroutines.launch\n\nclass MainActivity : ComponentActivity() {\n @OptIn(ExperimentalMaterial3Api::class)\n override fun onCreate(savedInstanceState: Bundle?) {\n super.onCreate(savedInstanceState)\n\n Appwrite.init(applicationContext)\n\n setContent {\n MyApplicationTheme {\n Surface(\n modifier = Modifier.fillMaxSize(),\n color = MaterialTheme.colorScheme.background\n ) {\n val coroutineScope = rememberCoroutineScope()\n\n var user by remember { mutableStateOf(\"\") }\n var email by remember { mutableStateOf(\"\") }\n var password by remember { mutableStateOf(\"\") }\n\n if (user.isNotEmpty()) {\n Column(\n modifier = Modifier.fillMaxSize(),\n horizontalAlignment = Alignment.CenterHorizontally,\n verticalArrangement = Arrangement.Center\n ) {\n Text(text = \"Logged in as $user\")\n Button(onClick = {\n coroutineScope.launch {\n Appwrite.onLogout()\n }\n }) {\n Text(\"Logout\")\n }\n }\n }\n\n Column(\n modifier = Modifier.fillMaxSize(),\n horizontalAlignment = Alignment.CenterHorizontally,\n verticalArrangement = Arrangement.Center\n ) {\n TextField(\n value = email,\n onValueChange = { email = it },\n label = { Text(\"Username\") },\n modifier = Modifier\n .fillMaxWidth()\n .padding(16.dp)\n )\n TextField(\n value = password,\n onValueChange = { password = it },\n label = { Text(\"Password\") },\n modifier = Modifier\n .fillMaxWidth()\n .padding(16.dp),\n visualTransformation = PasswordVisualTransformation(),\n keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password)\n )\n Row(\n modifier = Modifier\n .fillMaxWidth()\n .padding(16.dp),\n horizontalArrangement = Arrangement.SpaceBetween\n ) {\n Button(onClick = {\n coroutineScope.launch {\n try {\n Appwrite.onLogin(email, password)\n\n user = email\n } catch (e: Exception) {\n e.printStackTrace()\n }\n }\n }) {\n Text(\"Login\")\n }\n Button(onClick = {\n coroutineScope.launch {\n try {\n Appwrite.onRegister(email, password)\n } catch (e: Exception) {\n e.printStackTrace()\n }\n }\n }) {\n Text(\"Register\")\n }\n }\n }\n }\n }\n }\n }\n}\n```\n{% /section %}\n\n{% section #step-6 step=6 title=\"Type safety with models\" %}\nFor enhanced type safety, you can use custom model classes with the `nestedType` parameter:\n\n```kotlin\nimport io.appwrite.services.TablesDB\n\ndata class User(\n val name: String,\n val email: String,\n val isVerified: Boolean = false\n)\n\n// Usage with type safety\nval tablesDB = TablesDB(Appwrite.client)\n\ntry {\n val users = tablesDB.listRows(\n databaseId = \"[DATABASE_ID]\",\n tableId = \"[TABLE_ID]\",\n nestedType = User::class.java // Enables type safety\n )\n\n for (user in users.rows) {\n Log.d(\"Appwrite\", \"User: ${user.name} (${user.email})\")\n }\n} catch (e: AppwriteException) {\n Log.e(\"Appwrite\", \"Error: ${e.message}\")\n}\n```\n\n{% info title=\"Generate types automatically\" %}\nUse the [Appwrite CLI](/docs/products/databases/type-generation) to generate model classes automatically: `appwrite types ./models`\n{% /info %}\n\n{% /section %}\n\n{% section #step-7 step=7 title=\"All set\" %}\nRun your project by clicking **Run app** in Android Studio.\n{% /section %}"}, {"path": "docs/quick-starts/android-java", "title": "Start with Android (Java)", "description": "Get started with Appwrite on Android using Java and learn how to build secure and scalable apps using our powerful backend.", "content": "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.\n\n{% info title=\"Using Kotlin?\" %}\nCheck out the [Start with Android (Kotlin)](/docs/quick-starts/android) guide.\n{% /info %}\n\n{% section #step-1 step=1 title=\"Create Android project\" %}\nOpen Android Studio and click **New Project** to create a new project.\n\nChoose your desired project template, for example **Empty Activity**, and click **Next**.\n\nNow 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.\n\n{% /section %}\n\n{% section #step-2 step=2 title=\"Create Appwrite project\" %}\nHead to the [Appwrite Console](https://cloud.appwrite.io/console).\n\n{% only_dark %}\n![Create project screen](/images/docs/quick-starts/dark/create-project.avif)\n{% /only_dark %}\n{% only_light %}\n![Create project screen](/images/docs/quick-starts/create-project.avif)\n{% /only_light %}\n\nIf this is your first time using Appwrite, create an account and create your first project.\n\nThen, under **Add a platform**, add an **Android app**.\n\nAdd 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.\n\n{% only_dark %}\n![Add a platform](/images/docs/quick-starts/dark/add-platform.avif)\n{% /only_dark %}\n{% only_light %}\n![Add a platform](/images/docs/quick-starts/add-platform.avif)\n{% /only_light %}\n\nYou can skip optional steps.\n\n{% /section %}\n\n{% section #step-3 step=3 title=\"Add the Appwrite SDK\" %}\nTo add the Appwrite SDK for Android as a dependency, add the following to your app-level **build.gradle** file inside the **dependencies** block.\n\n```groovy\nimplementation \"io.appwrite:sdk-for-android:8.1.0\"\n```\n\nIn 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).\nBe sure to replace the **<PROJECT_ID>** string with your actual Appwrite project ID.\nYou can find your Appwrite project ID in you project settings screen in your Appwrite Console.\n\n```xml\n<manifest ...>\n ...\n <application ...>\n ...\n <!-- Add this inside the `<application>` tag, along side the existing `<activity>` tags -->\n <activity android:name=\"io.appwrite.views.CallbackActivity\" android:exported=\"true\">\n <intent-filter android:label=\"android_web_auth\">\n <action android:name=\"android.intent.action.VIEW\" />\n <category android:name=\"android.intent.category.DEFAULT\" />\n <category android:name=\"android.intent.category.BROWSABLE\" />\n <data android:scheme=\"appwrite-callback-<PROJECT_ID>\" />\n </intent-filter>\n </activity>\n </application>\n</manifest>\n```\n{% /section %}\n\n{% section #step-4 step=4 title=\"Create Appwrite helper class\" %}\nFind your project's ID in the **Settings** page.\n\n{% only_dark %}\n![Project settings screen](/images/docs/quick-starts/dark/project-id.avif)\n{% /only_dark %}\n{% only_light %}\n![Project settings screen](/images/docs/quick-starts/project-id.avif)\n{% /only_light %}\n\nCreate a new file `AppwriteHelper.java` and add the following code to it, replacing `<PROJECT_ID>` with your project ID.\n\n```java\npackage <YOUR_ROOT_PACKAGE_HERE>;\n\nimport android.content.Context;\n\nimport java.util.Map;\n\nimport io.appwrite.Client;\nimport io.appwrite.ID;\nimport io.appwrite.coroutines.CoroutineCallback;\nimport io.appwrite.models.Session;\nimport io.appwrite.models.User;\nimport io.appwrite.services.Account;\n\npublic class AppwriteHelper {\n private static AppwriteHelper instance;\n private Client client;\n private Account account;\n\n private AppwriteHelper(Context context) {\n client = new Client(context)\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\")\n .setProject(\"<PROJECT_ID>\");\n\n account = new Account(client);\n }\n\n public static synchronized AppwriteHelper getInstance(Context context) {\n if (instance == null) {\n instance = new AppwriteHelper(context.getApplicationContext());\n }\n return instance;\n }\n\n public interface AuthCallback<T> {\n void onSuccess(T result);\n void onError(Exception error);\n }\n\n public void login(String email, String password, final AuthCallback<Session> callback) {\n account.createEmailPasswordSession(\n email,\n password,\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n callback.onError(error);\n return;\n }\n callback.onSuccess(result);\n })\n );\n }\n\n public void register(String email, String password, final AuthCallback<User<Map<String, Object>>> callback) {\n account.create(\n ID.unique(),\n email,\n password,\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n callback.onError(error);\n return;\n }\n callback.onSuccess(result);\n })\n );\n }\n\n public void logout(final AuthCallback<Object> callback) {\n account.deleteSession(\n \"current\",\n new CoroutineCallback<>((result, error) -> {\n if (error != null) {\n callback.onError(error);\n return;\n }\n callback.onSuccess(result);\n })\n );\n }\n}\n```\n{% /section %}\n\n{% section #step-5 step=5 title=\"Create a login UI in XML\" %}\nFirst, update your `activity_main.xml` layout file:\n\n```xml\n<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n xmlns:tools=\"http://schemas.android.com/tools\"\n android:layout_width=\"match_parent\"\n android:layout_height=\"match_parent\"\n android:orientation=\"vertical\"\n android:padding=\"16dp\"\n tools:context=\".MainActivity\">\n\n <TextView\n android:id=\"@+id/textViewStatus\"\n android:layout_width=\"match_parent\"\n android:layout_height=\"wrap_content\"\n android:gravity=\"center\"\n android:padding=\"16dp\"\n android:textSize=\"18sp\"\n android:visibility=\"gone\" />\n\n <Button\n android:id=\"@+id/buttonLogout\"\n android:layout_width=\"match_parent\"\n android:layout_height=\"wrap_content\"\n android:text=\"Logout\"\n android:layout_marginBottom=\"16dp\"\n android:visibility=\"gone\" />\n\n <EditText\n android:id=\"@+id/editTextEmail\"\n android:layout_width=\"match_parent\"\n android:layout_height=\"wrap_content\"\n android:layout_marginBottom=\"8dp\"\n android:hint=\"Email\"\n android:inputType=\"textEmailAddress\" />\n\n <EditText\n android:id=\"@+id/editTextPassword\"\n android:layout_width=\"match_parent\"\n android:layout_height=\"wrap_content\"\n android:layout_marginBottom=\"16dp\"\n android:hint=\"Password\"\n android:inputType=\"textPassword\" />\n\n <LinearLayout\n android:layout_width=\"match_parent\"\n android:layout_height=\"wrap_content\"\n android:orientation=\"horizontal\">\n\n <Button\n android:id=\"@+id/buttonLogin\"\n android:layout_width=\"0dp\"\n android:layout_height=\"wrap_content\"\n android:layout_weight=\"1\"\n android:layout_marginEnd=\"8dp\"\n android:text=\"Login\" />\n\n <Button\n android:id=\"@+id/buttonRegister\"\n android:layout_width=\"0dp\"\n android:layout_height=\"wrap_content\"\n android:layout_weight=\"1\"\n android:layout_marginStart=\"8dp\"\n android:text=\"Register\" />\n </LinearLayout>\n\n</LinearLayout>\n```\n{% /section %}\n\n{% section #step-6 step=6 title=\"Create MainActivity\" %}\nNow update your `MainActivity.java` file with the following code:\n\n```java\npackage <YOUR_ROOT_PACKAGE_HERE>;\n\nimport android.os.Bundle;\nimport android.util.Log;\nimport android.view.View;\nimport android.widget.Button;\nimport android.widget.EditText;\nimport android.widget.TextView;\nimport android.widget.Toast;\n\nimport androidx.appcompat.app.AppCompatActivity;\n\nimport java.util.Map;\n\nimport io.appwrite.models.Session;\nimport io.appwrite.models.User;\n\npublic class MainActivity extends AppCompatActivity {\n private static final String TAG = \"MainActivity\";\n\n private EditText editTextEmail;\n private EditText editTextPassword;\n private Button buttonLogin;\n private Button buttonRegister;\n private TextView textViewStatus;\n private Button buttonLogout;\n private AppwriteHelper appwrite;\n\n @Override\n protected void onCreate(Bundle savedInstanceState) {\n super.onCreate(savedInstanceState);\n\n // Initialize Appwrite\n appwrite = AppwriteHelper.getInstance(getApplicationContext());\n\n setContentView(R.layout.activity_main);\n\n // Initialize UI components\n editTextEmail = findViewById(R.id.editTextEmail);\n editTextPassword = findViewById(R.id.editTextPassword);\n buttonLogin = findViewById(R.id.buttonLogin);\n buttonRegister = findViewById(R.id.buttonRegister);\n textViewStatus = findViewById(R.id.textViewStatus);\n buttonLogout = findViewById(R.id.buttonLogout);\n\n // Set up click listeners\n buttonLogin.setOnClickListener(v -> login());\n buttonRegister.setOnClickListener(v -> register());\n buttonLogout.setOnClickListener(v -> logout());\n }\n\n private void login() {\n String email = editTextEmail.getText().toString().trim();\n String password = editTextPassword.getText().toString().trim();\n\n if (email.isEmpty() || password.isEmpty()) {\n Toast.makeText(this, \"Please enter email and password\", Toast.LENGTH_SHORT).show();\n return;\n }\n\n appwrite.login(email, password, new AppwriteHelper.AuthCallback<Session>() {\n @Override\n public void onSuccess(Session result) {\n runOnUiThread(() -> {\n Toast.makeText(MainActivity.this, \"Login successful\", Toast.LENGTH_SHORT).show();\n showLoggedInUI(email);\n });\n }\n\n @Override\n public void onError(Exception error) {\n runOnUiThread(() -> {\n Log.e(TAG, \"Login failed\", error);\n Toast.makeText(MainActivity.this, \"Login failed: \" + error.getMessage(), Toast.LENGTH_SHORT).show();\n });\n }\n });\n }\n\n private void register() {\n String email = editTextEmail.getText().toString().trim();\n String password = editTextPassword.getText().toString().trim();\n\n if (email.isEmpty() || password.isEmpty()) {\n Toast.makeText(this, \"Please enter email and password\", Toast.LENGTH_SHORT).show();\n return;\n }\n\n appwrite.register(email, password, new AppwriteHelper.AuthCallback<User<Map<String, Object>>>() {\n @Override\n public void onSuccess(User<Map<String, Object>> result) {\n runOnUiThread(() -> {\n Toast.makeText(MainActivity.this, \"Registration successful. You can now login.\", Toast.LENGTH_SHORT).show();\n });\n }\n\n @Override\n public void onError(Exception error) {\n runOnUiThread(() -> {\n Log.e(TAG, \"Registration failed\", error);\n Toast.makeText(MainActivity.this, \"Registration failed: \" + error.getMessage(), Toast.LENGTH_SHORT).show();\n });\n }\n });\n }\n\n private void logout() {\n appwrite.logout(new AppwriteHelper.AuthCallback<Object>() {\n @Override\n public void onSuccess(Object result) {\n runOnUiThread(() -> {\n Toast.makeText(MainActivity.this, \"Logout successful\", Toast.LENGTH_SHORT).show();\n showLoginUI();\n });\n }\n\n @Override\n public void onError(Exception error) {\n runOnUiThread(() -> {\n Log.e(TAG, \"Logout failed\", error);\n Toast.makeText(MainActivity.this, \"Logout failed: \" + error.getMessage(), Toast.LENGTH_SHORT).show();\n });\n }\n });\n }\n\n private void showLoggedInUI(String email) {\n editTextEmail.setVisibility(View.GONE);\n editTextPassword.setVisibility(View.GONE);\n buttonLogin.setVisibility(View.GONE);\n buttonRegister.setVisibility(View.GONE);\n\n textViewStatus.setVisibility(View.VISIBLE);\n buttonLogout.setVisibility(View.VISIBLE);\n\n textViewStatus.setText(\"Logged in as \" + email);\n }\n\n private void showLoginUI() {\n editTextEmail.setVisibility(View.VISIBLE);\n editTextPassword.setVisibility(View.VISIBLE);\n buttonLogin.setVisibility(View.VISIBLE);\n buttonRegister.setVisibility(View.VISIBLE);\n\n textViewStatus.setVisibility(View.GONE);\n buttonLogout.setVisibility(View.GONE);\n }\n}\n```\n{% /section %}\n\n{% section #step-7 step=7 title=\"All set\" %}\nRun your project by clicking **Run app** in Android Studio.\n{% /section %}"}, {"path": "docs/quick-starts/angular", "title": "Start with Angular", "description": "Learn how to use Appwrite to add authentication, user management, file storage, and more to your Angular apps.", "content": "Learn how to setup your first Angular project powered by Appwrite.\n{% section #step-1 step=1 title=\"Create project\" %}\nHead to the [Appwrite Console](https://cloud.appwrite.io/console).\n\n{% only_dark %}\n![Create project screen](/images/docs/quick-starts/dark/create-project.avif)\n{% /only_dark %}\n{% only_light %}\n![Create project screen](/images/docs/quick-starts/create-project.avif)\n{% /only_light %}\n\nIf this is your first time using Appwrite, create an account and create your first project.\n\nThen, under **Add a platform**, add a **Web app**. The **Hostname** should be `localhost`.\n\n{% partial file=\"note-on-cors.md\" /%}\n\n{% only_dark %}\n![Add a platform](/images/docs/quick-starts/dark/add-platform.avif)\n{% /only_dark %}\n{% only_light %}\n![Add a platform](/images/docs/quick-starts/add-platform.avif)\n{% /only_light %}\n\nYou can skip optional steps.\n\n{% /section %}\n{% section #step-2 step=2 title=\"Create Angular project\" %}\nCreate an Angular project.\n\nIf you don't have Angular CLI installed, run this command.\n```sh\nnpm install -g @angular/cli\n```\n\nThen, create a project.\n\n```sh\nng new my-app\ncd my-app\n```\n\n{% /section %}\n{% section #step-3 step=3 title=\"Install Appwrite\" %}\n\nInstall the JavaScript Appwrite SDK.\n\n```sh\nnpm install appwrite\n```\n{% /section %}\n{% section #step-4 step=4 title=\"Import Appwrite\" %}\nFind your project's ID in the **Settings** page.\n\n{% only_dark %}\n![Project settings screen](/images/docs/quick-starts/dark/project-id.avif)\n{% /only_dark %}\n{% only_light %}\n![Project settings screen](/images/docs/quick-starts/project-id.avif)\n{% /only_light %}\nCreate a new file `src/lib/appwrite.ts` and add the following code to it, replace `<PROJECT_ID>` with your project ID.\n\n```client-web\nimport { Client, Account} from 'appwrite';\n\nexport const client = new Client();\n\nclient\n .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')\n .setProject('<PROJECT_ID>'); // Replace with your project ID\n\nexport const account = new Account(client);\nexport { ID } from 'appwrite';\n```\n{% /section %}\n{% section #step-5 step=5 title=\"Create a login page\" %}\nFirst, add imports for the `FormsModule` from Angular to handle the login form..\n\n```ts\nimport { FormsModule } from '@angular/forms';\n...\n@NgModule({\n declarations: [\n // ...\n ],\n imports: [\n // ...\n FormsModule\n ],\n providers: [],\n bootstrap: [AppComponent]\n})\nexport class AppModule { }\n```\n\nThen, replace the contents of `src/app/app.component.html`.\n```html\n<div>\n <p>\n {{ loggedInUser ? 'Logged in as ' + loggedInUser.name : 'Not logged in' }}\n </p>\n\n <div>\n <input type=\"email\" placeholder=\"Email\" [(ngModel)]=\"email\" />\n <input type=\"password\" placeholder=\"Password\" [(ngModel)]=\"password\" />\n <input type=\"text\" placeholder=\"Name\" [(ngModel)]=\"name\" />\n\n <button (click)=\"login(email, password)\">\n Login\n </button>\n\n <button (click)=\"register(email, password, name)\">\n Register\n </button>\n\n <button (click)=\"logout()\">\n Logout\n </button>\n </div>\n</div>\n```\n\nLastly, update `src/app/app.component.ts`.\n```ts\nimport { Component } from '@angular/core';\nimport { account, ID } from '../lib/appwrite';\n@Component({\n selector: 'app-root',\n templateUrl: './app.component.html',\n styleUrls: ['./app.component.css']\n})\nexport class AppComponent {\n loggedInUser: any = null;\n email: string = '';\n password: string = '';\n name: string = '';\n\n async login(email: string, password: string) {\n await account.createEmailPasswordSession({\n email,\n password\n });\n this.loggedInUser = await account.get();\n }\n\n async register(email: string, password: string, name: string) {\n await account.create({\n userId: ID.unique(),\n email,\n password,\n name\n });\n this.login(email, password);\n }\n\n async logout() {\n await account.deleteSession({\n sessionId: 'current'\n });\n this.loggedInUser = null;\n }\n}\n```\n\n{% /section %}\n\n{% section #step-6 step=6 title=\"All set\" %}\nRun your project with `ng serve --port 3000` and open [Localhost on Port 3000](http://localhost:3000) in your browser.\n{% /section %}"}, {"path": "docs/quick-starts/apple", "title": "Start with Apple", "description": "Build iOS apps with Appwrite and learn how to use our powerful backend to add authentication, user management, file storage, and more.", "content": "Learn how to setup your first Apple project powered by Appwrite and the [Appwrite Apple SDK](https://github.com/appwrite/sdk-for-apple).\n\n{% section #step-1 step=1 title=\"Create Apple project\" %}\nOpen Xcode and click **Create a new Xcode project**.\n\nChoose your desired project template, for example **iOS App**, and click **Next**.\n\nNow 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.\n\nChoose a directory for your project in and click **Create** to create your project.\n\n{% /section %}\n\n{% section #step-2 step=2 title=\"Create Appwrite project\" %}\nHead to the [Appwrite Console](https://cloud.appwrite.io/console).\n\n{% only_dark %}\n![Create project screen](/images/docs/quick-starts/dark/create-project.avif)\n{% /only_dark %}\n{% only_light %}\n![Create project screen](/images/docs/quick-starts/create-project.avif)\n{% /only_light %}\n\nIf this is your first time using Appwrite, create an account and create your first project.\n\nThen, 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.\n\nAdd 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.\n\n{% only_dark %}\n![Add a platform](/images/docs/quick-starts/dark/add-platform.avif)\n{% /only_dark %}\n{% only_light %}\n![Add a platform](/images/docs/quick-starts/add-platform.avif)\n{% /only_light %}\n\nYou can skip optional steps.\n\n{% /section %}\n\n{% section #step-3 step=3 title=\"Add the Appwrite SDK\" %}\nTo add the Appwrite SDK for Apple as a dependency, open the **File** menu and click **Add Packages**.\n\nIn the **Package URL** search box, enter https://github.com/appwrite/sdk-for-apple. \n\nOnce the SDK is found, use `10.1.0` as version, select **Up to Next Major Version** as your **Dependency Rule** and click **Add Package**.\n\nWhen dependency resolution is complete, click **Add Package** again to add the SDK package to your target.\n\nIn order to allow creating OAuth sessions, the following URL scheme must be added to your **Info.plist** file.\n\n```xml\n<key>CFBundleURLTypes</key>\n<array>\n<dict>\n <key>CFBundleTypeRole</key>\n <string>Editor</string>\n <key>CFBundleURLName</key>\n <string>io.appwrite</string>\n <key>CFBundleURLSchemes</key>\n <array>\n <string>appwrite-callback-<PROJECT_ID></string>\n </array>\n</dict>\n</array>\n```\n\nIf you're using UIKit as opposed to SwiftUI, you will also need to add the following to your **SceneDelegate.swift** file.\n\n```swift\nfunc scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {\n guard let url = URLContexts.first?.url,\n url.absoluteString.contains(\"appwrite-callback\") else {\n return\n }\n\n WebAuthComponent.handleIncomingCookie(from: url)\n}\n```\n{% /section %}\n\n{% section #step-4 step=4 title=\"Create Appwrite Singleton\" %}\nFind your project's ID in the **Settings** page. \n\n{% only_dark %}\n![Project settings screen](/images/docs/quick-starts/dark/project-id.avif)\n{% /only_dark %}\n{% only_light %}\n![Project settings screen](/images/docs/quick-starts/project-id.avif)\n{% /only_light %}\nCreate a new file `Appwrite.swift` and add the following code to it, replacing `<PROJECT_ID>` with your project ID.\n\n```swift\nimport Foundation\nimport Appwrite\nimport JSONCodable\n\nclass Appwrite {\n var client: Client\n var account: Account\n \n public init() {\n self.client = Client()\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\")\n .setProject(\"<PROJECT_ID>\")\n \n self.account = Account(client)\n }\n \n public func onRegister(\n _ email: String,\n _ password: String\n ) async throws -> User<[String: AnyCodable]> {\n try await account.create(\n userId: ID.unique(),\n email: email,\n password: password\n )\n }\n \n public func onLogin(\n _ email: String,\n _ password: String\n ) async throws -> Session {\n try await account.createEmailPasswordSession(\n email: email,\n password: password\n )\n }\n \n public func onLogout() async throws {\n _ = try await account.deleteSession(\n sessionId: \"current\"\n )\n }\n \n}\n\n```\n{% /section %}\n{% section #step-5 step=5 title=\"Create a login page\" %}\nAdd the following code to `ContentView.swift`.\n\n```swift\nimport SwiftUI\n\nclass ViewModel: ObservableObject {\n @Published var email: String = \"\"\n @Published var password: String = \"\"\n}\n\nstruct ContentView: View {\n @ObservedObject var viewModel = ViewModel()\n let appwrite = Appwrite()\n\n var body: some View {\n VStack {\n TextField(\n \"Email\",\n text: $viewModel.email\n )\n SecureField(\n \"Password\",\n text: $viewModel.password\n )\n Button(\n action: { Task {\n try await appwrite.onRegister(\n viewModel.email,\n viewModel.password\n )\n }},\n label: {\n Text(\"Register\")\n }\n )\n Button(\n action: { Task {\n try await appwrite.onLogin(\n viewModel.email,\n viewModel.password\n )\n }},\n label: {\n Text(\"Login\")\n }\n )\n }\n .padding()\n }\n}\n```\n{% /section %}\n\n{% section #step-6 step=6 title=\"Type safety with models\" %}\nFor enhanced type safety, you can use custom model structs with the `nestedType` parameter:\n\n```swift\nimport Appwrite\nstruct User: Codable {\n let name: String\n let email: String\n let isVerified: Bool\n}\n\n// Usage with type safety\nlet appwrite = Appwrite()\nlet tablesDB = TablesDB(appwrite.client)\n\ndo {\n let users = try await tablesDB.listRows(\n databaseId: \"[DATABASE_ID]\",\n tableId: \"[TABLE_ID]\",\n nestedType: User.self // Enables type safety\n )\n\n for user in users.rows {\n print(\"User: \\(user.name) (\\(user.email))\")\n }\n} catch {\n print(\"Error: \\(error.localizedDescription)\")\n}\n```\n\n{% info title=\"Generate types automatically\" %}\nUse the [Appwrite CLI](/docs/products/databases/type-generation) to generate model structs automatically: `appwrite types collection`\n{% /info %}\n\n{% /section %}\n\n{% section #step-7 step=7 title=\"All set\" %}\nRun your project by clicking **Start active scheme** in Xcode.\n{% /section %}"}, {"path": "docs/quick-starts/astro", "title": "Start with Astro", "description": "Learn how to use Appwrite to add authentication, user management, file storage, and more to your Astro apps.", "content": "Improve the docs, add this guide.\n\nWe still don't have this guide in place, but we do have some great news. \nThe Appwrite docs, just like Appwrite, is completely open sourced.\nThis means, anyone can help improve them and add new guides and tutorials. \n\nIf you see this page, **we're actively looking for contributions to this page**.\nFollow 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."}, {"path": "docs/quick-starts/dart", "title": "Start with Dart", "description": "Build Flutter apps with Appwrite and learn how to use our powerful backend to add authentication, user management, file storage, and more.", "content": "Learn how to setup your first Dart project powered by Appwrite.\n{% section #step-1 step=1 title=\"Create project\" %}\nHead to the [Appwrite Console](https://cloud.appwrite.io/console).\n\n{% only_dark %}\n![Create project screen](/images/docs/quick-starts/dark/create-project.avif)\n{% /only_dark %}\n{% only_light %}\n![Create project screen](/images/docs/quick-starts/create-project.avif)\n{% /only_light %}\n\nIf this is your first time using Appwrite, create an account and create your first project.\n\n\nThen, under **Integrate with your server**, add an **API Key** with the following scopes.\n\n| Category {% width=120 %} | Required scopes | Purpose |\n|-----------|-----------------------|---------|\n| Database | `databases.write` | Allows API key to create, update, and delete [databases](/docs/products/databases/databases). |\n| | `tables.write` | Allows API key to create, update, and delete [tables](/docs/products/databases/tables). |\n| | `columns.write` | Allows API key to create, update, and delete [columns](/docs/products/databases/tables#columns). |\n| | `rows.read` | Allows API key to read [rows](/docs/products/databases/rows). |\n| | `rows.write` | Allows API key to create, update, and delete [rows](/docs/products/databases/rows). |\n\nOther scopes are optional.\n\n{% only_dark %}\n![Create project screen](/images/docs/quick-starts/dark/integrate-server.avif)\n{% /only_dark %}\n{% only_light %}\n![Create project screen](/images/docs/quick-starts/integrate-server.avif)\n{% /only_light %}\n\n{% /section %}\n{% section #step-2 step=2 title=\"Create Dart project\" %}\nCreate a Dart CLI application.\n\n```sh\ndart create -t console my_app\ncd my_app\n```\n\nAfter entering the project directory, remove the `lib/` and `test/` directories.\n\n{% /section %}\n{% section #step-3 step=3 title=\"Install Appwrite\" %}\n\nInstall the Dart Appwrite SDK.\n\n```sh\ndart pub add dart_appwrite:16.0.0\n```\n{% /section %}\n{% section #step-4 step=4 title=\"Import Appwrite\" %}\n\nFind your project ID in the **Settings** page.\n\n{% only_dark %}\n![Project settings screen](/images/docs/quick-starts/dark/project-id.avif)\n{% /only_dark %}\n{% only_light %}\n![Project settings screen](/images/docs/quick-starts/project-id.avif)\n{% /only_light %}\n\n Also, click on the **View API Keys** button to find the API key that was created earlier. \n\nOpen `bin/my_app.dart` and initialize the Appwrite Client. Replace `<PROJECT_ID>` with your project ID and `<YOUR_API_KEY>` with your API key.\n\n```dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\n\nvar client = Client();\n\nFuture<void> main() async {\n client\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\")\n .setProject(\"<PROJECT_ID>\")\n .setKey(\"<YOUR_API_KEY>\");\n}\n```\n\n{% /section %}\n{% section #step-5 step=5 title=\"Initialize database\" %}\n\nOnce the Appwrite Client is initialized, create a function to configure a todo table.\n\n```dart\nvar tablesDB;\nvar todoDatabase;\nvar todoTable;\n\nFuture<void> prepareDatabase() async {\n tablesDB = TablesDB(client);\n\n todoDatabase = await tablesDB.create(\n databaseId: ID.unique(), \n name: 'TodosDB'\n );\n\n todoTable = await tablesDB.createTable(\n databaseId: todoDatabase.$id, \n tableId: ID.unique(), \n name: 'Todos'\n );\n\n await tablesDB.createVarcharColumn(\n databaseId: todoDatabase.$id,\n tableId: todoTable.$id,\n key: 'title',\n size: 255,\n xrequired: true\n );\n\n await tablesDB.createTextColumn(\n databaseId: todoDatabase.$id,\n tableId: todoTable.$id,\n key: 'description',\n xrequired: false,\n xdefault: 'This is a test description'\n );\n\n await tablesDB.createBooleanColumn(\n databaseId: todoDatabase.$id,\n tableId: todoTable.$id,\n key: 'isComplete',\n xrequired: true\n );\n}\n```\n\n{% /section %}\n{% section #step-6 step=6 title=\"Add rows\" %}\nCreate a function to add some mock data into your new table.\n```dart\nFuture<void> seedDatabase() async {\n var testTodo1 = {\n 'title': 'Buy apples',\n 'description': 'At least 2KGs',\n 'isComplete': true\n };\n\n var testTodo2 = {\n 'title': 'Wash the apples',\n 'isComplete': true\n };\n\n var testTodo3 = {\n 'title': 'Cut the apples',\n 'description': 'Don\\'t forget to pack them in a box',\n 'isComplete': false\n };\n\n await tablesDB.createRow(\n databaseId: todoDatabase.$id,\n tableId: todoTable.$id,\n rowId: ID.unique(),\n data: testTodo1\n );\n\n await tablesDB.createRow(\n databaseId: todoDatabase.$id,\n tableId: todoTable.$id,\n rowId: ID.unique(),\n data: testTodo2\n );\n\n await tablesDB.createRow(\n databaseId: todoDatabase.$id,\n tableId: todoTable.$id,\n rowId: ID.unique(),\n data: testTodo3\n );\n}\n```\n\n{% /section %}\n{% section #step-7 step=7 title=\"Retrieve rows\" %}\n\nCreate a function to retrieve the mock todo data.\n```dart\nFuture<void> getTodos() async {\n var todos = await tablesDB.listRows(\n databaseId: todoDatabase.$id, \n tableId: todoTable.$id\n );\n\n todos.rows.forEach((todo) {\n print('Title: ${todo.data['title']}\\nDescription: ${todo.data['description']}\\nIs Todo Complete: ${todo.data['isComplete']}\\n\\n');\n });\n}\n```\n\nFinally, revisit the `main()` function and call the functions created in previous steps.\n```dart\nFuture<void> main() async {\n client\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\")\n .setProject(\"<PROJECT_ID>\")\n .setKey(\"<YOUR_API_KEY>\");\n\n await prepareDatabase();\n await Future.delayed(const Duration(seconds: 1));\n await seedDatabase();\n await getTodos();\n}\n```\n\n{% /section %}\n{% section #step-8 step=8 title=\"All set\" %}\n\nRun your project with `dart run bin/my_app.dart` and view the response in your console.\n\n{% /section %}"}, {"path": "docs/quick-starts/deno", "title": "Start with Deno", "description": "Dive into our step-by-step guide on integrating Appwrite with your Deno server backend application. Get your backend up and running quickly with this tutorial.", "content": "{% info title=\"Deno SDK Deprecation\" %}\nThe 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.\n{% /info %}\n\nLearn how to setup your first Deno project powered by Appwrite.\n{% section #step-1 step=1 title=\"Create project\" %}\nHead to the [Appwrite Console](https://cloud.appwrite.io/console).\n\nIf this is your first time using Appwrite, create an account and create your first project.\n\n{% only_dark %}\n![Create project screen](/images/docs/quick-starts/dark/create-project.avif)\n{% /only_dark %}\n{% only_light %}\n![Create project screen](/images/docs/quick-starts/create-project.avif)\n{% /only_light %}\n\nThen, under **Integrate with your server**, add an **API Key** with the following scopes.\n\n{% only_dark %}\n![Create project screen](/images/docs/quick-starts/dark/integrate-server.avif)\n{% /only_dark %}\n{% only_light %}\n![Create project screen](/images/docs/quick-starts/integrate-server.avif)\n{% /only_light %}\n\n| Category {% width=120 %} | Required scopes | Purpose |\n|-----------|-----------------------|---------|\n| Database | `databases.write` | Allows API key to create, update, and delete [databases](/docs/products/databases/databases). |\n| | `tables.write` | Allows API key to create, update, and delete [tables](/docs/products/databases/tables). |\n| | `columns.write` | Allows API key to create, update, and delete [columns](/docs/products/databases/tables#columns). |\n| | `rows.read` | Allows API key to read [rows](/docs/products/databases/rows). |\n| | `rows.write` | Allows API key to create, update, and delete [rows](/docs/products/databases/rows). |\n\nOther scopes are optional.\n\n{% /section %}\n{% section #step-2 step=2 title=\"Create Deno project\" %}\nCreate a Deno CLI application.\n\n```sh\nmkdir my-app\ncd my-app\necho \"console.log('Hello, Deno!');\" > mod.ts\n```\n\n{% /section %}\n{% section #step-3 step=3 title=\"Install Appwrite\" %}\n\nInstall the Appwrite SDK using npm specifiers at the top of your file.\n\n```\n// import all as sdk\nimport * as sdk from \"npm:node-appwrite\";\n\n// import only what you need\nimport { Client, ... other imports } from \"npm:node-appwrite\";\n```\n\n{% /section %}\n{% section #step-4 step=4 title=\"Import Appwrite\" %}\n\nFind your project ID in the **Settings** page. Also, click on the **View API Keys** button to find the API key that was created earlier.\n\n{% only_dark %}\n![Project settings screen](/images/docs/quick-starts/dark/project-id.avif)\n{% /only_dark %}\n{% only_light %}\n![Project settings screen](/images/docs/quick-starts/project-id.avif)\n{% /only_light %}\n\nOpen `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.\n\n```ts\nimport { Client, ID, TablesDB, Models } from \"npm:node-appwrite\";\n\nconst client: Client = new Client();\n\nclient\n .setEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\")\n .setProject(\"<PROJECT_ID>\")\n .setKey(\"<YOUR_API_KEY>\");\n```\n\n{% /section %}\n{% section #step-5 step=5 title=\"Initialize database\" %}\n\nOnce the Appwrite Client is initialized, create a function to configure a todo table.\n\n```ts\nconst tablesDB: TablesDB = new TablesDB(client);\n\nvar todoDatabase: Models.Database;\nvar todoTable: Models.Table;\n\ninterface Todo {\n title: string;\n description: string;\n isComplete?: boolean;\n}\n\nasync function prepareDatabase(): Promise<void> {\n todoDatabase = await tablesDB.create({\n databaseId: ID.unique(),\n name: 'TodosDB'\n });\n\n todoTable = await tablesDB.createTable({\n databaseId: todoDatabase.$id,\n tableId: ID.unique(),\n name: 'Todos'\n });\n\n await tablesDB.createVarcharColumn({\n databaseId: todoDatabase.$id,\n tableId: todoTable.$id,\n key: 'title',\n size: 255,\n required: true\n });\n\n await tablesDB.createTextColumn({\n databaseId: todoDatabase.$id,\n tableId: todoTable.$id,\n key: 'description',\n required: false,\n xdefault: 'This is a test description'\n });\n\n await tablesDB.createBooleanColumn({\n databaseId: todoDatabase.$id,\n tableId: todoTable.$id,\n key: 'isComplete',\n required: true\n });\n}\n```\n\n{% /section %}\n{% section #step-6 step=6 title=\"Add rows\" %}\nCreate a function to add some mock data into your new table.\n\n```ts\nasync function seedDatabase(): Promise<void> {\n const testTodo1: Todo = {\n title: 'Buy apples',\n description: 'At least 2KGs',\n isComplete: true\n };\n\n const testTodo2: Todo = {\n title: 'Wash the apples',\n isComplete: true\n };\n\n const testTodo3: Todo = {\n title: 'Cut the apples',\n description: 'Don\\'t forget to pack them in a box',\n isComplete: false\n };\n\n await tablesDB.createRow({\n databaseId: todoDatabase.$id,\n tableId: todoTable.$id,\n rowId: ID.unique(),\n data: testTodo1\n });\n await tablesDB.createRow({\n databaseId: todoDatabase.$id,\n tableId: todoTable.$id,\n rowId: ID.unique(),\n data: testTodo2\n });\n await tablesDB.createRow({\n databaseId: todoDatabase.$id,\n tableId: todoTable.$id,\n rowId: ID.unique(),\n data: testTodo3\n });\n}\n```\n\n{% /section %}\n{% section #step-7 step=7 title=\"Retrieve rows\" %}\n\nCreate a function to retrieve the mock todo data and a function to execute the requests in order.\nRun the functions to by calling `runAllTasks();`.\n\n```ts\nasync function getTodos(): Promise<void> {\n const todos = await tablesDB.listRows({\n databaseId: todoDatabase.$id,\n tableId: todoTable.$id\n });\n\n todos.rows.forEach((todo: Todo) => {\n console.log(`Title: ${todo.title}\\nDescription: ${todo.description}\\nIs Todo Complete: ${todo.isComplete}\\n\\n`);\n });\n}\n\nasync function runAllTasks(): Promise<void> {\n await prepareDatabase();\n await seedDatabase();\n await getTodos();\n}\nrunAllTasks();\n```\n\n{% /section %}\n\n{% section #step-8 step=8 title=\"All set\" %}\n\nRun your project with `deno mod.ts` and view the response in your console.\n\n{% /section %}"}, {"path": "docs/quick-starts/dotnet", "title": "Start with .NET", "description": "Learn to get started with server integrations with Appwrite .NET SDK.", "content": "Learn how to setup your first .NET project powered by Appwrite.\n{% section #step-1 step=1 title=\"Create project\" %}\nHead to the [Appwrite Console](https://cloud.appwrite.io/console).\n\nIf this is your first time using Appwrite, create an account and create your first project.\n\n{% only_dark %}\n![Create project screen](/images/docs/quick-starts/dark/create-project.avif)\n{% /only_dark %}\n{% only_light %}\n![Create project screen](/images/docs/quick-starts/create-project.avif)\n{% /only_light %}\n\nThen, under **Integrate with your server**, add an **API Key** with the following scopes.\n\n{% only_dark %}\n![Server integrations](/images/docs/quick-starts/dark/integrate-server.avif)\n{% /only_dark %}\n{% only_light %}\n![Server integrations](/images/docs/quick-starts/integrate-server.avif)\n{% /only_light %}\n| Category {% width=120 %} | Required scopes | Purpose |\n|-----------|-----------------------|---------|\n| Database | `databases.write` | Allows API key to create, update, and delete [databases](/docs/products/databases/databases). |\n| | `tables.write` | Allows API key to create, update, and delete [tables](/docs/products/databases/tables). |\n| | `columns.write` | Allows API key to create, update, and delete [columns](/docs/products/databases/tables#columns). |\n| | `rows.read` | Allows API key to read [rows](/docs/products/databases/rows). |\n| | `rows.write` | Allows API key to create, update, and delete [rows](/docs/products/databases/rows). |\n\nOther scopes are optional.\n\n{% only_dark %}\n![Project settings screen](/images/docs/quick-starts/dark/project-id.avif)\n{% /only_dark %}\n{% only_light %}\n![Project settings screen](/images/docs/quick-starts/project-id.avif)\n{% /only_light %}\n\n{% /section %}\n{% section #step-2 step=2 title=\"Create .NET project\" %}\nCreate a .NET CLI application.\n\n```sh\ndotnet new console -o MyApp\ncd MyApp\n```\n\n{% /section %}\n{% section #step-3 step=3 title=\"Install Appwrite\" %}\n\nInstall the .NET Appwrite SDK.\n\n```sh\ndotnet add package Appwrite --version 0.13.0\n```\n{% /section %}\n{% section #step-4 step=4 title=\"Import Appwrite\" %}\n\nFind your project ID in the **Settings** page. Also, click on the **View API Keys** button to find the API key that was created earlier. \n\n{% only_dark %}\n![Project settings screen](/images/docs/quick-starts/dark/project-id.avif)\n{% /only_dark %}\n{% only_light %}\n![Project settings screen](/images/docs/quick-starts/project-id.avif)\n{% /only_light %}\n\nOpen the file `Program.cs` and initialize the Appwrite Client. Replace `<PROJECT_ID>` with your project ID and `<YOUR_API_KEY>` with your API key.\n\n```csharp\nusing Appwrite;\nusing Appwrite.Models;\nusing Appwrite.Services;\n\nvar client = new Client();\n\nclient\n .SetEndpoint(\"https://<REGION>.cloud.appwrite.io/v1\")\n .SetProject(\"<PROJECT_ID>\")\n .SetKey(\"<YOUR_API_KEY>\");\n```\n\n{% /section %}\n{% section #step-5 step=5 title=\"Initialize database\" %}\n\nOnce the Appwrite Client is initialized, create a function to configure a todo table.\n\n```csharp\nvar tablesDB = new TablesDB(client);\n\nDatabase todoDatabase;\nTable todoTable;\n\ntodoDatabase = await tablesDB.Create(\n databaseId: ID.Unique(),\n name: \"TodosDB\"\n);\n\ntodoTable = await tablesDB.CreateTable(\n databaseId: todoDatabase.Id,\n tableId: ID.Unique(),\n name: \"Todos\"\n);\n\nawait tablesDB.CreateVarcharColumn(\n databaseId: todoDatabase.Id,\n tableId: todoTable.Id,\n key: \"title\",\n size: 255,\n required: true\n);\n\nawait tablesDB.CreateTextColumn(\n databaseId: todoDatabase.Id,\n tableId: todoTable.Id,\n key: \"description\",\n required: false,\n xdefault: \"This is a test description\"\n);\n\nawait tablesDB.CreateBooleanColumn(\n databaseId: todoDatabase.Id,\n tableId: todoTable.Id,\n key: \"isComplete\",\n required: true\n);\n```\n\n{% /section %}\n{% section #step-6 step=6 title=\"Add rows\" %}\nCreate a function to add some mock data into your new table.\n```csharp\nvar testTodo1 = new Dictionary<string, object>()\n{\n {\"title\", \"Buy apples\"},\n {\"description\", \"At least 2KGs\"},\n {\"isComplete\", true}\n};\n\nvar testTodo2 = new Dictionary<string, object>()\n{\n {\"title\", \"Wash the apples\"},\n {\"isComplete\", true}\n};\n\nvar testTodo3 = new Dictionary<string, object>()\n{\n {\"title\", \"Cut the apples\"},\n {\"description\", \"Don't forget to pack them in a box\"},\n {\"isComplete\", false}\n};\n\nawait tablesDB.CreateRow(\n databaseId: todoDatabase.Id,\n tableId: todoTable.Id,\n rowId: ID.Unique(),\n data: testTodo1\n);\n\nawait tablesDB.CreateRow(\n databaseId: todoDatabase.Id,\n tableId: todoTable.Id,\n rowId: ID.Unique(),\n data: testTodo2\n);\n\nawait tablesDB.CreateRow(\n databaseId: todoDatabase.Id,\n tableId: todoTable.Id,\n rowId: ID.Unique(),\n data: testTodo3\n);\n```\n\n{% /section %}\n{% section #step-7 step=7 title=\"Retrieve rows\" %}\n\nCreate a function to retrieve the mock todo data.\n\n```csharp\nvar todos = await tablesDB.ListRows(\n databaseId: todoDatabase.Id,\n tableId: todoTable.Id\n);\n\nforeach (var todo in todos.Rows)\n{\n Console.WriteLine($\"Title: {todo.Data[\"title\"]}\\nDescription: {todo.Data[\"description\"]}\\nIs Todo Complete: {todo.Data[\"isComplete\"]}\\n\\n\");\n}\n```\n\n{% /section %}\n\n{% section #step-8 step=8 title=\"All set\" %}\n\nRun your project with `dotnet run` and view the response in your console.\n\n{% /section %}"}, {"path": "docs/quick-starts/flutter", "title": "Start with Flutter", "description": "Build Flutter apps with Appwrite and learn how to use our powerful backend to add authentication, user management, file storage, and more.", "content": "Learn how to setup your first Flutter project powered by Appwrite.\n\n{% section #step-1 step=1 title=\"Create Flutter project\" %}\nCreate a Flutter project.\n\n```sh\nflutter create my_app && cd my_app\n```\n{% /section %}\n\n{% section #step-2 step=2 title=\"Create project\" %}\nHead to the [Appwrite Console](https://cloud.appwrite.io/console).\n\n{% only_dark %}\n![Create project screen](/images/docs/quick-starts/dark/create-project.avif)\n{% /only_dark %}\n{% only_light %}\n![Create project screen](/images/docs/quick-starts/create-project.avif)\n{% /only_light %}\n\nIf this is your first time using Appwrite, create an account and create your first project.\n\nThen, under **Add a platform**, add a **Flutter app**. You can choose between many different platforms.\n\n{% tabs %}\n{% tabsitem #web title=\"Web\" %}\nAdd your app **name** and **Hostname**. If you're testing your app locally, **Hostname** should be `localhost`.\n\nFor 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.\n\n```html\n<!DOCTYPE html>\n<title>Authentication complete\n

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

\n\n```\n\nThe 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.\nThe path must point to the created HTML file, `/auth.html` in this case.\nThe 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.\n\n{% info title=\"Flutter web cross-domain communication & cookies\" %}\nWhile 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.\nWhen 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.\nIn production, Appwrite allows you to set multiple [custom-domains](/docs/products/network/custom-domains) for each project.\n{% /info %}\n\n{% /tabsitem %}\n\n{% tabsitem #ios title=\"iOS\" %}\n\nAdd your app **name** and **Bundle ID**. You can find your **Bundle Identifier** in the **General** tab for your app's primary target in XCode.\n\nThe Appwrite SDK uses `ASWebAuthenticationSession` on iOS 12+ and `SFAuthenticationSession` on iOS 11 to allow OAuth authentication.\nYou 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.\n\n1. In **XCode**, open `Runner.xcworkspace` in your app's iOS folder.\n2. 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**.\n3. Select the **General** tab.\n4. In **Deployment Info** > **Target**, select iOS 11.0 or above\n{% /tabsitem %}\n\n{% tabsitem #android title=\"Android\" %}\nAdd 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.\n\nIn order to capture the Appwrite OAuth callback url, the following activity needs to be added inside the `` tag, along side the existing `` tags in your [AndroidManifest.xml](https://github.com/appwrite/playground-for-flutter/blob/master/android/app/src/main/AndroidManifest.xml).\nBe sure to replace the **** string with your actual Appwrite project ID.\nYou can find your Appwrite project ID in you project settings screen in your Appwrite Console.\n\n```xml\n\n ...\n \n ...\n \n \n \n \n \n \n \" />\n \n \n \n\n```\n{% /tabsitem %}\n\n{% tabsitem #linux title=\"Linux\" %}\n\nAdd your app **name** and **package name**.\nYour package name is generally the **name** in your [pubspec.yaml](https://github.com/appwrite/playground-for-flutter/blob/master/pubspec.yaml#L1) file.\nIf you cannot find the correct package name, run the application in Linux and make any request with proper exception handling.\nYou should get the application ID needed to add in the received error message.\n\n{% /tabsitem %}\n\n{% tabsitem #macos title=\"macOS\" %}\n\nAdd your app **name** and **Bundle ID**. You can find your **Bundle Identifier** in the **General** tab for your app's primary target in XCode.\n\nThe 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.\n\nIn order to capture the Appwrite OAuth 2 callback url, the following URL scheme needs to added to your `Info.plist`.\n\n```xml\nCFBundleURLTypes\n\n\n CFBundleTypeRole\n Editor\n CFBundleURLName\n io.appwrite\n CFBundleURLSchemes\n \n appwrite-callback-\n \n\n\n```\n{% /tabsitem %}\n\n{% tabsitem #windows title=\"Windows\" %}\n\nFor **Windows**, add your app *name* and *package name*.\nYour package name is generally the **name** in your [pubspec.yaml](https://github.com/appwrite/playground-for-flutter/blob/master/pubspec.yaml#L1) file.\nIf 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.\n{% /tabsitem %}\n\n{% /tabs %}\n\n{% only_dark %}\n![Add a platform](/images/docs/quick-starts/dark/add-platform.avif)\n{% /only_dark %}\n{% only_light %}\n![Add a platform](/images/docs/quick-starts/add-platform.avif)\n{% /only_light %}\n\nYou can skip optional steps.\n\n{% /section %}\n\n{% section #step-3 step=3 title=\"Install Appwrite\" %}\nInstall the Appwrite SDK for Flutter.\n\n```sh\nflutter pub add appwrite:17.0.0\n```\n\n{% /section %}\n\n{% section #step-4 step=4 title=\"Import Appwrite\" %}\nFind your project's ID in the **Settings** page.\n\n{% only_dark %}\n![Project settings screen](/images/docs/quick-starts/dark/project-id.avif)\n{% /only_dark %}\n{% only_light %}\n![Project settings screen](/images/docs/quick-starts/project-id.avif)\n{% /only_light %}\nOpen the generated `lib/main.dart` and add the following code to it, replace `` with your project ID.\nThis imports and initializes Appwrite.\n\n```dart\nimport 'package:flutter/material.dart';\nimport 'package:appwrite/appwrite.dart';\nimport 'package:appwrite/models.dart' as models;\n\nvoid main() {\n WidgetsFlutterBinding.ensureInitialized();\n Client client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\");\n Account account = Account(client);\n\n runApp(MaterialApp(\n home: MyApp(account: account),\n ));\n}\nclass MyApp extends StatefulWidget {\n final Account account;\n\n MyApp({required this.account});\n\n @override\n MyAppState createState() {\n return MyAppState();\n }\n}\n```\n{% /section %}\n{% section #step-5 step=5 title=\"Create a login page\" %}\nThen, append the following widgets to `lib/main.dart` create your login page.\n\n```dart\nclass MyAppState extends State {\n models.User? loggedInUser;\n final TextEditingController emailController = TextEditingController();\n final TextEditingController passwordController = TextEditingController();\n final TextEditingController nameController = TextEditingController();\n\n Future login(String email, String password) async {\n await widget.account.createEmailPasswordSession(\n email: email,\n password: password,\n );\n final user = await widget.account.get();\n setState(() {\n loggedInUser = user;\n });\n }\n\n Future register(String email, String password, String name) async {\n await widget.account.create(\n userId: ID.unique(),\n email: email,\n password: password,\n name: name,\n );\n await login(email, password);\n }\n\n Future logout() async {\n await widget.account.deleteSession(sessionId: 'current');\n setState(() {\n loggedInUser = null;\n });\n }\n\n @override\n Widget build(BuildContext context) {\n return MaterialApp(\n home: Scaffold(\n body: Column(\n crossAxisAlignment: CrossAxisAlignment.start,\n children: [\n Text(loggedInUser != null\n ? 'Logged in as ${loggedInUser!.name}'\n : 'Not logged in'),\n SizedBox(height: 16.0),\n TextField(\n controller: emailController,\n decoration: InputDecoration(labelText: 'Email'),\n ),\n SizedBox(height: 16.0),\n TextField(\n controller: passwordController,\n decoration: InputDecoration(labelText: 'Password'),\n obscureText: true,\n ),\n SizedBox(height: 16.0),\n TextField(\n controller: nameController,\n decoration: InputDecoration(labelText: 'Name'),\n ),\n SizedBox(height: 16.0),\n Row(\n mainAxisAlignment: MainAxisAlignment.start,\n children: [\n ElevatedButton(\n onPressed: () {\n login(emailController.text, passwordController.text);\n },\n child: Text('Login'),\n ),\n SizedBox(width: 16.0),\n ElevatedButton(\n onPressed: () {\n register(emailController.text, passwordController.text,\n nameController.text);\n },\n child: Text('Register'),\n ),\n SizedBox(width: 16.0),\n ElevatedButton(\n onPressed: () {\n logout();\n },\n child: Text('Logout'),\n ),\n ],\n ),\n ],\n ),\n ),\n );\n }\n}\n```\n{% /section %}\n\n{% section #step-6 step=6 title=\"All set\" %}\nRun your project with `flutter run` and select a browser, platform, or emulator to run your project.\n{% /section %}"}, {"path": "docs/quick-starts/go", "title": "Start with Go", "description": "Integrating Appwrite with your Go backend application is a quick and simple process. Get your backend up and running with our step-by-step guide.", "content": "Learn how to set up your first Go project powered by Appwrite.\n{% section #step-1 step=1 title=\"Create project\" %}\nHead to the [Appwrite Console](https://cloud.appwrite.io/console).\n\nIf this is your first time using Appwrite, create an account and create your first project.\n\n{% only_dark %}\n![Create project screen](/images/docs/quick-starts/dark/create-project.avif)\n{% /only_dark %}\n{% only_light %}\n![Create project screen](/images/docs/quick-starts/create-project.avif)\n{% /only_light %}\n\nThen, under **Integrate with your server**, add an **API Key** with the following scopes.\n\n{% only_dark %}\n![Create project screen](/images/docs/quick-starts/dark/integrate-server.avif)\n{% /only_dark %}\n{% only_light %}\n![Create project screen](/images/docs/quick-starts/integrate-server.avif)\n{% /only_light %}\n\n| Category {% width=120 %} | Required scopes | Purpose |\n|-----------|-----------------------|---------|\n| Database | `databases.write` | Allows API key to create, update, and delete [databases](/docs/products/databases/databases). |\n| | `tables.write` | Allows API key to create, update, and delete [tables](/docs/products/databases/tables). |\n| | `columns.write` | Allows API key to create, update, and delete [columns](/docs/products/databases/tables#columns). |\n| | `rows.read` | Allows API key to read [rows](/docs/products/databases/rows). |\n| | `rows.write` | Allows API key to create, update, and delete [rows](/docs/products/databases/rows). |\n\nOther scopes are optional.\n\n{% /section %}\n{% section #step-2 step=2 title=\"Create Go project\" %}\n\nCreate a go application.\n\n```sh\nmkdir my-app\ncd my-app\ngo mod init go-appwrite/main\n```\n\n{% /section %}\n{% section #step-3 step=3 title=\"Install Appwrite\" %}\n\nInstall the Go Appwrite SDK.\n\n```sh\ngo get github.com/appwrite/sdk-for-go\n```\n\n{% /section %}\n\n{% section #step-4 step=4 title=\"Import Appwrite\" %}\n\nFind your project ID in the **Settings** page. Also, click on the **View API Keys** button to find the API key that was created earlier.\n\n{% only_dark %}\n![Project settings screen](/images/docs/quick-starts/dark/project-id.avif)\n{% /only_dark %}\n{% only_light %}\n![Project settings screen](/images/docs/quick-starts/project-id.avif)\n{% /only_light %}\n\nCreate a new file called `app.go`, initialize a function, and initialize the Appwrite Client. Replace `` with your project ID and `` with your API key. Import the Appwrite dependencies for appwrite, client, databases, and models.\n\n```go\npackage main\n\nimport (\n\t\"github.com/appwrite/sdk-for-go/appwrite\"\n\t\"github.com/appwrite/sdk-for-go/client\"\n\t\"github.com/appwrite/sdk-for-go/tablesdb\"\n\t\"github.com/appwrite/sdk-for-go/models\"\n\t\"github.com/appwrite/sdk-for-go/query\"\n)\n\nvar (\n\tappwriteClient client.Client\n\ttodoDatabase *models.Database\n\ttodoTable *models.Table\n\ttablesDB *tablesdb.TablesDB\n)\n\nfunc main() {\n\tappwriteClient = appwrite.NewClient(\n\t\tappwrite.WithProject(\"\"),\n\t\tappwrite.WithKey(\"\"),\n\t)\n}\n```\n\n{% /section %}\n{% section #step-5 step=5 title=\"Initialize database\" %}\n\nOnce 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.\n\n```go\nfunc prepareDatabase() {\n\ttablesDB = appwrite.NewTablesDB(appwriteClient)\n\n\ttodoDatabase, _ = tablesDB.Create(\n\t\tid.Unique(),\n\t\t\"TodosDB\",\n\t)\n\n\ttodoTable, _ = tablesDB.CreateTable(\n\t\ttodoDatabase.Id,\n\t\tid.Unique(),\n\t\t\"Todos\",\n\t)\n\n\ttablesDB.CreateVarcharColumn(\n\t\ttodoDatabase.Id,\n\t\ttodoTable.Id,\n\t\t\"title\",\n\t\t255,\n\t\ttrue,\n\t)\n\n\ttablesDB.CreateTextColumn(\n\t\ttodoDatabase.Id,\n\t\ttodoTable.Id,\n\t\t\"description\",\n\t\tfalse,\n\t)\n\n\ttablesDB.CreateBooleanColumn(\n\t\ttodoDatabase.Id,\n\t\ttodoTable.Id,\n\t\t\"isComplete\",\n\t\ttrue,\n\t)\n}\n```\n\n{% /section %}\n{% section #step-6 step=6 title=\"Add rows\" %}\nCreate a function to add some mock data to your new table.\n\n```go\nfunc seedDatabase() {\n\ttestTodo1 := map[string]interface{}{\n\t\t\"title\": \"Buy apples\",\n\t\t\"description\": \"At least 2KGs\",\n\t\t\"isComplete\": true,\n\t}\n\n\ttestTodo2 := map[string]interface{}{\n\t\t\"title\": \"Wash the apples\",\n\t\t\"isComplete\": true,\n\t}\n\n\ttestTodo3 := map[string]interface{}{\n\t\t\"title\": \"Cut the apples\",\n\t\t\"description\": \"Don't forget to pack them in a box\",\n\t\t\"isComplete\": false,\n\t}\n\n\ttablesDB.CreateRow(\n\t\ttodoDatabase.Id,\n\t\ttodoTable.Id,\n\t\tid.Unique(),\n\t\ttestTodo1,\n\t)\n\n\ttablesDB.CreateRow(\n\t\ttodoDatabase.Id,\n\t\ttodoTable.Id,\n\t\tid.Unique(),\n\t\ttestTodo2,\n\t)\n\n\ttablesDB.CreateRow(\n\t\ttodoDatabase.Id,\n\t\ttodoTable.Id,\n\t\tid.Unique(),\n\t\ttestTodo3,\n\t)\n}\n```\n\n{% /section %}\n{% section #step-7 step=7 title=\"Retrieve rows\" %}\n\nCreate a function to retrieve the mock todo data.\n\n```go\ntype Todo struct {\n\tTitle string `json:\"title\"`\n\tDescription string `json:\"description\"`\n\tIsComplete bool `json:\"isComplete\"`\n}\n\ntype TodoList struct {\n\t*models.DocumentList\n\tDocuments []Todo `json:\"rows\"`\n}\n\nfunc getTodos() {\n\t// Retrieve rows (default limit is 25)\n\ttodoResponse, _ := tablesDB.ListRows(\n\t\ttodoDatabase.Id,\n\t\ttodoTable.Id,\n\t)\n\n\tvar todos TodoList\n\ttodoResponse.Decode(&todos)\n\n\tfmt.Println(\"Todos:\")\n\tfor _, todo := range todos.Documents {\n\t\tfmt.Printf(\"Title: %s\\nDescription: %s\\nIs Todo Complete: %t\\n\\n\", todo.Title, todo.Description, todo.IsComplete)\n\t}\n}\n\nfunc getCompletedTodos() {\n\t// Use queries to filter completed todos with pagination\n\ttodoResponse, _ := tablesDB.ListRows(\n\t\ttodoDatabase.Id,\n\t\ttodoTable.Id,\n\t\ttablesDB.WithListRowsQueries([]string{\n\t\t\tquery.Equal(\"isComplete\", true),\n\t\t\tquery.OrderDesc(\"$createdAt\"),\n\t\t\tquery.Limit(5),\n\t\t}),\n\t)\n\n\tvar todos TodoList\n\ttodoResponse.Decode(&todos)\n\n\tfmt.Println(\"Completed todos (limited to 5):\")\n\tfor _, todo := range todos.Documents {\n\t\tfmt.Printf(\"Title: %s\\nDescription: %s\\nIs Todo Complete: %t\\n\\n\", todo.Title, todo.Description, todo.IsComplete)\n\t}\n}\n\nfunc getIncompleteTodos() {\n\t// Query for incomplete todos\n\ttodoResponse, _ := tablesDB.ListRows(\n\t\ttodoDatabase.Id,\n\t\ttodoTable.Id,\n\t\ttablesDB.WithListRowsQueries([]string{\n\t\t\tquery.Equal(\"isComplete\", false),\n\t\t\tquery.OrderAsc(\"title\"),\n\t\t}),\n\t)\n\n\tvar todos TodoList\n\ttodoResponse.Decode(&todos)\n\n\tfmt.Println(\"Incomplete todos (ordered by title):\")\n\tfor _, todo := range todos.Documents {\n\t\tfmt.Printf(\"Title: %s\\nDescription: %s\\nIs Todo Complete: %t\\n\\n\", todo.Title, todo.Description, todo.IsComplete)\n\t}\n}\n```\n\nMake sure to update `main()` with the functions you created. Your `main()` function should look something like this:\n\n```go\npackage main\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/appwrite/sdk-for-go/appwrite\"\n\t\"github.com/appwrite/sdk-for-go/client\"\n\t\"github.com/appwrite/sdk-for-go/tablesdb\"\n\t\"github.com/appwrite/sdk-for-go/id\"\n\t\"github.com/appwrite/sdk-for-go/models\"\n\t\"github.com/appwrite/sdk-for-go/query\"\n)\n\nvar (\n\tappwriteClient client.Client\n\ttodoDatabase *models.Database\n\ttodoTable *models.Table\n\ttablesDB *tablesdb.TablesDB\n)\n\nfunc main() {\n\tappwriteClient = appwrite.NewClient(\n\t\tappwrite.WithProject(\"\"),\n\t\tappwrite.WithKey(\"\"),\n\t)\n\n\tprepareDatabase()\n\tseedDatabase()\n\tgetTodos()\n\tgetCompletedTodos()\n\tgetIncompleteTodos()\n}\n```\n\n{% /section %}\n\n{% section #step-8 step=8 title=\"All set\" %}\n\nRun your project with `go run .` and view the response in your console.\n\n{% /section %}"}, {"path": "docs/quick-starts/kotlin", "title": "Start with Kotlin", "description": "Learn to get started with server integrations with Appwrite Kotlin SDK.", "content": "Learn how to setup your first Kotlin project powered by Appwrite.\n{% section #step-1 step=1 title=\"Create project\" %}\nHead to the [Appwrite Console](https://cloud.appwrite.io/console).\n\n{% info title=\"Server SDK\" %}\nThis tutorial is for the Kotlin Server SDK, meant for server and backend applications.\nIf you're trying to build a client-side app, like an Android app,\nfollow the [Start with Android guide](https://appwrite.io/docs/quick-starts/android).\n{% /info %}\n\nIf this is your first time using Appwrite, create an account and create your first project.\n\n{% only_dark %}\n![Create project screen](/images/docs/quick-starts/dark/create-project.avif)\n{% /only_dark %}\n{% only_light %}\n![Create project screen](/images/docs/quick-starts/create-project.avif)\n{% /only_light %}\n\nThen, under **Integrate with your server**, add an **API Key** with the following scopes.\n\n{% only_dark %}\n![Server integrations](/images/docs/quick-starts/dark/integrate-server.avif)\n{% /only_dark %}\n{% only_light %}\n![Server integrations](/images/docs/quick-starts/integrate-server.avif)\n{% /only_light %}\n| Category {% width=120 %} | Required scopes | Purpose |\n|-----------|-----------------------|---------|\n| Database | `databases.write` | Allows API key to create, update, and delete [databases](/docs/products/databases/databases). |\n| | `tables.write` | Allows API key to create, update, and delete [tables](/docs/products/databases/tables). |\n| | `columns.write` | Allows API key to create, update, and delete [columns](/docs/products/databases/tables#columns). |\n| | `rows.read` | Allows API key to read [rows](/docs/products/databases/rows). |\n| | `rows.write` | Allows API key to create, update, and delete [rows](/docs/products/databases/rows). |\n\nOther scopes are optional.\n\n{% only_dark %}\n![Project settings screen](/images/docs/quick-starts/dark/project-id.avif)\n{% /only_dark %}\n{% only_light %}\n![Project settings screen](/images/docs/quick-starts/project-id.avif)\n{% /only_light %}\n\n{% /section %}\n{% section #step-2 step=2 title=\"Create Kotlin project\" %}\nCreate a Kotlin application by opening **IntelliJ IDEA** > **New Project** and create a **Kotlin** application.\nThis 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.\n\nFollow the wizard and open your new project. \n\n{% /section %}\n{% section #step-3 step=3 title=\"Install Appwrite\" %}\nOpen your `build.gradle.kts` file and implement the following dependency.\n\n```groovy\ndependencies {\n ... other dependencies\n implementation(\"io.appwrite:sdk-for-kotlin:9.0.0\")\n}\n```\n\n{% /section %}\n{% section #step-4 step=4 title=\"Import Appwrite\" %}\n\nFind your project ID in the **Settings** page. Also, click on the **View API Keys** button to find the API key that was created earlier. \n\n{% only_dark %}\n![Project settings screen](/images/docs/quick-starts/dark/project-id.avif)\n{% /only_dark %}\n{% only_light %}\n![Project settings screen](/images/docs/quick-starts/project-id.avif)\n{% /only_light %}\n\nOpen the file `Main.kt` and initialize the Appwrite Client. Replace `` with your project ID and `` with your API key.\n\n```kotlin\nimport io.appwrite.Client\nimport io.appwrite.ID\nimport io.appwrite.services.TablesDB\nimport io.appwrite.models.Database\nimport io.appwrite.models.Table\nimport kotlinx.coroutines.coroutineScope\n\nval client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\");\n```\n\n{% /section %}\n{% section #step-5 step=5 title=\"Initialize database\" %}\n\nOnce the Appwrite Client is initialized, create a function to configure a todo table.\n\n```kotlin\nval tablesDB = TablesDB(client)\n\nvar todoDatabase: Database? = null\nvar todoTable: Table? = null\n\nsuspend fun prepareDatabase() {\n todoDatabase = tablesDB.create(ID.unique(), \"TodosDB\")\n todoTable = tablesDB.createTable(todoDatabase?.id!!, ID.unique(), \"Todos\")\n\n tablesDB.createVarcharColumn(\n databaseId = todoDatabase?.id!!,\n tableId = todoTable?.id!!,\n key = \"title\",\n size = 255,\n required = true\n )\n\n tablesDB.createTextColumn(\n databaseId = todoDatabase?.id!!,\n tableId = todoTable?.id!!,\n key = \"description\",\n required = false,\n default = \"This is a test description.\"\n )\n\n tablesDB.createBooleanColumn(\n databaseId = todoDatabase?.id!!,\n tableId = todoTable?.id!!,\n key = \"isComplete\",\n required = true\n )\n}\n```\n\n{% /section %}\n{% section #step-6 step=6 title=\"Add rows\" %}\nCreate a function to add some mock data into your new table.\n```kotlin\nsuspend fun seedDatabase() {\n val testTodo1 = mapOf(\n \"title\" to \"Buy apples\",\n \"description\" to \"At least 2KGs\",\n \"isComplete\" to true\n )\n\n val testTodo2 = mapOf(\n \"title\" to \"Wash the apples\",\n \"isComplete\" to true\n )\n\n val testTodo3 = mapOf(\n \"title\" to \"Cut the apples\",\n \"description\" to \"Don't forget to pack them in a box\",\n \"isComplete\" to false\n )\n\n tablesDB.createRow(\n databaseId = todoDatabase?.id!!,\n tableId = todoTable?.id!!,\n rowId = ID.unique(),\n data = testTodo1\n )\n\n tablesDB.createRow(\n databaseId = todoDatabase?.id!!,\n tableId = todoTable?.id!!,\n rowId = ID.unique(),\n data = testTodo2\n )\n\n tablesDB.createRow(\n databaseId = todoDatabase?.id!!,\n tableId = todoTable?.id!!,\n rowId = ID.unique(),\n data = testTodo3\n )\n}\n```\n\n{% /section %}\n{% section #step-7 step=7 title=\"Retrieve rows\" %}\n\nCreate a function to retrieve the mock todo data.\n\n```kotlin\nsuspend fun getTodos() {\n val todos = tablesDB.listRows(todoDatabase?.id!!, todoTable?.id!!)\n for (todo in todos.rows) {\n println(\n \"\"\"\n Title: ${todo.data[\"title\"]}\n Description: ${todo.data[\"description\"]}\n Is Todo Complete: ${todo.data[\"isComplete\"]}\n \"\"\".trimIndent()\n )\n }\n}\n\nsuspend fun main() = coroutineScope {\n prepareDatabase()\n seedDatabase()\n getTodos()\n}\n```\n\n{% /section %}\n\n{% section #step-8 step=8 title=\"All set\" %}\n\nRun your project with IntelliJ and view the response in your console.\n\n{% /section %}"}, {"path": "docs/quick-starts/nextjs", "title": "Start with Next.js", "description": "Learn how to use Appwrite to add authentication, user management, file storage, and more to your Next.js apps.", "content": "Learn how to setup your first Next.js project powered by Appwrite.\n{% section #step-1 step=1 title=\"Create project\" %}\nHead to the [Appwrite Console](https://cloud.appwrite.io/console).\n\n{% only_dark %}\n![Create project screen](/images/docs/quick-starts/dark/create-project.avif)\n{% /only_dark %}\n{% only_light %}\n![Create project screen](/images/docs/quick-starts/create-project.avif)\n{% /only_light %}\n\nIf this is your first time using Appwrite, create an account and create your first project.\n\nThen, under **Add a platform**, add a **Web app**. The **Hostname** should be `localhost`.\n\n{% partial file=\"note-on-cors.md\" /%}\n\n{% only_dark %}\n![Add a platform](/images/docs/quick-starts/dark/add-platform.avif)\n{% /only_dark %}\n{% only_light %}\n![Add a platform](/images/docs/quick-starts/add-platform.avif)\n{% /only_light %}\n\nYou can skip optional steps.\n\n{% /section %}\n{% section #step-2 step=2 title=\"Create Next.js project\" %}\nCreate a Next.js project by running the following command:\n\n```sh\nnpx create-next-app@latest && cd my-app\n```\n\nWhen prompted, configure your project with these recommended settings:\n- **Would you like to use TypeScript?** → No\n- **Would you like to use ESLint?** → Yes\n- **Would you like to use Tailwind CSS?** → No (unless you plan to use it)\n- **Would you like to use `src/` directory?** → Yes/No (either works for this tutorial)\n- **Would you like to use App Router?** → Yes\n- **Would you like to customize the default import alias?** → No\n\nThese settings will create a minimal Next.js setup that's perfect for getting started with Appwrite.\n{% /section %}\n{% section #step-3 step=3 title=\"Install Appwrite SDK\" %}\n\nInstall the JavaScript Appwrite SDK.\n\n```sh\nnpm install appwrite\n```\n{% /section %}\n{% section #step-4 step=4 title=\"Define Appwrite service\" %}\nFind your project's ID in the **Settings** page.\n\n{% only_dark %}\n![Project settings screen](/images/docs/quick-starts/dark/project-id.avif)\n{% /only_dark %}\n{% only_light %}\n![Project settings screen](/images/docs/quick-starts/project-id.avif)\n{% /only_light %}\n\nCreate a new file `app/appwrite.js` and add the following code to it, replace `` with your project ID.\n\n```client-web\nimport { Client, Account } from 'appwrite';\n\nexport const client = new Client();\n\nclient\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject(''); // Replace with your project ID\n\nexport const account = new Account(client);\nexport { ID } from 'appwrite';\n```\n{% /section %}\n{% section #step-5 step=5 title=\"Create a login page\" %}\nCreate or update `app/page.js` file and add the following code to it.\n\n```js\n\"use client\";\nimport { useState } from \"react\";\nimport { account, ID } from \"./appwrite\";\n\nconst LoginPage = () => {\n const [loggedInUser, setLoggedInUser] = useState(null);\n const [email, setEmail] = useState(\"\");\n const [password, setPassword] = useState(\"\");\n const [name, setName] = useState(\"\");\n\n const login = async (email, password) => {\n const session = await account.createEmailPasswordSession({\n email,\n password\n });\n setLoggedInUser(await account.get());\n };\n\n const register = async () => {\n await account.create({\n userId: ID.unique(),\n email,\n password,\n name\n });\n login(email, password);\n };\n\n const logout = async () => {\n await account.deleteSession({ sessionId: 'current' });\n setLoggedInUser(null);\n };\n\n if (loggedInUser) {\n return (\n
\n

Logged in as {loggedInUser.name}

\n \n
\n );\n }\n\n return (\n
\n

Not logged in

\n
\n setEmail(e.target.value)}\n />\n setPassword(e.target.value)}\n />\n setName(e.target.value)}\n />\n \n \n \n
\n );\n};\n\nexport default LoginPage;\n```\n{% /section %}\n\n{% section #step-6 step=6 title=\"All set\" %}\nRun your project with `npm run dev` and open [Localhost on Port 3000](http://localhost:3000) in your browser.\n\nDon't forget to add some CSS to suit your style.\n{% /section %}"}, {"path": "docs/quick-starts/node", "title": "Start with Node.js", "description": "Dive into our step-by-step guide on integrating Appwrite with your Node.js server backend application. Get your backend up and running quickly with this tutorial.", "content": "Learn how to setup your first Node.js project powered by Appwrite.\n{% section #step-1 step=1 title=\"Create project\" %}\nHead to the [Appwrite Console](https://cloud.appwrite.io/console).\n\nIf this is your first time using Appwrite, create an account and create your first project.\n\n{% only_dark %}\n![Create project screen](/images/docs/quick-starts/dark/create-project.avif)\n{% /only_dark %}\n{% only_light %}\n![Create project screen](/images/docs/quick-starts/create-project.avif)\n{% /only_light %}\n\nThen, under **Integrate with your server**, add an **API Key** with the following scopes.\n\n{% only_dark %}\n![Create project screen](/images/docs/quick-starts/dark/integrate-server.avif)\n{% /only_dark %}\n{% only_light %}\n![Create project screen](/images/docs/quick-starts/integrate-server.avif)\n{% /only_light %}\n\n| Category {% width=120 %} | Required scopes | Purpose |\n|-----------|-----------------------|---------|\n| Database | `databases.write` | Allows API key to create, update, and delete [databases](/docs/products/databases/databases). |\n| | `tables.write` | Allows API key to create, update, and delete [tables](/docs/products/databases/tables). |\n| | `columns.write` | Allows API key to create, update, and delete [columns](/docs/products/databases/tables#columns). |\n| | `rows.read` | Allows API key to read [rows](/docs/products/databases/rows). |\n| | `rows.write` | Allows API key to create, update, and delete [rows](/docs/products/databases/rows). |\n\nOther scopes are optional.\n\n{% /section %}\n{% section #step-2 step=2 title=\"Create Node.js project\" %}\nCreate a Node.js CLI application.\n\n```sh\nmkdir my-app\ncd my-app\nnpm init\n```\n\n{% /section %}\n{% section #step-3 step=3 title=\"Install Appwrite\" %}\n\nInstall the Node.js Appwrite SDK.\n\n```sh\nnpm install node-appwrite\n```\n{% /section %}\n{% section #step-4 step=4 title=\"Import Appwrite\" %}\n\nFind your project ID in the **Settings** page. Also, click on the **View API Keys** button to find the API key that was created earlier.\n\n{% only_dark %}\n![Project settings screen](/images/docs/quick-starts/dark/project-id.avif)\n{% /only_dark %}\n{% only_light %}\n![Project settings screen](/images/docs/quick-starts/project-id.avif)\n{% /only_light %}\n\nCreate a new file `app.js` and initialize the Appwrite Client. Replace `` with your project ID and `` with your API key.\n\n```js\nconst sdk = require(\"node-appwrite\");\n\nconst client = new sdk.Client();\n\nclient\n .setEndpoint(\"https://.cloud.appwrite.io/v1\")\n .setProject(\"\")\n .setKey(\"\");\n```\n\n{% /section %}\n{% section #step-5 step=5 title=\"Initialize database\" %}\n\nOnce the Appwrite Client is initialized, create a function to configure a todo table.\n\n```js\nconst tablesDB = new sdk.TablesDB(client);\n\nvar todoDatabase;\nvar todoTable;\n\nasync function prepareDatabase() {\n todoDatabase = await tablesDB.create({\n databaseId: sdk.ID.unique(),\n name: 'TodosDB'\n });\n\n todoTable = await tablesDB.createTable({\n databaseId: todoDatabase.$id,\n tableId: sdk.ID.unique(),\n name: 'Todos'\n });\n\n await tablesDB.createVarcharColumn({\n databaseId: todoDatabase.$id,\n tableId: todoTable.$id,\n key: 'title',\n size: 255,\n required: true\n });\n\n await tablesDB.createTextColumn({\n databaseId: todoDatabase.$id,\n tableId: todoTable.$id,\n key: 'description',\n required: false,\n xdefault: 'This is a test description'\n });\n\n await tablesDB.createBooleanColumn({\n databaseId: todoDatabase.$id,\n tableId: todoTable.$id,\n key: 'isComplete',\n required: true\n });\n}\n```\n\n{% /section %}\n{% section #step-6 step=6 title=\"Add rows\" %}\nCreate a function to add some mock data into your new table.\n\n```js\nasync function seedDatabase() {\n var testTodo1 = {\n title: 'Buy apples',\n description: 'At least 2KGs',\n isComplete: true\n };\n\n var testTodo2 = {\n title: 'Wash the apples',\n isComplete: true\n };\n\n var testTodo3 = {\n title: 'Cut the apples',\n description: 'Don\\'t forget to pack them in a box',\n isComplete: false\n };\n\n await tablesDB.createRow({\n databaseId: todoDatabase.$id,\n tableId: todoTable.$id,\n rowId: sdk.ID.unique(),\n data: testTodo1\n });\n await tablesDB.createRow({\n databaseId: todoDatabase.$id,\n tableId: todoTable.$id,\n rowId: sdk.ID.unique(),\n data: testTodo2\n });\n await tablesDB.createRow({\n databaseId: todoDatabase.$id,\n tableId: todoTable.$id,\n rowId: sdk.ID.unique(),\n data: testTodo3\n });\n}\n```\n\n{% /section %}\n{% section #step-7 step=7 title=\"Retrieve rows\" %}\n\nCreate a function to retrieve the mock todo data and a function to execute the requests in order.\nRun the functions to by calling `runAllTasks();`.\n\n```js\nconst { Query } = require('node-appwrite');\n\nasync function getTodos() {\n // Retrieve rows (default limit is 25)\n var todos = await tablesDB.listRows({\n databaseId: todoDatabase.$id,\n tableId: todoTable.$id\n });\n\n console.log(\"Todos:\");\n todos.rows.forEach(todo => {\n console.log(`Title: ${todo.title}\\nDescription: ${todo.description}\\nIs Todo Complete: ${todo.isComplete}\\n\\n`);\n });\n}\n\nasync function getCompletedTodos() {\n // Use queries to filter completed todos with pagination\n var todos = await tablesDB.listRows({\n databaseId: todoDatabase.$id,\n tableId: todoTable.$id,\n queries: [\n Query.equal(\"isComplete\", true),\n Query.orderDesc(\"$createdAt\"),\n Query.limit(5)\n ]\n });\n\n console.log(\"Completed todos (limited to 5):\");\n todos.rows.forEach(todo => {\n console.log(`Title: ${todo.title}\\nDescription: ${todo.description}\\nIs Todo Complete: ${todo.isComplete}\\n\\n`);\n });\n}\n\nasync function getIncompleteTodos() {\n // Query for incomplete todos\n var todos = await tablesDB.listRows({\n databaseId: todoDatabase.$id,\n tableId: todoTable.$id,\n queries: [\n Query.equal(\"isComplete\", false),\n Query.orderAsc(\"title\")\n ]\n });\n\n console.log(\"Incomplete todos (ordered by title):\");\n todos.rows.forEach(todo => {\n console.log(`Title: ${todo.title}\\nDescription: ${todo.description}\\nIs Todo Complete: ${todo.isComplete}\\n\\n`);\n });\n}\n\nasync function runAllTasks() {\n await prepareDatabase();\n await seedDatabase();\n await getTodos();\n await getCompletedTodos();\n await getIncompleteTodos();\n}\nrunAllTasks();\n```\n\n{% /section %}\n{% section #step-8 step=8 title=\"Type safety with TypeScript\" %}\n\nFor better type safety in TypeScript Node.js projects, define interfaces and use generics:\n\n```typescript\ninterface Todo {\n title: string;\n description: string;\n isComplete: boolean;\n}\n\nimport { Client, TablesDB } from 'node-appwrite';\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nconst tablesDB = new TablesDB(client);\n\n// Type-safe database operations\nasync function getTodos() {\n const todos = await tablesDB.listRows({\n databaseId: '',\n tableId: ''\n });\n\n todos.rows.forEach(todo => {\n console.log(`Title: ${todo.title} - Complete: ${todo.isComplete}`);\n });\n}\n```\n\n{% info title=\"Generate types automatically\" %}\nUse the [Appwrite CLI](/docs/products/databases/type-generation) to generate TypeScript interfaces automatically: `appwrite types ./types`\n{% /info %}\n\n{% /section %}\n\n{% section #step-9 step=9 title=\"All set\" %}\n\nRun your project with `node app.js` and view the response in your console.\n\n{% /section %}"}, {"path": "docs/quick-starts/nuxt", "title": "Start with Nuxt", "description": "Build Nuxt.js apps with Appwrite and learn how to use our powerful backend to add authentication, user management, file storage, and more.", "content": "Learn how to setup your first Nuxt project powered by Appwrite.\n{% section #step-1 step=1 title=\"Create project\" %}\nHead to the [Appwrite Console](https://cloud.appwrite.io/console).\n\n{% only_dark %}\n![Create project screen](/images/docs/quick-starts/dark/create-project.avif)\n{% /only_dark %}\n{% only_light %}\n![Create project screen](/images/docs/quick-starts/create-project.avif)\n{% /only_light %}\n\nIf this is your first time using Appwrite, create an account and create your first project.\n\nThen, under **Add a platform**, add a **Web app**. The **Hostname** should be `localhost`.\n\n{% partial file=\"note-on-cors.md\" /%}\n\n{% only_dark %}\n![Add a platform](/images/docs/quick-starts/dark/add-platform.avif)\n{% /only_dark %}\n{% only_light %}\n![Add a platform](/images/docs/quick-starts/add-platform.avif)\n{% /only_light %}\n\nYou can skip optional steps.\n\n{% /section %}\n{% section #step-2 step=2 title=\"Create Nuxt project\" %}\nCreate a Nuxt project.\n\n```sh\nnpx nuxi@latest init my-app && cd my app\n\n```\n{% /section %}\n{% section #step-3 step=3 title=\"Install Appwrite\" %}\n\nInstall the JavaScript Appwrite SDK.\n\n```sh\nnpm install appwrite\n```\n{% /section %}\n{% section #step-4 step=4 title=\"Import Appwrite\" %}\nFind your project's ID in the **Settings** page.\n\n{% only_dark %}\n![Project settings screen](/images/docs/quick-starts/dark/project-id.avif)\n{% /only_dark %}\n{% only_light %}\n![Project settings screen](/images/docs/quick-starts/project-id.avif)\n{% /only_light %}\n\nCreate a new file `utils/appwrite.js` and add the following code to it, replace `` with your project ID.\n\n```client-web\nimport { Client, Account} from 'appwrite';\n\nexport const client = new Client();\n\nclient\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject(''); // Replace with your project ID\n\nexport const account = new Account(client);\nexport { ID } from 'appwrite';\n```\n{% /section %}\n{% section #step-5 step=5 title=\"Create a login page\" %}\nAdd the following code to `app.vue`.\n\n```html\n\n\n\n```\n{% /section %}\n\n{% section #step-6 step=6 title=\"All set\" %}\nRun your project with `npm run dev -- --open --port 3000` and open [Localhost on Port 3000](http://localhost:3000) in your browser.\n{% /section %}"}, {"path": "docs/quick-starts/php", "title": "Start with PHP", "description": "Dive into our step-by-step guide on integrating Appwrite with your PHP server backend application. Get your backend up and running quickly with this tutorial.", "content": "Learn how to setup your first PHP project powered by Appwrite.\n{% section #step-1 step=1 title=\"Create project\" %}\nHead to the [Appwrite Console](https://cloud.appwrite.io/console).\n\nIf this is your first time using Appwrite, create an account and create your first project.\n\n{% only_dark %}\n![Create project screen](/images/docs/quick-starts/dark/create-project.avif)\n{% /only_dark %}\n{% only_light %}\n![Create project screen](/images/docs/quick-starts/create-project.avif)\n{% /only_light %}\n\nThen, under **Integrate with your server**, add an **API Key** with the following scopes.\n\n{% only_dark %}\n![Create project screen](/images/docs/quick-starts/dark/integrate-server.avif)\n{% /only_dark %}\n{% only_light %}\n![Create project screen](/images/docs/quick-starts/integrate-server.avif)\n{% /only_light %}\n\n| Category {% width=120 %} | Required scopes | Purpose |\n|-----------|-----------------------|---------|\n| Database | `databases.write` | Allows API key to create, update, and delete [databases](/docs/products/databases/databases). |\n| | `tables.write` | Allows API key to create, update, and delete [tables](/docs/products/databases/tables). |\n| | `columns.write` | Allows API key to create, update, and delete [columns](/docs/products/databases/tables#columns). |\n| | `rows.read` | Allows API key to read [rows](/docs/products/databases/rows). |\n| | `rows.write` | Allows API key to create, update, and delete [rows](/docs/products/databases/rows). |\n\nOther scopes are optional.\n\n{% /section %}\n{% section #step-2 step=2 title=\"Create PHP project\" %}\nCreate a PHP CLI application.\n\n```sh\nmkdir my-app\ncd my-app\ncomposer init\n```\n\n{% /section %}\n{% section #step-3 step=3 title=\"Install Appwrite\" %}\n\nInstall the PHP Appwrite SDK.\n\n```sh\ncomposer require appwrite/appwrite:15.0.0\n```\n{% /section %}\n{% section #step-4 step=4 title=\"Import Appwrite\" %}\n\nFind your project ID in the **Settings** page. Also, click on the **View API Keys** button to find the API key that was created earlier.\n\n{% only_dark %}\n![Project settings screen](/images/docs/quick-starts/dark/project-id.avif)\n{% /only_dark %}\n{% only_light %}\n![Project settings screen](/images/docs/quick-starts/project-id.avif)\n{% /only_light %}\n\nCreate a new file `index.php` and initialize the Appwrite Client. Replace `` with your project ID and `` with your API key.\n\n```php\nsetEndpoint('https://.cloud.appwrite.io/v1')\n ->setProject('')\n ->setKey('');\n```\n\n{% /section %}\n{% section #step-5 step=5 title=\"Initialize database\" %}\n\nOnce the Appwrite Client is initialized, create a function to configure a todo table.\n\n```php\n$tablesDB = new TablesDB($client);\n\nfunction prepareDatabase($tablesDB) {\n $todoDatabase = $tablesDB->create(\n databaseId: ID::unique(), \n name: 'TodosDB'\n );\n\n $todoTable = $tablesDB->createTable(\n databaseId: $todoDatabase['$id'], \n tableId: ID::unique(),\n name: 'Todos'\n );\n\n $tablesDB->createVarcharColumn(\n databaseId: $todoDatabase['$id'],\n tableId: $todoTable['$id'],\n key: 'title',\n size: 255,\n required: true\n );\n\n $tablesDB->createTextColumn(\n databaseId: $todoDatabase['$id'],\n tableId: $todoTable['$id'],\n key: 'description',\n required: false,\n );\n\n $tablesDB->createBooleanColumn(\n databaseId: $todoDatabase['$id'], \n tableId: $todoTable['$id'], \n key: 'isComplete', \n required: true\n );\n\n return [$todoDatabase, $todoTable];\n}\n```\n\n{% /section %}\n{% section #step-6 step=6 title=\"Add rows\" %}\nCreate a function to add some mock data into your new table.\n\n```php\nfunction seedDatabase($tablesDB, $todoDatabase, $todoTable) {\n $testTodo1 = [\n 'title' => 'Buy apples',\n 'description' => 'At least 2KGs',\n 'isComplete' => true\n ];\n\n $testTodo2 = [\n 'title' => 'Wash the apples',\n 'isComplete' => true\n ];\n\n $testTodo3 = [\n 'title' => 'Cut the apples',\n 'description' => 'Don\\'t forget to pack them in a box',\n 'isComplete' => false\n ];\n\n $tablesDB->createRow(\n $todoDatabase['$id'], \n $todoTable['$id'], \n ID::unique(), \n $testTodo1\n );\n \n $tablesDB->createRow(\n $todoDatabase['$id'], \n $todoTable['$id'], \n ID::unique(), \n $testTodo2\n );\n \n $tablesDB->createRow(\n $todoDatabase['$id'], \n $todoTable['$id'], \n ID::unique(), \n $testTodo3\n );\n}\n```\n\n{% /section %}\n{% section #step-7 step=7 title=\"Retrieve rows\" %}\n\nCreate a function to retrieve the mock todo data and a function to execute the requests in order.\nRun the functions to by calling `runAllTasks();`.\n\n```php\nuse Appwrite\\Query;\n\nfunction getTodos($tablesDB, $todoDatabase, $todoTable) {\n // Retrieve rows (default limit is 25)\n $todos = $tablesDB->listRows(\n $todoDatabase['$id'], \n $todoTable['$id']\n );\n\n echo \"Todos:\\n\";\n foreach ($todos['rows'] as $todo) {\n echo \"Title: {$todo['title']}\\n\" .\n \"Description: {$todo['description']}\\n\" .\n \"Is Todo Complete: {$todo['isComplete']}\\n\\n\";\n }\n}\n\nfunction getCompletedTodos($tablesDB, $todoDatabase, $todoTable) {\n // Use queries to filter completed todos with pagination\n $todos = $tablesDB->listRows(\n $todoDatabase['$id'],\n $todoTable['$id'],\n [\n Query::equal('isComplete', true),\n Query::orderDesc('$createdAt'),\n Query::limit(5)\n ]\n );\n\n echo \"Completed todos (limited to 5):\\n\";\n foreach ($todos['rows'] as $todo) {\n echo \"Title: {$todo['title']}\\n\" .\n \"Description: {$todo['description']}\\n\" .\n \"Is Todo Complete: {$todo['isComplete']}\\n\\n\";\n }\n}\n\nfunction getIncompleteTodos($tablesDB, $todoDatabase, $todoTable) {\n // Query for incomplete todos\n $todos = $tablesDB->listRows(\n $todoDatabase['$id'],\n $todoTable['$id'],\n [\n Query::equal('isComplete', false),\n Query::orderAsc('title')\n ]\n );\n\n echo \"Incomplete todos (ordered by title):\\n\";\n foreach ($todos['rows'] as $todo) {\n echo \"Title: {$todo['title']}\\n\" .\n \"Description: {$todo['description']}\\n\" .\n \"Is Todo Complete: {$todo['isComplete']}\\n\\n\";\n }\n}\n\nfunction runAllTasks($tablesDB) {\n [$todoDatabase, $todoTable] = prepareDatabase($tablesDB);\n seedDatabase($tablesDB, $todoDatabase, $todoTable);\n getTodos($tablesDB, $todoDatabase, $todoTable);\n getCompletedTodos($tablesDB, $todoDatabase, $todoTable);\n getIncompleteTodos($tablesDB, $todoDatabase, $todoTable);\n}\n\nrunAllTasks($tablesDB);\n```\n\n{% /section %}\n\n{% section #step-8 step=8 title=\"All set\" %}\n\nRun your project with `php src/index.php` and view the response in your console.\n\n{% /section %}"}, {"path": "docs/quick-starts/python", "title": "Start with Python", "description": "Learn to get started with server integrations with Appwrite Python SDK.", "content": "Learn how to setup your first Python project powered by Appwrite.\n{% section #step-1 step=1 title=\"Create project\" %}\nHead to the [Appwrite Console](https://cloud.appwrite.io/console).\n\nIf this is your first time using Appwrite, create an account and create your first project.\n\n{% only_dark %}\n![Create project screen](/images/docs/quick-starts/dark/create-project.avif)\n{% /only_dark %}\n{% only_light %}\n![Create project screen](/images/docs/quick-starts/create-project.avif)\n{% /only_light %}\n\nThen, under **Integrate with your server**, add an **API Key** with the following scopes.\n\n{% only_dark %}\n![Server integrations](/images/docs/quick-starts/dark/integrate-server.avif)\n{% /only_dark %}\n{% only_light %}\n![Server integrations](/images/docs/quick-starts/integrate-server.avif)\n{% /only_light %}\n\n| Category {% width=120 %} | Required scopes | Purpose |\n|-----------|-----------------------|---------|\n| Database | `databases.write` | Allows API key to create, update, and delete [databases](/docs/products/databases/databases). |\n| | `tables.write` | Allows API key to create, update, and delete [tables](/docs/products/databases/tables). |\n| | `columns.write` | Allows API key to create, update, and delete [columns](/docs/products/databases/tables#columns). |\n| | `rows.read` | Allows API key to read [rows](/docs/products/databases/rows). |\n| | `rows.write` | Allows API key to create, update, and delete [rows](/docs/products/databases/rows). |\n\nOther scopes are optional.\n\n{% only_dark %}\n![Project settings screen](/images/docs/quick-starts/dark/project-id.avif)\n{% /only_dark %}\n{% only_light %}\n![Project settings screen](/images/docs/quick-starts/project-id.avif)\n{% /only_light %}\n\n{% /section %}\n{% section #step-2 step=2 title=\"Create Python project\" %}\nCreate a directory for the project.\n\n```sh\nmkdir my_app\ncd my_app\n```\n\nAfter that, create a virtual environment in this directory and activate it.\n\n```sh\n# Create a venv\npython -m venv .venv\n\n# Active the venv in Unix shell\nsource .venv/bin/activate\n\n# Or in Powershell\n.venv/Scripts/Activate.ps1\n```\n\nFinally, create a file `my_app.py`.\n\n{% /section %}\n{% section #step-3 step=3 title=\"Install Appwrite\" %}\n\nInstall the Python Appwrite SDK.\n\n```sh\npip install appwrite\n```\n\nOr with `uv`:\n\n```sh\nuv add appwrite\n```\n\n{% /section %}\n{% section #step-4 step=4 title=\"Import Appwrite\" %}\n\nFind your project ID in the **Settings** page. Also, click on the **View API Keys** button to find the API key that was created earlier.\n\n{% only_dark %}\n![Project settings screen](/images/docs/quick-starts/dark/project-id.avif)\n{% /only_dark %}\n{% only_light %}\n![Project settings screen](/images/docs/quick-starts/project-id.avif)\n{% /only_light %}\n\nOpen `my_app.py` and initialize the Appwrite Client. Replace `` with your project ID and `` with your API key.\n\n```py\nfrom appwrite.client import Client\nfrom appwrite.services.tables_db import TablesDB\nfrom appwrite.id import ID\n\nclient = Client()\nclient.set_endpoint('https://.cloud.appwrite.io/v1')\nclient.set_project('')\nclient.set_key('')\n```\n\n{% /section %}\n{% section #step-5 step=5 title=\"Initialize database\" %}\n\nOnce the Appwrite Client is initialized, define a Pydantic model for type-safe data access and create a function to configure a todo table.\n\n```py\nfrom pydantic import BaseModel\nfrom typing import Optional\n\n# Define a Pydantic model matching the table schema\nclass Todo(BaseModel):\n title: str\n description: Optional[str] = None\n isComplete: bool\n\ntablesDB = TablesDB(client)\n\ntodoDatabase = None\ntodoTable = None\n\ndef prepare_database():\n global todoDatabase\n global todoTable\n\n todoDatabase = tablesDB.create(\n database_id=ID.unique(),\n name='TodosDB'\n )\n\n todoTable = tablesDB.create_table(\n database_id=todoDatabase.id,\n table_id=ID.unique(),\n name='Todos'\n )\n\n tablesDB.create_varchar_column(\n database_id=todoDatabase.id,\n table_id=todoTable.id,\n key='title',\n size=255,\n required=True\n )\n\n tablesDB.create_text_column(\n database_id=todoDatabase.id,\n table_id=todoTable.id,\n key='description',\n required=False,\n default='This is a test description.'\n )\n\n tablesDB.create_boolean_column(\n database_id=todoDatabase.id,\n table_id=todoTable.id,\n key='isComplete',\n required=True\n )\n```\n\n{% /section %}\n{% section #step-6 step=6 title=\"Add rows\" %}\nCreate a function to add some mock data into your new table.\n\n```py\ndef seed_database():\n testTodo1 = Todo(\n title=\"Buy apples\",\n description=\"At least 2KGs\",\n isComplete=True\n )\n\n testTodo2 = Todo(\n title=\"Wash the apples\",\n isComplete=True\n )\n\n testTodo3 = Todo(\n title=\"Cut the apples\",\n description=\"Don\\'t forget to pack them in a box\",\n isComplete=False\n )\n\n tablesDB.create_row(\n database_id=todoDatabase.id,\n table_id=todoTable.id,\n row_id=ID.unique(),\n data=testTodo1.model_dump()\n )\n\n tablesDB.create_row(\n database_id=todoDatabase.id,\n table_id=todoTable.id,\n row_id=ID.unique(),\n data=testTodo2.model_dump()\n )\n\n tablesDB.create_row(\n database_id=todoDatabase.id,\n table_id=todoTable.id,\n row_id=ID.unique(),\n data=testTodo3.model_dump()\n )\n```\n\n{% /section %}\n{% section #step-7 step=7 title=\"Retrieve rows\" %}\n\nCreate a function to retrieve the mock todo data,\nthen execute the functions in `_main_`.\n\n```py\nfrom appwrite.query import Query\n\ndef get_todos():\n # Retrieve rows with type-safe access (default limit is 25)\n todos = tablesDB.list_rows(\n database_id=todoDatabase.id,\n table_id=todoTable.id,\n model_type=Todo\n )\n print(\"Todos:\")\n for todo in todos.rows:\n print(f\"Title: {todo.data.title}\\nDescription: {todo.data.description}\\nIs Todo Complete: {todo.data.isComplete}\\n\\n\")\n\ndef get_completed_todos():\n # Use queries to filter completed todos with pagination\n todos = tablesDB.list_rows(\n database_id=todoDatabase.id,\n table_id=todoTable.id,\n model_type=Todo,\n queries=[\n Query.equal(\"isComplete\", True),\n Query.order_desc(\"$createdAt\"),\n Query.limit(5)\n ]\n )\n print(\"Completed todos (limited to 5):\")\n for todo in todos.rows:\n print(f\"Title: {todo.data.title}\\nDescription: {todo.data.description}\\nIs Todo Complete: {todo.data.isComplete}\\n\\n\")\n\ndef get_incomplete_todos():\n # Query for incomplete todos\n todos = tablesDB.list_rows(\n database_id=todoDatabase.id,\n table_id=todoTable.id,\n model_type=Todo,\n queries=[\n Query.equal(\"isComplete\", False),\n Query.order_asc(\"title\")\n ]\n )\n print(\"Incomplete todos (ordered by title):\")\n for todo in todos.rows:\n print(f\"Title: {todo.data.title}\\nDescription: {todo.data.description}\\nIs Todo Complete: {todo.data.isComplete}\\n\\n\")\n\nif __name__ == \"__main__\":\n prepare_database()\n seed_database()\n get_todos()\n get_completed_todos()\n get_incomplete_todos()\n```\n\n{% /section %}\n{% section #step-8 step=8 title=\"All set\" %}\n\nRun your project with `python my_app.py` and view the response in your console.\n\n{% /section %}"}, {"path": "docs/quick-starts/qwik", "title": "Start with Qwik", "description": "Learn how to use Appwrite to add authentication, user management, file storage, and more to your Qwik apps.", "content": "Improve the docs, add this guide.\n\nWe still don't have this guide in place, but we do have some great news. \nThe Appwrite docs, just like Appwrite, is completely open sourced.\nThis means, anyone can help improve them and add new guides and tutorials. \n\nIf you see this page, **we're actively looking for contributions to this page**.\nFollow 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."}, {"path": "docs/quick-starts/react", "title": "Start with React", "description": "Build React apps with Appwrite and learn how to use our powerful backend to add authentication, user management, file storage, and more.", "content": "Learn how to setup your first React project powered by Appwrite.\n{% section #step-1 step=1 title=\"Create project\" %}\nHead to the [Appwrite Console](https://cloud.appwrite.io/console).\n\n{% only_dark %}\n![Create project screen](/images/docs/quick-starts/dark/create-project.avif)\n{% /only_dark %}\n{% only_light %}\n![Create project screen](/images/docs/quick-starts/create-project.avif)\n{% /only_light %}\n\nIf this is your first time using Appwrite, create an account and create your first project.\n\nThen, under **Add a platform**, add a **Web app**. The **Hostname** should be `localhost`.\n\n{% partial file=\"note-on-cors.md\" /%}\n\n{% only_dark %}\n![Add a platform](/images/docs/quick-starts/dark/add-platform.avif)\n{% /only_dark %}\n{% only_light %}\n![Add a platform](/images/docs/quick-starts/add-platform.avif)\n{% /only_light %}\n\nYou can skip optional steps.\n\n{% /section %}\n{% section #step-2 step=2 title=\"Create React project\" %}\nCreate a Vite project.\n\n```sh\nnpm create vite@latest my-app -- --template react && cd my-app\n```\n{% /section %}\n{% section #step-3 step=3 title=\"Install Appwrite\" %}\n\nInstall the JavaScript Appwrite SDK.\n\n```sh\nnpm install appwrite\n```\n{% /section %}\n{% section #step-4 step=4 title=\"Import Appwrite\" %}\nFind your project's ID in the **Settings** page.\n\n{% only_dark %}\n![Project settings screen](/images/docs/quick-starts/dark/project-id.avif)\n{% /only_dark %}\n{% only_light %}\n![Project settings screen](/images/docs/quick-starts/project-id.avif)\n{% /only_light %}\nCreate a new file `src/lib/appwrite.js` and add the following code to it, replace `` with your project ID.\n\n```client-web\nimport { Client, Account} from 'appwrite';\n\nexport const client = new Client();\n\nclient\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject(''); // Replace with your project ID\n\nexport const account = new Account(client);\nexport { ID } from 'appwrite';\n```\n{% /section %}\n{% section #step-5 step=5 title=\"Create a login page\" %}\nAdd the following code to `src/App.jsx`.\n\n```js\nimport React, { useState } from 'react';\nimport { account, ID } from './lib/appwrite';\n\nconst App = () => {\n const [loggedInUser, setLoggedInUser] = useState(null);\n const [email, setEmail] = useState('');\n const [password, setPassword] = useState('');\n const [name, setName] = useState('');\n\n async function login(email, password) {\n await account.createEmailPasswordSession({\n email,\n password\n });\n setLoggedInUser(await account.get());\n }\n\n return (\n
\n

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

\n\n
\n setEmail(e.target.value)} />\n setPassword(e.target.value)} />\n setName(e.target.value)} />\n\n \n\n {\n await account.create({\n userId: ID.unique(),\n email,\n password,\n name\n });\n login(email, password);\n }}\n >\n Register\n \n\n {\n await account.deleteSession({\n sessionId: 'current'\n });\n setLoggedInUser(null);\n }}\n >\n Logout\n \n \n
\n );\n};\n\nexport default App;\n```\n{% /section %}\n\n{% section #step-6 step=6 title=\"All set\" %}\nRun your project with `npm run dev -- --open --port 3000` and open [Localhost on Port 3000](http://localhost:3000) in your browser.\n{% /section %}"}, {"path": "docs/quick-starts/react-native", "title": "Start with React Native", "description": "Discover how to leverage Appwrite's powerful backend to help you build React Native apps for iOS, Android and other native platforms.", "content": "Learn how to setup your first React Native project powered by Appwrite.\nThe React Native SDK is still in `beta`. Proceed with caution if you plan to use this SDK in production.\n\n{% info title=\"React for web\" %}\nLooking to start with React for web?\nFollow the [React quickstart](/docs/quick-starts/react) and [React tutorial](/docs/tutorials/react/step-1) flows.\n{% /info %}\n\n{% section #step-1 step=1 title=\"Create React Native project\" %}\nCreate a React Native project using [npx](https://www.npmjs.com/package/npx).\n\n```sh\nnpx create-expo-app my-app\ncd my-app\n```\n{% /section %}\n\n{% section #step-2 step=2 title=\"Create project\" %}\nHead to the [Appwrite Console](https://cloud.appwrite.io/console).\n\n{% only_dark %}\n![Create project screen](/images/docs/quick-starts/dark/create-project.avif)\n{% /only_dark %}\n\n{% only_light %}\n![Create project screen](/images/docs/quick-starts/create-project.avif)\n{% /only_light %}\n\nIf this is your first time using Appwrite, create an account and create your first project.\n\nThen, under **Add a platform**, add a **Android app** or a **Apple app**.\n\n{% tabs %}\n{% tabsitem #ios title=\"iOS\" %}\nAdd your app **name** and **Bundle ID**. You can find your **Bundle Identifier** in the **General** tab for your app's primary target in XCode.\n\n**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)\n\n{% only_dark %}\n![Add a platform](/images/docs/quick-starts/dark/add-platform.avif)\n{% /only_dark %}\n\n{% only_light %}\n![Add a platform](/images/docs/quick-starts/add-platform.avif)\n{% /only_light %}\n{% /tabsitem %}\n\n{% tabsitem #android title=\"Android\" %}\nAdd 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.\n\n**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)\n\n{% arrow_link\n href=\"https://developer.android.com/build/configure-app-module\" %}\nLearn more about Android app module\n{% /arrow_link %}\n{% /tabsitem %}\n{% /tabs %}\n\nYou can skip optional steps.\n{% /section %}\n\n{% section #step-3 step=3 title=\"Install Appwrite\" %}\nInstall the Appwrite SDK for React Native and required dependencies.\n\n```sh\nnpx expo install react-native-appwrite react-native-url-polyfill\n```\n{% /section %}\n\n{% section #step-4 step=4 title=\"Implement Appwrite\" %}\nFind your project's ID in the **Settings** page.\n\n{% only_dark %}\n![Project settings screen](/images/docs/quick-starts/dark/project-id.avif)\n{% /only_dark %}\n\n{% only_light %}\n![Project settings screen](/images/docs/quick-starts/project-id.avif)\n{% /only_light %}\n\nOpen `app/(tabs)/index.tsx` and add the following code to it, replace `` with your project ID and `` with your application id or package name.\n\nThis imports and initializes Appwrite and defines some basic authentication methods.\n\n```client-react-native\nimport { StatusBar } from 'expo-status-bar';\nimport { StyleSheet, Text, View, TextInput, TouchableOpacity } from 'react-native';\nimport { Client, Account, ID, Models } from 'react-native-appwrite';\nimport React, { useState } from 'react';\n\nlet client: Client;\nlet account: Account;\n\nclient = new Client();\nclient\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('66e943139f030e2feaf8') // Your Project ID\n .setPlatform('com.example.my-app'); // Your package name / bundle identifier\n\naccount = new Account(client);\nexport default function App() {\n const [loggedInUser, setLoggedInUser] = useState | null>(null);\n const [email, setEmail] = useState('');\n const [password, setPassword] = useState('');\n const [name, setName] = useState('');\n\n async function login(email: string, password: string) {\n await account.createEmailPasswordSession({\n email,\n password\n });\n setLoggedInUser(await account.get());\n }\n\n async function register(email: string, password: string, name: string) {\n await account.create({\n userId: ID.unique(),\n email,\n password,\n name\n });\n await login(email, password);\n setLoggedInUser(await account.get());\n }\n return (\n // ... Implement your UI here\n );\n}\n\nconst styles = StyleSheet.create({\n // ... define some styles\n});\n\n```\n{% /section %}\n\n{% section #step-5 step=5 title=\"Create a login form\" %}\nWith `Client` and `Account` service initialized, you can now use them to make your first requests to Appwrite.\n\nAdd the following components to your `App.js` file to create a simple login form.\n\n```client-react-native\n\n \n {loggedInUser ? `Logged in as ${loggedInUser.name}` : 'Not logged in'}\n \n \n setEmail(text)}\n />\n setPassword(text)}\n secureTextEntry\n />\n setName(text)}\n />\n\n login(email, password)}\n >\n Login\n \n\n register(email, password, name)}\n >\n Register\n \n\n {\n await account.deleteSession({ sessionId: 'current' });\n setLoggedInUser(null);\n }}\n >\n Logout\n \n \n\n```\n\nYou can also add some simple styling to your app by adding the following styles to your `App.js` file.\n\n```client-react-native\nconst styles = StyleSheet.create({\n root: {\n marginTop: 40,\n marginBottom: 40\n },\n input: {\n height: 40,\n borderColor: 'gray',\n borderWidth: 1,\n marginBottom: 10,\n paddingHorizontal: 10,\n },\n button: {\n backgroundColor: 'gray',\n padding: 10,\n marginBottom: 10,\n alignItems: 'center',\n },\n});\n```\n{% /section %}\n\n{% section #step-6 step=6 title=\"All set\" %}\nRun your project with `npx expo start`.\n\n{% arrow_link\n href=\"https://github.com/appwrite/playground-for-react-native\" %}\nExplore the React Native playground\n{% /arrow_link %}\n{% /section %}"}, {"path": "docs/quick-starts/refine", "title": "Start with Refine", "description": "Build Refine apps with Appwrite and learn how to use our powerful backend to add authentication, user management, file storage, and more.", "content": "Learn how to setup your first Refine project powered by Appwrite.\n{% section #step-1 step=1 title=\"Create project\" %}\nHead to the [Appwrite Console](https://cloud.appwrite.io/console).\n\n{% only_dark %}\n![Create project screen](/images/docs/quick-starts/dark/create-project.avif)\n{% /only_dark %}\n{% only_light %}\n![Create project screen](/images/docs/quick-starts/create-project.avif)\n{% /only_light %}\n\nIf this is your first time using Appwrite, create an account and create your first project.\n\nThen, under **Add a platform**, add a **Web app**. The **Hostname** should be `localhost`. \n\n{% partial file=\"note-on-cors.md\" /%}\n\n{% only_dark %}\n![Add a platform](/images/docs/quick-starts/dark/add-platform.avif)\n{% /only_dark %}\n{% only_light %}\n![Add a platform](/images/docs/quick-starts/add-platform.avif)\n{% /only_light %}\n\nYou can skip optional steps.\n\n{% /section %}\n{% section #step-2 step=2 title=\"Create Refine project\" %}\nCreate a Refine project with Appwrite support.\n\n```sh\nnpm create refine-app@latest -- --preset refine-appwrite\n```\n{% /section %}\n{% section #step-3 step=3 title=\"Install Appwrite\" %}\n\n\nUsing the `refine-appwrite` preset eliminates the need for extra dependencies for a quick start.\n\nIf you want to integrate Appwrite into an existing Refine app, simply use this command:\n```sh\nnpm install @refinedev/appwrite\n```\nThen follow [this guide on the Refine documentation site](https://refine.dev/docs/packages/documentation/data-providers/appwrite).\n\n\n{% /section %}\n{% section #step-4 step=4 title=\"Import Appwrite\" %}\nFind your project's ID in the **Settings** page. \n\n{% only_dark %}\n![Project settings screen](/images/docs/quick-starts/dark/project-id.avif)\n{% /only_dark %}\n{% only_light %}\n![Project settings screen](/images/docs/quick-starts/project-id.avif)\n{% /only_light %}\nNavigate to `src/utility/appwriteClient.ts` and add your API credentials.\n\n\n```ts\nimport { Account, Appwrite, Storage } from \"@refinedev/appwrite\";\n\nconst APPWRITE_URL = ''; // Replace with your Appwrite API Endpoint\nconst APPWRITE_PROJECT = \"\"; // Replace with your project ID\n\nconst appwriteClient = new Appwrite();\n\nappwriteClient.setEndpoint(APPWRITE_URL).setProject(APPWRITE_PROJECT);\nconst account = new Account(appwriteClient);\nconst storage = new Storage(appwriteClient);\n\nexport { account, appwriteClient, storage };\n\n```\n{% /section %}\n{% section #step-5 step=5 title=\"Create a login page\" %}\n\nReplace the code in `src/App.tsx` with the following.\n\n```client-web\nimport { Authenticated, Refine } from '@refinedev/core';\nimport { dataProvider, liveProvider } from '@refinedev/appwrite';\nimport {\n AuthPage,\n ErrorComponent,\n RefineThemes,\n ThemedLayoutV2,\n useNotificationProvider,\n} from '@refinedev/antd';\nimport routerProvider, {\n CatchAllNavigate,\n NavigateToResource,\n} from '@refinedev/react-router-v6';\nimport '@refinedev/antd/dist/reset.css';\n\nimport { App as AntdApp, ConfigProvider } from 'antd';\nimport { BrowserRouter, Outlet, Route, Routes } from 'react-router-dom';\n\nimport { appwriteClient } from './utility';\nimport { authProvider } from './authProvider';\n\nconst App: React.FC = () => {\n return (\n \n \n \n ',\n })}\n liveProvider={liveProvider(appwriteClient, {\n databaseId: '',\n })}\n authProvider={authProvider}\n routerProvider={routerProvider}\n notificationProvider={useNotificationProvider}\n >\n \n \n }\n >\n \n \n \n \n }\n >\n\n }>\n \" />\n \n }\n >\n } />\n }\n />\n \n\n \n \n \n \n \n }\n >\n } />\n \n \n \n \n \n \n );\n};\n\nexport default App;\n```\n{% /section %}\n\n{% section #step-6 step=6 title=\"All set\" %}\nRun your project with `npm run dev -- --open --port 3000` and open [Localhost on Port 3000](http://localhost:3000) in your browser.\n{% /section %}"}, {"path": "docs/quick-starts/ruby", "title": "Start with Ruby", "description": "Dive into our step-by-step guide on integrating Appwrite with your Ruby server backend application. Get your backend up and running quickly with this tutorial.", "content": "Learn how to setup your first Ruby project powered by Appwrite.\n{% section #step-1 step=1 title=\"Create project\" %}\nHead to the [Appwrite Console](https://cloud.appwrite.io/console).\n\nIf this is your first time using Appwrite, create an account and create your first project.\n\n{% only_dark %}\n![Create project screen](/images/docs/quick-starts/dark/create-project.avif)\n{% /only_dark %}\n{% only_light %}\n![Create project screen](/images/docs/quick-starts/create-project.avif)\n{% /only_light %}\n\nThen, under **Integrate with your server**, add an **API Key** with the following scopes.\n\n{% only_dark %}\n![Create project screen](/images/docs/quick-starts/dark/integrate-server.avif)\n{% /only_dark %}\n{% only_light %}\n![Create project screen](/images/docs/quick-starts/integrate-server.avif)\n{% /only_light %}\n\n| Category {% width=120 %} | Required scopes | Purpose |\n|-----------|-----------------------|---------|\n| Database | `databases.write` | Allows API key to create, update, and delete [databases](/docs/products/databases/databases). |\n| | `tables.write` | Allows API key to create, update, and delete [tables](/docs/products/databases/tables). |\n| | `columns.write` | Allows API key to create, update, and delete [columns](/docs/products/databases/tables#columns). |\n| | `rows.read` | Allows API key to read [rows](/docs/products/databases/rows). |\n| | `rows.write` | Allows API key to create, update, and delete [rows](/docs/products/databases/rows). |\n\nOther scopes are optional.\n\n{% /section %}\n{% section #step-2 step=2 title=\"Create Ruby project\" %}\nCreate a Ruby CLI application.\n\n```sh\nmkdir my-app\ncd my-app\nbundle init\n```\n\n{% /section %}\n{% section #step-3 step=3 title=\"Install Appwrite\" %}\n\nInstall the Ruby Appwrite SDK. Make sure to lock your SDK to version `10.0.0` to avoid breaking changes.\n\n```sh\nbundle add appwrite \n```\n{% /section %}\n{% section #step-4 step=4 title=\"Import Appwrite\" %}\n\nFind your project ID in the **Settings** page. Also, click on the **View API Keys** button to find the API key that was created earlier. \n\n{% only_dark %}\n![Project settings screen](/images/docs/quick-starts/dark/project-id.avif)\n{% /only_dark %}\n{% only_light %}\n![Project settings screen](/images/docs/quick-starts/project-id.avif)\n{% /only_light %}\n\nCreate a new file `app.rb` and initialize the Appwrite Client. Replace `` with your project ID and `` with your API key.\n\n```ruby\n# Initialize the Appwrite client\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new()\n\nclient\n .set_endpoint('https://.cloud.appwrite.io/v1') # Your Appwrite Endpoint\n .set_project('') # Your project ID\n .set_key('') # Your secret API key\n```\n\n{% /section %}\n{% section #step-5 step=5 title=\"Initialize database\" %}\n\nOnce the Appwrite Client is initialized, create a function to configure a todo table.\n\n```ruby\ntablesDB = TablesDB.new(client)\n\ntodo_database = nil\ntodo_table = nil\n\ndef prepare_database(databases)\n todo_database = tablesDB.create(\n database_id: ID.unique(), \n name: 'TodosDB'\n )\n\n todo_table = tablesDB.create_table(\n database_id: todo_database.id, \n table_id: ID.unique(), \n name: 'Todos'\n )\n\n tablesDB.create_varchar_column(\n database_id: todo_database.id,\n table_id: todo_table.id,\n key: 'title',\n size: 255,\n required: true\n )\n\n tablesDB.create_text_column(\n database_id: todo_database.id,\n table_id: todo_table.id,\n key: 'description',\n required: false\n )\n\n tablesDB.create_boolean_column(\n database_id: todo_database.id, \n table_id: todo_table.id, \n key: 'isComplete', \n required: false,\n default: false\n )\n return todo_database, todo_table\nend\n```\n\n{% /section %}\n{% section #step-6 step=6 title=\"Add rows\" %}\nCreate a function to add some mock data into your new table.\n\n```ruby\ndef seed_database(databases, todo_database, todo_table)\n test_todo1 = {\n title: 'Buy apples',\n description: 'At least 2KGs',\n isComplete: true\n }\n\n test_todo2 = {\n title: 'Wash the apples',\n isComplete: true\n }\n\n test_todo3 = {\n title: 'Cut the apples',\n description: 'Don\\'t forget to pack them in a box',\n isComplete: false\n }\n\n tablesDB.create_row(\n database_id: todo_database.id, \n table_id: todo_table.id, \n row_id: ID.unique(), \n data: test_todo1\n )\n \n tablesDB.create_row(\n database_id: todo_database.id, \n table_id: todo_table.id, \n row_id: ID.unique(), \n data: test_todo2\n )\n \n tablesDB.create_row(\n database_id: todo_database.id, \n table_id: todo_table.id, \n row_id: ID.unique(), \n data: test_todo3\n )\nend\n```\n\n{% /section %}\n{% section #step-7 step=7 title=\"Retrieve rows\" %}\n\nCreate a function to retrieve the mock todo data and a function to execute the requests in order.\nRun the functions to by calling `run_all_tasks()`. \n\n```ruby\ndef get_todos(databases, todo_database, todo_table)\n todos = tablesDB.list_rows(\n database_id: todo_database.id, \n table_id: todo_table.id\n )\n\n todos.rows.each do |todo|\n puts \"Title: #{todo.data['title']}\\nDescription: #{todo.data['description']}\\nIs Todo Complete: #{todo.data['isComplete']}\\n\\n\"\n end\nend\n\ndef run_all_tasks(databases)\n todo_database, todo_table = prepare_database(databases)\n seed_database(databases, todo_database, todo_table)\n get_todos(databases, todo_database, todo_table)\nend\n\nrun_all_tasks(databases)\n```\n\n{% /section %}\n\n{% section #step-8 step=8 title=\"All set\" %}\n\nRun your project with `ruby app.rb` and view the response in your console. \n\n{% /section %}"}, {"path": "docs/quick-starts/rust", "title": "Start with Rust", "description": "Learn to get started with server integrations with Appwrite Rust SDK.", "content": "Learn how to setup your first Rust project powered by Appwrite.\n{% section #step-1 step=1 title=\"Create project\" %}\nHead to the [Appwrite Console](https://cloud.appwrite.io/console).\n\nIf this is your first time using Appwrite, create an account and create your first project.\n\n{% only_dark %}\n![Create project screen](/images/docs/quick-starts/dark/create-project.avif)\n{% /only_dark %}\n{% only_light %}\n![Create project screen](/images/docs/quick-starts/create-project.avif)\n{% /only_light %}\n\nThen, under **Integrate with your server**, add an **API Key** with the following scopes.\n\n{% only_dark %}\n![Server integrations](/images/docs/quick-starts/dark/integrate-server.avif)\n{% /only_dark %}\n{% only_light %}\n![Server integrations](/images/docs/quick-starts/integrate-server.avif)\n{% /only_light %}\n\n| Category {% width=120 %} | Required scopes | Purpose |\n|-----------|-----------------------|---------|\n| Database | `databases.write` | Allows API key to create, update, and delete [databases](/docs/products/databases/databases). |\n| | `tables.write` | Allows API key to create, update, and delete [tables](/docs/products/databases/tables). |\n| | `columns.write` | Allows API key to create, update, and delete [columns](/docs/products/databases/tables#columns). |\n| | `rows.read` | Allows API key to read [rows](/docs/products/databases/rows). |\n| | `rows.write` | Allows API key to create, update, and delete [rows](/docs/products/databases/rows). |\n\nOther scopes are optional.\n\n{% only_dark %}\n![Project settings screen](/images/docs/quick-starts/dark/project-id.avif)\n{% /only_dark %}\n{% only_light %}\n![Project settings screen](/images/docs/quick-starts/project-id.avif)\n{% /only_light %}\n\n{% /section %}\n{% section #step-2 step=2 title=\"Create Rust project\" %}\n\nCreate a new Rust application.\n\n```sh\ncargo new my_app\ncd my_app\n```\n\n{% /section %}\n{% section #step-3 step=3 title=\"Install Appwrite\" %}\n\nInstall the Rust Appwrite SDK and its dependencies.\n\n```sh\ncargo add appwrite\ncargo add tokio -F full\ncargo add serde_json\n```\n\n{% /section %}\n{% section #step-4 step=4 title=\"Import Appwrite\" %}\n\nFind your project ID in the **Settings** page. Also, click on the **View API Keys** button to find the API key that was created earlier.\n\n{% only_dark %}\n![Project settings screen](/images/docs/quick-starts/dark/project-id.avif)\n{% /only_dark %}\n{% only_light %}\n![Project settings screen](/images/docs/quick-starts/project-id.avif)\n{% /only_light %}\n\nOpen `src/main.rs` and initialize the Appwrite Client. Replace `` with your project ID and `` with your API key.\n\n```rust\nuse appwrite::Client;\nuse appwrite::services::tables_db::TablesDB;\nuse appwrite::id::ID;\nuse serde_json::json;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box> {\n let client = Client::new()\n .set_endpoint(\"https://.cloud.appwrite.io/v1\")\n .set_project(\"\")\n .set_key(\"\");\n\n Ok(())\n}\n```\n\n{% /section %}\n{% section #step-5 step=5 title=\"Initialize database\" %}\n\nOnce the Appwrite Client is initialized, create a function to configure a todo table.\n\n```rust\nasync fn prepare_database(\n tables_db: &TablesDB,\n) -> Result<(String, String), Box> {\n let todo_database = tables_db.create(\n ID::unique(),\n \"TodosDB\",\n None,\n ).await?;\n\n let todo_table = tables_db.create_table(\n &todo_database.id,\n ID::unique(),\n \"Todos\",\n None, None, None, None, None,\n ).await?;\n\n tables_db.create_varchar_column(\n &todo_database.id,\n &todo_table.id,\n \"title\",\n 255,\n true,\n None, None, None,\n ).await?;\n\n tables_db.create_text_column(\n &todo_database.id,\n &todo_table.id,\n \"description\",\n false,\n Some(\"This is a test description.\"),\n None, None,\n ).await?;\n\n tables_db.create_boolean_column(\n &todo_database.id,\n &todo_table.id,\n \"isComplete\",\n true,\n None, None,\n ).await?;\n\n Ok((todo_database.id, todo_table.id))\n}\n```\n\n{% /section %}\n{% section #step-6 step=6 title=\"Add rows\" %}\nCreate a function to add some mock data into your new table.\n\n```rust\nasync fn seed_database(\n tables_db: &TablesDB,\n database_id: &str,\n table_id: &str,\n) -> Result<(), Box> {\n tables_db.create_row(\n database_id,\n table_id,\n ID::unique(),\n json!({\n \"title\": \"Buy apples\",\n \"description\": \"At least 2KGs\",\n \"isComplete\": true\n }),\n None, None,\n ).await?;\n\n tables_db.create_row(\n database_id,\n table_id,\n ID::unique(),\n json!({\n \"title\": \"Wash the apples\",\n \"isComplete\": true\n }),\n None, None,\n ).await?;\n\n tables_db.create_row(\n database_id,\n table_id,\n ID::unique(),\n json!({\n \"title\": \"Cut the apples\",\n \"description\": \"Don't forget to pack them in a box\",\n \"isComplete\": false\n }),\n None, None,\n ).await?;\n\n Ok(())\n}\n```\n\n{% /section %}\n{% section #step-7 step=7 title=\"Retrieve rows\" %}\n\nCreate a function to retrieve the mock todo data and execute the functions in `main`.\n\n```rust\nuse appwrite::query::Query;\n\nasync fn get_todos(\n tables_db: &TablesDB,\n database_id: &str,\n table_id: &str,\n) -> Result<(), Box> {\n // Retrieve rows (default limit is 25)\n let todos = tables_db.list_rows(\n database_id,\n table_id,\n None, None, None, None,\n ).await?;\n\n println!(\"Todos:\");\n for todo in &todos.rows {\n println!(\"Title: {}\\nDescription: {}\\nIs Todo Complete: {}\\n\",\n todo.get::(\"title\").unwrap_or_default(),\n todo.get::(\"description\").unwrap_or_default(),\n todo.get::(\"isComplete\").unwrap_or_default(),\n );\n }\n\n // Use queries to filter completed todos with pagination\n let completed_todos = tables_db.list_rows(\n database_id,\n table_id,\n Some(vec![\n Query::equal(\"isComplete\", true).to_string(),\n Query::order_desc(\"$createdAt\").to_string(),\n Query::limit(5).to_string(),\n ]),\n None, None, None,\n ).await?;\n\n println!(\"Completed todos (limited to 5):\");\n for todo in &completed_todos.rows {\n println!(\"Title: {}\\nDescription: {}\\nIs Todo Complete: {}\\n\",\n todo.get::(\"title\").unwrap_or_default(),\n todo.get::(\"description\").unwrap_or_default(),\n todo.get::(\"isComplete\").unwrap_or_default(),\n );\n }\n\n // Query for incomplete todos\n let incomplete_todos = tables_db.list_rows(\n database_id,\n table_id,\n Some(vec![\n Query::equal(\"isComplete\", false).to_string(),\n Query::order_asc(\"title\").to_string(),\n ]),\n None, None, None,\n ).await?;\n\n println!(\"Incomplete todos (ordered by title):\");\n for todo in &incomplete_todos.rows {\n println!(\"Title: {}\\nDescription: {}\\nIs Todo Complete: {}\\n\",\n todo.get::(\"title\").unwrap_or_default(),\n todo.get::(\"description\").unwrap_or_default(),\n todo.get::(\"isComplete\").unwrap_or_default(),\n );\n }\n\n Ok(())\n}\n```\n\nMake sure to update `main()` with the functions you created. Your `main()` function should look something like this:\n\n```rust\nuse appwrite::Client;\nuse appwrite::services::tables_db::TablesDB;\nuse appwrite::id::ID;\nuse appwrite::query::Query;\nuse serde_json::json;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box> {\n let client = Client::new()\n .set_endpoint(\"https://.cloud.appwrite.io/v1\")\n .set_project(\"\")\n .set_key(\"\");\n\n let tables_db = TablesDB::new(&client);\n\n let (database_id, table_id) = prepare_database(&tables_db).await?;\n seed_database(&tables_db, &database_id, &table_id).await?;\n get_todos(&tables_db, &database_id, &table_id).await?;\n\n Ok(())\n}\n```\n\n{% /section %}\n\n{% section #step-8 step=8 title=\"All set\" %}\n\nRun your project with `cargo run` and view the response in your console.\n\n{% /section %}"}, {"path": "docs/quick-starts/solid", "title": "Start with Solid", "description": "Build Solid apps with Appwrite and learn how to use our powerful backend to add authentication, user management, file storage, and more.", "content": "Learn how to setup your first Solid project powered by Appwrite.\n\n{% section #step-1 step=1 title=\"Create project\" %}\nHead to the [Appwrite Console](https://cloud.appwrite.io/console).\n\n{% only_dark %}\n![Create project screen](/images/docs/quick-starts/dark/create-project.avif)\n{% /only_dark %}\n\n{% only_light %}\n![Create project screen](/images/docs/quick-starts/create-project.avif)\n{% /only_light %}\n\nIf this is your first time using Appwrite, create an account and create your first project.\n\nThen, under **Add a platform**, add a **Web app**. The **Hostname** should be `localhost`.\n\n{% partial file=\"note-on-cors.md\" /%}\n\n{% only_dark %}\n![Add a platform](/images/docs/quick-starts/dark/add-platform.avif)\n{% /only_dark %}\n\n{% only_light %}\n![Add a platform](/images/docs/quick-starts/add-platform.avif)\n{% /only_light %}\n\nYou can skip optional steps.\n{% /section %}\n\n{% section #step-2 step=2 title=\"Create Solid project\" %}\nCreate a Vite project.\n\n```sh\nnpm create vite@latest my-app -- --template solid && cd my-app\n```\n{% /section %}\n\n{% section #step-3 step=3 title=\"Install Appwrite\" %}\nInstall the JavaScript Appwrite SDK.\n\n```sh\nnpm install appwrite\n```\n{% /section %}\n\n{% section #step-4 step=4 title=\"Import Appwrite\" %}\nFind your project's ID in the **Settings** page.\n\n{% only_dark %}\n![Project settings screen](/images/docs/quick-starts/dark/project-id.avif)\n{% /only_dark %}\n\n{% only_light %}\n![Project settings screen](/images/docs/quick-starts/project-id.avif)\n{% /only_light %}\n\nCreate a new file `src/lib/appwrite.js` and add the following code to it, replace `` with your project ID.\n\n```client-web\nimport { Client, Account } from 'appwrite';\n\nexport const client = new Client();\n\nclient\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject(''); // Replace with your project ID\n\nexport const account = new Account(client);\nexport { ID } from 'appwrite';\n```\n{% /section %}\n\n{% section #step-5 step=5 title=\"Create a login page\" %}\nAdd the following code to `src/App.jsx`.\n\n```client-web\nimport { createSignal } from 'solid-js'\nimport { account, ID } from './lib/appwrite';\n\nconst App = () => {\n const [loggedInUser, setLoggedInUser] = createSignal(null);\n const [email, setEmail] = createSignal('');\n const [password, setPassword] = createSignal('');\n const [name, setName] = createSignal('');\n\n async function login(email, password) {\n await account.createEmailPasswordSession({\n email,\n password\n });\n setLoggedInUser(await account.get());\n }\n\n async function register(email, password, name) {\n await account.create({\n userId: ID.unique(),\n email,\n password,\n name\n });\n login(email, password);\n }\n\n async function logout() {\n await account.deleteSession({ sessionId: 'current' });\n setLoggedInUser(null);\n }\n\n if (loggedInUser()) {\n return (\n
\n

Logged in as {loggedInUser().name}

\n \n
\n );\n }\n\n return (\n
\n

Not logged in

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

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

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

Logged in as {loggedInUser.name}

\n \n
\n );\n }\n\n return (\n
\n

Not logged in

\n
\n setEmail(e.target.value)}\n />\n setPassword(e.target.value)}\n />\n setName(e.target.value)}\n />\n \n \n \n
\n );\n}\n```\n{% /section %}\n\n{% section #step-6 step=6 title=\"All set\" %}\nRun your project with `npm run dev` and open [localhost on port 3000](http://localhost:3000) in your browser.\n{% /section %}"}, {"path": "docs/quick-starts/vue", "title": "Start with Vue.js", "description": "Build Vue.js apps with Appwrite and learn how to use our powerful backend to add authentication, user management, file storage, and more.", "content": "Learn how to setup your first Vue project powered by Appwrite.\n{% section #step-1 step=1 title=\"Create project\" %}\nHead to the [Appwrite Console](https://cloud.appwrite.io/console).\n\n{% only_dark %}\n![Create project screen](/images/docs/quick-starts/dark/create-project.avif)\n{% /only_dark %}\n{% only_light %}\n![Create project screen](/images/docs/quick-starts/create-project.avif)\n{% /only_light %}\n\nIf this is your first time using Appwrite, create an account and create your first project.\n\nThen, under **Add a platform**, add a **Web app**. The **Hostname** should be `localhost`.\n\n{% partial file=\"note-on-cors.md\" /%}\n\n{% only_dark %}\n![Add a platform](/images/docs/quick-starts/dark/add-platform.avif)\n{% /only_dark %}\n{% only_light %}\n![Add a platform](/images/docs/quick-starts/add-platform.avif)\n{% /only_light %}\n\nYou can skip optional steps.\n\n{% /section %}\n{% section #step-2 step=2 title=\"Create Vue project\" %}\nCreate a Vue project.\n\n```sh\nnpm init vue@latest my-app && cd my-app\n```\n{% /section %}\n{% section #step-3 step=3 title=\"Install Appwrite\" %}\n\nInstall the JavaScript Appwrite SDK.\n\n```sh\nnpm install appwrite\n```\n{% /section %}\n{% section #step-4 step=4 title=\"Import Appwrite\" %}\nFind your project's ID in the **Settings** page.\n\n{% only_dark %}\n![Project settings screen](/images/docs/quick-starts/dark/project-id.avif)\n{% /only_dark %}\n{% only_light %}\n![Project settings screen](/images/docs/quick-starts/project-id.avif)\n{% /only_light %}\n\nCreate a new file `src/lib/appwrite.js` and add the following code to it, replace `` with your project ID.\n\n```client-web\nimport { Client, Account} from 'appwrite';\n\nexport const client = new Client();\n\nclient\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject(''); // Replace with your project ID\n\nexport const account = new Account(client);\nexport { ID } from 'appwrite';\n```\n{% /section %}\n{% section #step-5 step=5 title=\"Create a login page\" %}\nAdd the following code to `src/App.vue`.\n\n```html\n\n\n\n```\n{% /section %}\n\n{% section #step-6 step=6 title=\"All set\" %}\nRun your project with `npm run dev -- --open --port 3000` and open [Localhost on Port 3000](http://localhost:3000) in your browser.\n{% /section %}"}, {"path": "docs/quick-starts/web", "title": "Start with Web", "description": "Build JavaScript or Typescript web apps with Appwrite. Add authentication, user management, file storage, and more. Read our guide to get started!", "content": "Learn how to add Appwrite to your web apps.\n{% section #step-1 step=1 title=\"Create project\" %}\nHead to the [Appwrite Console](https://cloud.appwrite.io/console).\n\n{% only_dark %}\n![Create project screen](/images/docs/quick-starts/dark/create-project.avif)\n{% /only_dark %}\n{% only_light %}\n![Create project screen](/images/docs/quick-starts/create-project.avif)\n{% /only_light %}\n\nIf this is your first time using Appwrite, create an account and create your first project.\n\nThen, under **Add a platform**, add a **Web app**.\nThe **Hostname** should be `localhost` or the domain on which you're hosting your web app.\n\n{% partial file=\"note-on-cors.md\" /%}\n\n{% only_dark %}\n![Add a platform](/images/docs/quick-starts/dark/add-platform.avif)\n{% /only_dark %}\n{% only_light %}\n![Add a platform](/images/docs/quick-starts/add-platform.avif)\n{% /only_light %}\n\nYou can skip optional steps.\n\n{% /section %}\n{% section #step-2 step=2 title=\"Install Appwrite\" %}\nYou can install the Appwrite Web SDK using a package manager.\n```sh\nnpm install appwrite\n```\n\nYou 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.\n\n```html\n\n```\n{% /section %}\n{% section #step-3 step=3 title=\"Initialize Appwrite\" %}\n\nIf you installed via npm, you can import `Client` and `Account` from the Appwrite SDK.\n\n```client-web\nimport { Client, Account } from 'appwrite';\n\nexport const client = new Client();\n\nclient\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject(''); // Replace with your project ID\n\nexport const account = new Account(client);\nexport { ID } from 'appwrite';\n```\n\nIf you're using CDN, the library loads directly in your browser as a global object, so you access it through Appwrite instead of imports.\n\n```js\nconst client = new Appwrite.Client()\n\nclient\n .setEndpoint('https://cloud.appwrite.io/v1')\n .setProject('') // Replace with your project ID\n\nconst account = new Appwrite.Account(client)\nconst tablesDB = new Appwrite.TablesDB(client)\n```\n{% /section %}\n\n{% section #step-4 step=4 title=\"Using TypeScript\" %}\nIf you prefer TypeScript, you can import TypeScript models from the Appwrite SDK.\n\n```ts\n// appwrite.ts\n\nimport { Client, TablesDB, Account } from \"appwrite\";\n// Import type models for Appwrite\nimport { type Models } from 'appwrite';\n\nconst client: Client = new Client();\n\nclient\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject(''); // Replace with your project ID\n\nexport const account: Account = new Account(client);\nexport const tablesDB: TablesDB = new TablesDB(client);\n\n// You then use the imported type definitions like this\nconst authUser: Models.Session = await account.createEmailPasswordSession({\n email,\n password\n});\n```\n{% /section %}\n\n{% section #step-5 step=5 title=\"Extending TypeScript models\" %}\nSometimes you'll need to extend TypeScript models with your own type definitions.\n\nFor example, when you fetch a list of rows from a table, you can define the expected structure of the rows like this.\n```ts\ninterface Idea extends Models.Row {\n title: string;\n description: string;\n userId: string;\n}\n```\n\nWhen you fetch rows, you can use this new `Idea` interface like this.\n\n```ts\nconst response = await tablesDB.listRows({\n databaseId: ideasDatabaseId,\n tableId: ideasTableId,\n queries: [Query.orderDesc(\"$createdAt\"), Query.limit(queryLimit)]\n});\nconst ideas = response.rows as Idea[];\n```\n{% /section %}\n\n{% section #step-6 step=6 title=\"All set\" %}\nThe Appwrite SDK works with your favorite Web frameworks.\n\nLearn to use Appwrite by adding authentication to a simple web app.\n{% cards %}\n{% cards_item href=\"/docs/quick-starts/nextjs\" title=\"Next.js\" %}\nGet started with Appwrite and Next.js\n{% /cards_item %}\n{% cards_item href=\"/docs/quick-starts/react\" title=\"React\" %}\nGet started with Appwrite and React\n{% /cards_item %}\n{% cards_item href=\"/docs/quick-starts/vue\" title=\"Vue.js\" %}\nGet started with Appwrite and Vue.js\n{% /cards_item %}\n{% cards_item href=\"/docs/quick-starts/nuxt\" title=\"Nuxt\" %}\nGet started with Appwrite and Nuxt\n{% /cards_item %}\n{% cards_item href=\"/docs/quick-starts/sveltekit\" title=\"SvelteKit\" %}\nGet started with Appwrite and SvelteKit\n{% /cards_item %}\n{% cards_item href=\"/docs/quick-starts/angular\" title=\"Angular\" %}\nGet started with Appwrite and Angular\n{% /cards_item %}\n{% /cards %}\n\nLearn to use Appwrite by building an idea tracker app.\n{% cards %}\n{% cards_item href=\"/docs/tutorials/react\" title=\"React\" %}\nGet started with Appwrite and React\n{% /cards_item %}\n{% cards_item href=\"/docs/tutorials/vue\" title=\"Vue.js\" %}\nGet started with Appwrite and Vue.js\n{% /cards_item %}\n{% cards_item href=\"/docs/tutorials/nuxt\" title=\"Nuxt\" %}\nGet started with Appwrite and Nuxt\n{% /cards_item %}\n{% cards_item href=\"/docs/tutorials/sveltekit\" title=\"SvelteKit\" %}\nGet started with Appwrite and SvelteKit\n{% /cards_item %}\n{% /cards %}\n{% /section %}\n\n{% section #step-7 step=7 title=\"Type safety with TypeScript\" %}\n## Type safety with TypeScript\n\nFor better type safety in TypeScript projects, define interfaces and use generics:\n\n```typescript\ninterface User {\n name: string;\n email: string;\n isVerified: boolean;\n}\n\nimport { Client, TablesDB } from \"appwrite\";\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nconst databases = new TablesDB(client);\n\n// Type-safe database operations\ntry {\n const users = await databases.listRows({\n databaseId: '[DATABASE_ID]',\n tableId: '[TABLE_ID]'\n });\n\n users.rows.forEach(user => {\n console.log(`User: ${user.name} (${user.email})`);\n });\n} catch (error) {\n console.log(error);\n}\n```\n\n{% info title=\"Generate types automatically\" %}\nUse the [Appwrite CLI](/docs/products/databases/type-generation) to generate TypeScript interfaces automatically: `appwrite types ./types`\n{% /info %}\n\n{% /section %}"}, {"path": "docs/references", "title": "API reference", "description": "Here's a complete API reference for Appwrite SDK, REST, and GraphQL APIs. Learn how to use Authentication, Databases, Storage, and other Appwrite APIs.", "content": "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).\n\n## Client vs Server APIs {% #client-vs-server %}\n\nClient APIs and SDKs are for integrating with Appwrite to build client-based applications and websites.\nClient APIs only give access to resources if users have been [granted permissions](/docs/advanced/security/permissions).\n\nServer API and SDKs are for integrating with Appwrite to build backend or server applications.\nServer APIs are constrained by an [API key's](/docs/advanced/security/api-keys) scope, ignoring user permissions.\n\n## APIs {% #api %}\nBefore using the Appwrite APIs, in the **Settings** of your Appwrite project, obtain your **API endpoint** and **Project ID**.\nClient APIs require an active session, created from [signing up and logging in](/docs/products/auth/accounts#signup-login).\nServer APIs require [API keys](/docs/advanced/security/api-keys).\n\n\n{% only_dark %}\n![Project settings screen](/images/docs/quick-starts/dark/project-id.avif)\n{% /only_dark %}\n{% only_light %}\n![Project settings screen](/images/docs/quick-starts/project-id.avif)\n{% /only_light %}\n\n{% cards %}\n{% cards_item href=\"/docs/references/cloud/client-web/account\" title=\"Account\" %}\nAdd signup, login, logout, and other common authentication methods to client apps.\n{% /cards_item %}\n{% cards_item href=\"/docs/references/cloud/server-nodejs/users\" title=\"Users\" %}\nManage users from an admin scope to build server integrations.\n{% /cards_item %}\n{% cards_item href=\"/docs/references/cloud/client-web/teams\" title=\"Teams\" %}\nGroup users to share access to rows, files, and functions.\n{% /cards_item %}\n{% cards_item href=\"/docs/references/cloud/client-web/databases\" title=\"Databases\" %}\nScalable and robust database backed by your favorite technologies.\n{% /cards_item %}\n{% cards_item href=\"/docs/references/cloud/client-web/tablesDB\" title=\"TablesDB\" %}\nCreate structured tables of rows with validated columns and fine-grained permissions.\n{% /cards_item %}\n{% cards_item href=\"/docs/references/cloud/server-nodejs/sites\" title=\"Sites\" %}\nBuild and deploy websites on the internet at scale.\n{% /cards_item %}\n{% cards_item href=\"/docs/references/cloud/client-web/storage\" title=\"Storage\" %}\nSecurely store files with advanced compression, encryption and image transformations.\n{% /cards_item %}\n{% cards_item href=\"/docs/references/cloud/client-web/functions\" title=\"Functions\" %}\nDeploy and scale serverless functions in secure, isolated runtimes.\n{% /cards_item %}\n{% cards_item href=\"/docs/references/cloud/server-nodejs/messaging\" title=\"Messaging\" %}\nSend push notifications, SMS, or emails to users or groups of users using your app.\n{% /cards_item %}\n{% cards_item href=\"/docs/references/cloud/server-nodejs/tokens\" title=\"Tokens\" %}\nCreate and manage temporary access tokens for secure file sharing and resource access control.\n{% /cards_item %}\n{% cards_item href=\"/docs/references/cloud/client-web/locale\" title=\"Localization\" %}\nUtility APIs to customize your app based on your users' location.\n{% /cards_item %}\n{% cards_item href=\"/docs/references/cloud/client-web/avatars\" title=\"Avatars\" %}\nComplete everyday tasks related to your app image, icons, and avatars.\n{% /cards_item %}\n{% cards_item href=\"/docs/references/cloud/client-web/presences\" title=\"Presences\" %}\nTrack which signed-in users are active right now and broadcast their status in realtime.\n{% /cards_item %}\n{% /cards %}\n\n## Error handling {% #error-handling %}\n\nWhen 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."}, {"path": "docs/references/quick-start", "title": "Quick start", "description": "Configure the Appwrite SDKs and take the necessary steps to start using Appwrite.", "content": "Follow these steps before you begin using the Appwrite SDKs or accessing Appwrite through the REST and GraphQL API.\n\nIf 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.\n\n{% section #select-the-right-API step=1 title=\"Select the right API\" %}\nAppwrite has two types of APIs for different use cases, select one or both depending on your use case.\n\nIf you're creating a **web, mobile, or native application** used by end-users that will register and create accounts,\ninstall a [Client SDK](/docs/sdks#client) and follow steps for Client APIs.\n\nIf you're create a server application, like a **backend, admin app, or a CLI tool**,\ninstall a [Server SDK](/docs/sdks#server) and follow steps for Server APIs.\n\nIf you're creating a **Server-side Rendered (SSR)** web app, \ninstall a [Server SDK](/docs/sdks#server) and follow steps for SSR.\n{% /section %}\n\n{% section #configure-project step=2 title=\"Configure project\" %}\n\nHead to the [Appwrite Console](https://cloud.appwrite.io/console).\n\nIf this is your first time using Appwrite, create an account and create your first project.\n\n{% only_dark %}\n![Create project screen](/images/docs/quick-starts/dark/create-project.avif)\n{% /only_dark %}\n{% only_light %}\n![Create project screen](/images/docs/quick-starts/create-project.avif)\n{% /only_light %}\n\nThen, configure your project depending on use case.\nYou can follow all three flows to enable all three use cases.\n\n{% tabs %}\n{% tabsitem #client title=\"Client\" %}\nUnder **Add a platform**, add a platform for **each** web, mobile, and native app you plan to create.\nThis means, a different platform for each web app hosted under a different domain,\nand a different platform for each mobile or native app that use a different package ID.\n\n{% only_dark %}\n![Add a platform](/images/docs/quick-starts/dark/add-platform.avif)\n{% /only_dark %}\n{% only_light %}\n![Add a platform](/images/docs/quick-starts/add-platform.avif)\n{% /only_light %}\n\nYou can skip optional steps.\n{% /tabsitem %}\n\n{% tabsitem #server title=\"Server\" %}\nUnder **Integrate with your server**, add an **API Key**.\n{% only_dark %}\n![Create project screen](/images/docs/quick-starts/dark/integrate-server.avif)\n{% /only_dark %}\n{% only_light %}\n![Create project screen](/images/docs/quick-starts/integrate-server.avif)\n{% /only_light %}\n\nEnable the scopes for the Appwrite products you plan to use for your app.\nIt's a good idea to only grant scopes that you need, and edit the API keys as your needs change for security.\n{% /tabsitem %}\n\n{% tabsitem #ssr title=\"SSR\" %}\nUnder **Integrate with your server**, add an **API Key** with the following scopes.\n{% only_dark %}\n![Create project screen](/images/docs/quick-starts/dark/integrate-server.avif)\n{% /only_dark %}\n{% only_light %}\n![Create project screen](/images/docs/quick-starts/integrate-server.avif)\n{% /only_light %}\n\n| Category {% width=120 %} | Required scopes | Purpose |\n|-----------|-----------------------|---------|\n| Sessions | `sessions.write` | Allows API key to create, update, and delete sessions. |\n\n{% /tabsitem %}\n{% /tabs %}\n\n{% /section %}\n\n{% section #initialize-sdks step=3 title=\"Initialize SDKs\" %}\nWhen using the Appwrite APIs, you need to pass information like endpoint, project ID, credentials\nand other metadata for Appwrite to properly parse your request.\n\n{% tabs %}\n{% tabsitem #client title=\"Client\" %}\nClient apps need to be configured with endpoint and project ID,\nso the Appwrite SDK knows which endpoint and project to connect to.\n{% multicode %}\n```client-web\nimport { Client } from \"appwrite\";\n\nconst client = new Client();\n\nclient\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n;\n```\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\nClient client = Client();\n\nclient\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n;\n```\n```client-apple\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n```\n```client-android-kotlin\nimport io.appwrite.Client\n\nval client = Client(context)\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n```\n{% /multicode %}\n{% /tabsitem %}\n\n{% tabsitem #server title=\"Server\" %}\nServer apps need to be configured with endpoint, project ID, and an API key\nso the Appwrite SDK knows which endpoint and project to connect to, as well as have credentials to perform admin actions.\n\n{% multicode %}\n```server-nodejs\nconst sdk = require('node-appwrite');\n\n// Init SDK\nconst client = new sdk.Client();\n\nclient\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n .setJWT('') // Your secret JSON Web Token\n;\n```\n```deno\nimport * as sdk from \"npm:node-appwrite\";\n\n// Init SDK\nlet client = new sdk.Client();\n\nclient\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n .setJWT('') // Your secret JSON Web Token\n;\n```\n```php\nsetEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n ->setProject('') // Your project ID\n ->setJWT('') // Your secret JSON Web Token\n;\n```\n```python\nfrom appwrite.client import Client\n\nclient = Client()\n\n(client\n .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('') # Your project ID\n .set_jwt('') # Your secret JSON Web Token\n)\n```\n```ruby\nrequire 'appwrite'\n\ninclude Appwrite\n\nclient = Client.new\n .set_endpoint('https://.cloud.appwrite.io/v1') # Your API Endpoint\n .set_project('') # Your project ID\n .set_jwt('') # Your secret JSON Web Token\n```\n```csharp\nusing Appwrite;\nusing Appwrite.Services;\n\nvar client = new Client()\n .SetEndPoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .SetProject(\"\") // Your project ID\n .SetJWT(\"\"); // Your secret JSON Web Token\n```\n```dart\nimport 'package:dart_appwrite/dart_appwrite.dart';\n\nvoid main() { // Init SDK\n Client client = Client();\n\n client\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n .setJWT('') // Your secret JSON Web Token\n ;\n}\n```\n```kotlin\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\n\nClient client = new Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n .setJWT(\"\"); // Your secret JSON Web Token\n```\n```java\nimport io.appwrite.Client;\nimport io.appwrite.coroutines.CoroutineCallback;\n\nClient client = new Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n .setJWT(\"\"); // Your secret JSON Web Token\n```\n```swift\nimport Appwrite\n\nlet client = Client()\n .setEndpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .setProject(\"\") // Your project ID\n .setJWT(\"\") // Your secret JSON Web Token\n```\n```server-rust\nuse appwrite::Client;\n\nlet client = Client::new()\n .set_endpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .set_project(\"\") // Your project ID\n .set_jwt(\"\"); // Your secret JSON Web Token\n```\n{% /multicode %}\n{% /tabsitem %}\n\n{% tabsitem #ssr title=\"SSR\" %}\nAppwrite uses Server SDKs for SSR apps. The initialization is different\n## Admin client {% #admin-client %}\n\n{% info title=\"Admin clients\" %}\nAdmin clients should only be used if you need to perform admin actions that bypass permissions\nor [unauthenticated requests that bypass rate limits](/docs/products/auth/server-side-rendering#rate-limits).\n{% /info %}\n\nTo initialize the admin client, we'll need to first [generated an API key](/docs/advanced/security/api-keys#create-api-key). \nThe API key should have the following scope in order to perform authentication:\n\n| Category {% width=120 %} | Required scopes | Purpose |\n|-----------|---------------------|---------|\n| Sessions | `sessions.write` | Allows API key to create, update, and delete sessions. |\n\n{% multicode %}\n```server-nodejs\nimport { Client } from \"node-appwrite\"; // Using the server SDK\n\nconst adminClient = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject('') // Your project ID\n .setKey(''); // Your secret API key\n```\n```php\nuse Appwrite\\Client;\nuse Appwrite\\Services\\Account;\n\n$adminClient = (new Client())\n ->setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n ->setProject('') // Your project ID\n ->setKey(''); // Your secret API key\n\n\n```\n```server-rust\nuse appwrite::Client;\n\nlet admin_client = Client::new()\n .set_endpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .set_project(\"\") // Your project ID\n .set_key(\"\"); // Your secret API key\n```\n{% /multicode %}\n\nIt 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.\n\n## Session client {% #session-client %}\n\nThe session client will be used to make requests to Appwrite on behalf of the end-user. \nIt will be initialized with the session, usually stored within a cookie.\n\nYou should create a new client for each request and **never** share the client between requests.\n\n{% multicode %}\n```server-nodejs\nconst sessionClient = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n .setProject(''); // Your project ID\n\nconst session = req.cookies.session; // Get the session cookie from the request\nif (session) {\n sessionClient.setSession(session);\n}\n```\n```php\n$sessionClient = (new Client())\n ->setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint\n ->setProject(''); // Your project ID\n\n$session = $_COOKIE['session']; // Get the session cookie from the request\nif ($session) {\n $sessionClient->setSession($session);\n}\n```\n```server-rust\nuse appwrite::Client;\n\nlet session_client = Client::new()\n .set_endpoint(\"https://.cloud.appwrite.io/v1\") // Your API Endpoint\n .set_project(\"\"); // Your project ID\n\n// Get the session cookie from the request\n// This depends on your HTTP framework (e.g. actix-web, axum, rocket)\nlet session = req.cookie(\"a_session_\");\nsession_client.set_session(session);\n```\n{% /multicode %}\n{% /tabsitem %}\n\nYou will use the initialized client in all requests you make to Appwrite.\n{% /tabs %}\n\nIf you're using Appwrite without an SDK, follow the guides for the [REST API](/docs/apis/rest) or [GraphQL API](/docs/apis/graphql).\n{% /section %}\n\n{% section #examples step=4 title=\"Examples\" %}\nIf you prefer to explore examples, follow one of the following quick starts.\n\n## Client apps {% #client-app %}\nExamples when building with Client APIs\n{% cards %}\n{% cards_item href=\"/docs/quick-starts/web\" title=\"Web\" icon=\"icon-nextjs\" %}\nJust plain JavaScript and TypeScript.\n{% /cards_item %}\n{% cards_item href=\"/docs/quick-starts/nextjs\" title=\"Next.js\" icon=\"icon-nextjs\" %}\nGet started with Appwrite and Next.js\n{% /cards_item %}\n{% cards_item href=\"/docs/quick-starts/react\" title=\"React\" icon=\"icon-react\" %}\nGet started with Appwrite and React\n{% /cards_item %}\n{% cards_item href=\"/docs/quick-starts/vue\" title=\"Vue.js\" icon=\"web-icon-vue\" %}\nGet started with Appwrite and Vue.js\n{% /cards_item %}\n{% cards_item href=\"/docs/quick-starts/nuxt\" title=\"Nuxt\" icon=\"web-icon-nuxt\" %}\nGet started with Appwrite and Nuxt\n{% /cards_item %}\n{% cards_item href=\"/docs/quick-starts/sveltekit\" title=\"SvelteKit\" icon=\"icon-svelte\" %}\nGet started with Appwrite and SvelteKit\n{% /cards_item %}\n{% cards_item href=\"/docs/quick-starts/angular\" title=\"Angular\" icon=\"icon-angular\" %}\nGet started with Appwrite and Angular\n{% /cards_item %}\n{% cards_item href=\"/docs/quick-starts/flutter\" title=\"Flutter\" icon=\"icon-flutter\" %}\nGet started with Appwrite and Flutter\n{% /cards_item %}\n{% cards_item href=\"/docs/quick-starts/apple\" title=\"Apple\" icon=\"icon-apple\" %}\nGet started with Appwrite and Apple\n{% /cards_item %}\n{% cards_item href=\"/docs/quick-starts/android\" title=\"Android\" icon=\"icon-android\" %}\nGet started with Appwrite and Android\n{% /cards_item %}\n{% /cards %}\n\n## Server apps {% #server-app %}\nExamples when building with Server APIs\n\n{% cards %}\n{% cards_item href=\"/docs/quick-starts/node\" title=\"Node.js\" icon=\"icon-node_js\" %}\nGet started with Appwrite and Node.js\n{% /cards_item %}\n{% cards_item href=\"/docs/quick-starts/python\" title=\"Python\" icon=\"icon-python\" %}\nGet started with Appwrite and Python\n{% /cards_item %}\n{% cards_item href=\"/docs/quick-starts/dart\" title=\"Dart\" icon=\"icon-dart\" %}\nGet started with Appwrite and Dart\n{% /cards_item %}\n{% cards_item href=\"/docs/quick-starts/php\" title=\"PHP\" icon=\"icon-php\" %}\nGet started with Appwrite and PHP\n{% /cards_item %}\n{% cards_item href=\"/docs/quick-starts/ruby\" title=\"Ruby\" icon=\"icon-ruby\" %}\nGet started with Appwrite and Ruby\n{% /cards_item %}\n{% cards_item href=\"/docs/quick-starts/dotnet\" title=\".NET\" icon=\"icon-dotnet\" %}\nGet started with Appwrite and .NET\n{% /cards_item %}\n{% cards_item href=\"/docs/quick-starts/deno\" title=\"Deno\" icon=\"icon-deno\" %}\nGet started with Appwrite and Deno\n{% /cards_item %}\n{% cards_item href=\"/docs/quick-starts/go\" title=\"Go\" icon=\"icon-go\" %}\nGet started with Appwrite and Go\n{% /cards_item %}\n{% cards_item href=\"/docs/quick-starts/swift\" title=\"Swift\" icon=\"icon-swift\" %}\nGet started with Appwrite and Swift\n{% /cards_item %}\n{% cards_item href=\"/docs/quick-starts/kotlin\" title=\"Kotlin\" icon=\"icon-kotlin\" %}\nGet started with Appwrite and Kotlin\n{% /cards_item %}\n{% /cards %}\n\n\n{% /section %}"}, {"path": "docs/sdks", "title": "SDKs", "description": "Get started with Appwrite SDKs and learn how to use them to add authentication, user management, file storage, and more to your apps.", "content": "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.\n\nWe'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.\n\n# Client {% #client %}\n\nClient 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.\n\n{% table %}\n*   {% width=48 %}\n* Platform\n* GitHub Repository\n*   {% width=80 %}\n---\n* {% only_dark %}{% icon_image src=\"/images/platforms/dark/javascript.svg\" alt=\"Javascript logo\" size=\"m\" /%}{% /only_dark %}\n{% only_light %}{% icon_image src=\"/images/platforms/light/javascript.svg\" alt=\"Javascript logo\" size=\"m\" /%}{% /only_light %}\n* Web SDK `23.0.0`\n* [appwrite/sdk-for-web](https://github.com/appwrite/sdk-for-web)\n* \n---\n* {% only_dark %}{% icon_image src=\"/images/platforms/dark/flutter.svg\" alt=\"Flutter logo\" size=\"m\" /%}{% /only_dark %}\n{% only_light %}{% icon_image src=\"/images/platforms/light/flutter.svg\" alt=\"Flutter logo\" size=\"m\" /%}{% /only_light %}\n* Flutter SDK `22.0.0`\n* [appwrite/sdk-for-flutter](https://github.com/appwrite/sdk-for-flutter)\n* \n---\n* {% only_dark %}{% icon_image src=\"/images/platforms/dark/react.svg\" alt=\"React logo\" size=\"m\" /%}{% /only_dark %}\n{% only_light %}{% icon_image src=\"/images/platforms/light/react.svg\" alt=\"React logo\" size=\"m\" /%}{% /only_light %}\n* React Native SDK `0.25.0`\n* [appwrite/sdk-for-react-native](https://github.com/appwrite/sdk-for-react-native)\n* `beta`\n---\n* {% only_dark %}{% icon_image src=\"/images/platforms/dark/apple.svg\" alt=\"Apple logo\" size=\"m\" /%}{% /only_dark %}\n{% only_light %}{% icon_image src=\"/images/platforms/light/apple.svg\" alt=\"Apple logo\" size=\"m\" /%}{% /only_light %}\n* Apple SDK `15.0.0`\n* [appwrite/sdk-for-apple](https://github.com/appwrite/sdk-for-apple)\n* \n---\n* {% only_dark %}{% icon_image src=\"/images/platforms/dark/android.svg\" alt=\"Android logo\" size=\"m\" /%}{% /only_dark %}\n{% only_light %}{% icon_image src=\"/images/platforms/light/android.svg\" alt=\"Android logo\" size=\"m\" /%}{% /only_light %}\n* Android SDK `13.0.0`\n* [appwrite/sdk-for-android](https://github.com/appwrite/sdk-for-android)\n* \n{% /table %}\n\n# Server {% #server %}\n\nServer 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.\n\n{% table %}\n*   {% width=48 %}\n* Platform\n* GitHub Repository\n*   {% width=80 %}\n---\n* {% only_dark %}{% icon_image src=\"/images/platforms/dark/node.svg\" alt=\"Node.js logo\" size=\"m\" /%}{% /only_dark %}\n{% only_light %}{% icon_image src=\"/images/platforms/light/node.svg\" alt=\"Node.js logo\" size=\"m\" /%}{% /only_light %}\n* Node.js SDK `22.1.3`\n* [appwrite/sdk-for-node](https://github.com/appwrite/sdk-for-node)\n* \n---\n* {% only_dark %}{% icon_image src=\"/images/platforms/dark/python.svg\" alt=\"Python logo\" size=\"m\" /%}{% /only_dark %}\n{% only_light %}{% icon_image src=\"/images/platforms/light/python.svg\" alt=\"Python logo\" size=\"m\" /%}{% /only_light %}\n* Python SDK `16.0.0`\n* [appwrite/sdk-for-python](https://github.com/appwrite/sdk-for-python)\n* \n---\n* {% only_dark %}{% icon_image src=\"/images/platforms/dark/dart.svg\" alt=\"Dart logo\" size=\"m\" /%}{% /only_dark %}\n{% only_light %}{% icon_image src=\"/images/platforms/light/dart.svg\" alt=\"Dart logo\" size=\"m\" /%}{% /only_light %}\n* Dart SDK `21.3.0`\n* [appwrite/sdk-for-dart](https://github.com/appwrite/sdk-for-dart)\n* \n---\n* {% only_dark %}{% icon_image src=\"/images/platforms/dark/php.svg\" alt=\"PHP logo\" size=\"m\" /%}{% /only_dark %}\n{% only_light %}{% icon_image src=\"/images/platforms/light/php.svg\" alt=\"PHP logo\" size=\"m\" /%}{% /only_light %}\n* PHP SDK `20.2.1`\n* [appwrite/sdk-for-php](https://github.com/appwrite/sdk-for-php)\n* \n---\n* {% only_dark %}{% icon_image src=\"/images/platforms/dark/ruby.svg\" alt=\"Ruby logo\" size=\"m\" /%}{% /only_dark %}\n{% only_light %}{% icon_image src=\"/images/platforms/light/ruby.svg\" alt=\"Ruby logo\" size=\"m\" /%}{% /only_light %}\n* Ruby SDK `21.1.0`\n* [appwrite/sdk-for-ruby](https://github.com/appwrite/sdk-for-ruby)\n* \n---\n* {% only_dark %}{% icon_image src=\"/images/platforms/dark/dotnet.svg\" alt=\".NET logo\" size=\"m\" /%}{% /only_dark %}\n{% only_light %}{% icon_image src=\"/images/platforms/light/dotnet.svg\" alt=\".NET logo\" size=\"m\" /%}{% /only_light %}\n* .NET SDK `1.0.0`\n* [appwrite/sdk-for-dotnet](https://github.com/appwrite/sdk-for-dotnet)\n* \n---\n* {% only_dark %}{% icon_image src=\"/images/platforms/dark/go.svg\" alt=\"Go logo\" size=\"m\" /%}{% /only_dark %}\n{% only_light %}{% icon_image src=\"/images/platforms/light/go.svg\" alt=\"Go logo\" size=\"m\" /%}{% /only_light %}\n* Go SDK `1.0.0`\n* [appwrite/sdk-for-go](https://github.com/appwrite/sdk-for-go)\n* \n---\n* {% only_dark %}{% icon_image src=\"/images/platforms/dark/swift.svg\" alt=\"Swift logo\" size=\"m\" /%}{% /only_dark %}\n{% only_light %}{% icon_image src=\"/images/platforms/light/swift.svg\" alt=\"Swift logo\" size=\"m\" /%}{% /only_light %}\n* Swift SDK `15.2.0`\n* [appwrite/sdk-for-swift](https://github.com/appwrite/sdk-for-swift)\n* \n---\n* {% only_dark %}{% icon_image src=\"/images/platforms/dark/kotlin.svg\" alt=\"Kotlin logo\" size=\"m\" /%}{% /only_dark %}\n{% only_light %}{% icon_image src=\"/images/platforms/light/kotlin.svg\" alt=\"Kotlin logo\" size=\"m\" /%}{% /only_light %}\n* Kotlin SDK `14.1.0`\n* [appwrite/sdk-for-kotlin](https://github.com/appwrite/sdk-for-kotlin)\n*\n---\n* {% only_dark %}{% icon_image src=\"/images/platforms/dark/rust.svg\" alt=\"Rust logo\" size=\"m\" /%}{% /only_dark %}\n{% only_light %}{% icon_image src=\"/images/platforms/light/rust.svg\" alt=\"Rust logo\" size=\"m\" /%}{% /only_light %}\n* Rust SDK `0.2.0`\n* [appwrite/sdk-for-rust](https://github.com/appwrite/sdk-for-rust)\n*\n{% /table %}\n\nIf 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.\n\n# Protocols {% #protocols %}\nWe 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.\n\nAppwrite 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:\n\n{% cards %}\n{% cards_item href=\"/docs/apis/realtime\" title=\"Realtime API\" %}\nIntegrate with the Appwrite Realtime API\n{% /cards_item %}\n\n{% cards_item href=\"/docs/apis/rest\" title=\"REST API\" %}\nIntegrate with the Appwrite REST API\n{% /cards_item %}\n\n{% cards_item href=\"/docs/apis/graphql\" title=\"GraphQL API\" %}\nIntegrate with the Appwrite GraphQL API\n{% /cards_item %}\n{% /cards %}\n\n# Utility classes {% #utility-classes %}\nAppwrite's SDKs provide useful utility classes to make your development experience easier.\nUse these classes and methods to reduce guess work and get better code suggestions in your IDE.\n\n## IDs {% #ids %}\nAppwrite has utility classes to help you handle IDs. \nThese generate the correct ID format to be passed to the Appwrite APIs.\n\n{% tabs %}\n{% tabsitem #client title=\"Client SDKs\" %}\n{% multicode %}\n```client-web\nimport { Client, Account } from \"appwrite\";\n\n// Generate a unique ID\nID.unique()\n\n// Generate a custom ID\nID.custom(\"my-custom-id\")\n```\n```client-flutter\nimport 'package:appwrite/appwrite.dart';\n\n// Generate a unique ID\nID.unique()\n\n// Generate a custom ID\nID.custom()\n```\n```client-apple\nimport Appwrite\n\n// Generate a unique ID\nID.unique()\n\n// Generate a custom ID\nID.custom(\"my-custom-id\")\n```\n```client-android-kotlin\nimport io.appwrite.ID\n\n// Generate a unique ID\nID.unique()\n\n// Generate a custom ID\nID.custom(\"my-custom-id\")\n```\n{% /multicode %}\n{% /tabsitem %}\n\n{% tabsitem #server title=\"Server SDKs\" %}\n{% multicode %}\n```server-nodejs\nconst sdk = require('node-appwrite');\n\n// Generate a unique ID\nsdk.ID.unique()\n\n// Generate a custom ID\nsdk.ID.custom(\"my-custom-id\")\n\n```\n```deno\nimport * as sdk from \"npm:node-appwrite\";\n// Generate a unique ID\nsdk.ID.unique()\n\n// Generate a custom ID\nsdk.ID.custom(\"my-custom-id\")\n```\n```php\n`.\n\n## Enums {% #enums %}\nAppwrite has enumeration classes for predefined strings used different parameters used for the Appwrite APIs.\n\nThese enums are available for authenticator type, name, OAuth provider, \npassword hash types, browsers, authentication factors, index types, credit cards, \nimage gravity, image format, relationship types, SMTP encryption, Function runtime, \nmessaging provider type, compression algorithms, execution methods, and country flags.\n{% tabs %}\n{% tabsitem #client title=\"Client SDKs\" %}\nYou can discover the available enums in each SDK at the source.\n\n{% table %}\n*   {% width=48 %}\n* Platform\n* Enums\n*   {% width=80 %}\n\n---\n* {% only_dark %}{% icon_image src=\"/images/platforms/dark/javascript.svg\" alt=\"Javascript logo\" size=\"m\" /%}{% /only_dark %}\n{% only_light %}{% icon_image src=\"/images/platforms/light/javascript.svg\" alt=\"Javascript logo\" size=\"m\" /%}{% /only_light %}\n* Web SDK `23.0.0`\n* [appwrite/sdk-for-web](https://github.com/appwrite/sdk-for-web/tree/dev/src/enums)\n*\n---\n* {% only_dark %}{% icon_image src=\"/images/platforms/dark/flutter.svg\" alt=\"Flutter logo\" size=\"m\" /%}{% /only_dark %}\n{% only_light %}{% icon_image src=\"/images/platforms/light/flutter.svg\" alt=\"Flutter logo\" size=\"m\" /%}{% /only_light %}\n* Flutter SDK `22.0.0`\n* [appwrite/sdk-for-flutter](https://github.com/appwrite/sdk-for-flutter/tree/dev/lib/src/enums)\n*\n---\n* {% only_dark %}{% icon_image src=\"/images/platforms/dark/react.svg\" alt=\"React logo\" size=\"m\" /%}{% /only_dark %}\n{% only_light %}{% icon_image src=\"/images/platforms/light/react.svg\" alt=\"React logo\" size=\"m\" /%}{% /only_light %}\n* React Native SDK `0.25.0`\n* [appwrite/sdk-for-react-native](https://github.com/appwrite/sdk-for-react-native)\n*\n---\n* {% only_dark %}{% icon_image src=\"/images/platforms/dark/apple.svg\" alt=\"Apple logo\" size=\"m\" /%}{% /only_dark %}\n{% only_light %}{% icon_image src=\"/images/platforms/light/apple.svg\" alt=\"Apple logo\" size=\"m\" /%}{% /only_light %}\n* Apple SDK `15.0.0`\n* [appwrite/sdk-for-apple](https://github.com/appwrite/sdk-for-apple/tree/dev/Sources/AppwriteEnums)\n*\n---\n* {% only_dark %}{% icon_image src=\"/images/platforms/dark/android.svg\" alt=\"Android logo\" size=\"m\" /%}{% /only_dark %}\n{% only_light %}{% icon_image src=\"/images/platforms/light/android.svg\" alt=\"Android logo\" size=\"m\" /%}{% /only_light %}\n* Android SDK `13.0.0`\n* [appwrite/sdk-for-android](https://github.com/appwrite/sdk-for-android/tree/dev/library/src/main/java/io/appwrite/enums)\n*\n{% /table %}\n{% /tabsitem %}\n\n{% tabsitem #server title=\"Server SDKs\" %}\nYou can discover the available enums in each SDK at the source.\n\n{% table %}\n*   {% width=48 %}\n* Platform\n* Enums\n*   {% width=80 %}\n---\n* {% only_dark %}{% icon_image src=\"/images/platforms/dark/node.svg\" alt=\"Node.js logo\" size=\"m\" /%}{% /only_dark %}\n{% only_light %}{% icon_image src=\"/images/platforms/light/node.svg\" alt=\"Node.js logo\" size=\"m\" /%}{% /only_light %}\n* Node.js SDK `22.1.3`\n* [appwrite/sdk-for-node](https://github.com/appwrite/sdk-for-node/tree/dev/lib/enums)\n*\n---\n* {% only_dark %}{% icon_image src=\"/images/platforms/dark/python.svg\" alt=\"Python logo\" size=\"m\" /%}{% /only_dark %}\n{% only_light %}{% icon_image src=\"/images/platforms/light/python.svg\" alt=\"Python logo\" size=\"m\" /%}{% /only_light %}\n* Python SDK `16.0.0`\n* [appwrite/sdk-for-python](https://github.com/appwrite/sdk-for-python/tree/dev/appwrite/enums)\n*\n---\n* {% only_dark %}{% icon_image src=\"/images/platforms/dark/dart.svg\" alt=\"Dart logo\" size=\"m\" /%}{% /only_dark %}\n{% only_light %}{% icon_image src=\"/images/platforms/light/dart.svg\" alt=\"Dart logo\" size=\"m\" /%}{% /only_light %}\n* Dart SDK `21.3.0`\n* [appwrite/sdk-for-dart](https://github.com/appwrite/sdk-for-dart/blob/dev/lib/enums.dart)\n*\n---\n* {% only_dark %}{% icon_image src=\"/images/platforms/dark/php.svg\" alt=\"PHP logo\" size=\"m\" /%}{% /only_dark %}\n{% only_light %}{% icon_image src=\"/images/platforms/light/php.svg\" alt=\"PHP logo\" size=\"m\" /%}{% /only_light %}\n* PHP SDK `20.2.1`\n* [appwrite/sdk-for-php](https://github.com/appwrite/sdk-for-php/tree/dev/src/Appwrite/Enums)\n*\n---\n* {% only_dark %}{% icon_image src=\"/images/platforms/dark/ruby.svg\" alt=\"Ruby logo\" size=\"m\" /%}{% /only_dark %}\n{% only_light %}{% icon_image src=\"/images/platforms/light/ruby.svg\" alt=\"Ruby logo\" size=\"m\" /%}{% /only_light %}\n* Ruby SDK `21.1.0`\n* [appwrite/sdk-for-ruby](https://github.com/appwrite/sdk-for-ruby/tree/dev/lib/appwrite/enums)\n*\n---\n* {% only_dark %}{% icon_image src=\"/images/platforms/dark/dotnet.svg\" alt=\".NET logo\" size=\"m\" /%}{% /only_dark %}\n{% only_light %}{% icon_image src=\"/images/platforms/light/dotnet.svg\" alt=\".NET logo\" size=\"m\" /%}{% /only_light %}\n* .NET SDK `1.0.0`\n* [appwrite/sdk-for-dotnet](https://github.com/appwrite/sdk-for-dotnet/tree/dev/src/Appwrite/Enums)\n*\n---\n* {% only_dark %}{% icon_image src=\"/images/platforms/dark/go.svg\" alt=\"Go logo\" size=\"m\" /%}{% /only_dark %}\n{% only_light %}{% icon_image src=\"/images/platforms/light/go.svg\" alt=\"Go logo\" size=\"m\" /%}{% /only_light %}\n* Go SDK `1.0.0`\n* [appwrite/sdk-for-go](https://github.com/appwrite/sdk-for-go)\n*\n---\n* {% only_dark %}{% icon_image src=\"/images/platforms/dark/swift.svg\" alt=\"Swift logo\" size=\"m\" /%}{% /only_dark %}\n{% only_light %}{% icon_image src=\"/images/platforms/light/swift.svg\" alt=\"Swift logo\" size=\"m\" /%}{% /only_light %}\n* Swift SDK `15.2.0`\n* [appwrite/sdk-for-swift](https://github.com/appwrite/sdk-for-swift/tree/dev/Sources/AppwriteEnums)\n*\n---\n* {% only_dark %}{% icon_image src=\"/images/platforms/dark/kotlin.svg\" alt=\"Kotlin logo\" size=\"m\" /%}{% /only_dark %}\n{% only_light %}{% icon_image src=\"/images/platforms/light/kotlin.svg\" alt=\"Kotlin logo\" size=\"m\" /%}{% /only_light %}\n* Kotlin SDK `14.1.0`\n* [appwrite/sdk-for-kotlin](https://github.com/appwrite/sdk-for-kotlin/tree/dev/src/main/kotlin/io/appwrite/enums)\n*\n---\n* {% only_dark %}{% icon_image src=\"/images/platforms/dark/rust.svg\" alt=\"Rust logo\" size=\"m\" /%}{% /only_dark %}\n{% only_light %}{% icon_image src=\"/images/platforms/light/rust.svg\" alt=\"Rust logo\" size=\"m\" /%}{% /only_light %}\n* Rust SDK `0.2.0`\n* [appwrite/sdk-for-rust](https://github.com/appwrite/sdk-for-rust/tree/dev/src/enums)\n*\n{% /table %}\n{% /tabsitem %}\n{% /tabs %}\n\n## Queries and permissions {% #queries-and-permissions %}\nAppwrite has utility classes for queries and permissions. \nYou can learn more about [query utility classes](/docs/products/databases/queries) and [permissions utility classes](/docs/advanced/security/permissions) in their own pages.\n\n## File I/O {% #file-io %}\nDepending on your platform, you will also need some helpers to interface with system I/O.\nLearn more about [storage input file classes](/docs/products/storage/upload-download#input-file).\n\n# Community {% #community %}\nIf 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).\n\nIf 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)."}, {"path": "docs/tooling/ai", "title": "AI", "description": "Discover Appwrite's AI tooling ecosystem. Build with AI-powered development tools, integrate AI capabilities into your apps, and leverage documentation designed for AI consumption.", "content": "Appwrite provides a comprehensive set of tools and resources to help you build with AI, from AI-powered development tools that accelerate your workflow to infrastructure for building AI-powered applications.\n\n# IDEs {% #ides %}\n\nAI-powered IDEs and code editors provide intelligent code completion and context-aware assistance as you write code. These tools support our MCP servers, giving AI agents direct access to your Appwrite project.\n\n{% only_light %}\n{% cards %}\n\n{% cards_item href=\"/docs/tooling/ai/agents/claude-code\" title=\"Claude Code\" image=\"/images/docs/mcp/logos/claude.svg\" %}\n{% /cards_item %}\n\n{% cards_item href=\"/docs/tooling/ai/agents/codex\" title=\"Codex\" image=\"/images/docs/mcp/logos/openai.svg\" %}\n{% /cards_item %}\n\n{% cards_item href=\"/docs/tooling/ai/agents/cursor\" title=\"Cursor\" image=\"/images/docs/mcp/logos/cursor-ai.svg\" %}\n{% /cards_item %}\n\n{% cards_item href=\"/docs/tooling/ai/agents/vscode\" title=\"VS Code\" image=\"/images/docs/mcp/logos/vscode.svg\" %}\n{% /cards_item %}\n\n{% cards_item href=\"/docs/tooling/ai/agents/zed\" title=\"Zed\" image=\"/images/docs/mcp/logos/zed.svg\" %}\n{% /cards_item %}\n\n{% cards_item href=\"/docs/tooling/ai/agents/opencode\" title=\"OpenCode\" image=\"/images/docs/mcp/logos/opencode.svg\" %}\n{% /cards_item %}\n\n{% cards_item href=\"/docs/tooling/ai/agents/antigravity\" title=\"Antigravity\" image=\"/images/docs/mcp/logos/google-antigravity.svg\" %}\n{% /cards_item %}\n\n{% /cards %}\n{% /only_light %}\n\n{% only_dark %}\n{% cards %}\n\n{% cards_item href=\"/docs/tooling/ai/agents/claude-code\" title=\"Claude Code\" image=\"/images/docs/mcp/logos/dark/claude.svg\" %}\n{% /cards_item %}\n\n{% cards_item href=\"/docs/tooling/ai/agents/codex\" title=\"Codex\" image=\"/images/docs/mcp/logos/dark/openai.svg\" %}\n{% /cards_item %}\n\n{% cards_item href=\"/docs/tooling/ai/agents/cursor\" title=\"Cursor\" image=\"/images/docs/mcp/logos/dark/cursor-ai.svg\" %}\n{% /cards_item %}\n\n{% cards_item href=\"/docs/tooling/ai/agents/vscode\" title=\"VS Code\" image=\"/images/docs/mcp/logos/dark/vscode.svg\" %}\n{% /cards_item %}\n\n{% cards_item href=\"/docs/tooling/ai/agents/zed\" title=\"Zed\" image=\"/images/docs/mcp/logos/dark/zed.svg\" %}\n{% /cards_item %}\n\n{% cards_item href=\"/docs/tooling/ai/agents/opencode\" title=\"OpenCode\" image=\"/images/docs/mcp/logos/dark/opencode.svg\" %}\n{% /cards_item %}\n\n{% cards_item href=\"/docs/tooling/ai/agents/antigravity\" title=\"Antigravity\" image=\"/images/docs/mcp/logos/dark/google-antigravity.svg\" %}\n{% /cards_item %}\n\n{% /cards %}\n{% /only_dark %}\n\n# Vibe coding {% #vibe-coding %}\n\nVibe coding platforms let you build applications through natural language. Describe what you want to build and the AI generates the application for you.\n\n{% only_light %}\n{% cards %}\n\n{% cards_item href=\"/docs/tooling/ai/vibe-coding/claude-desktop\" title=\"Claude Desktop\" image=\"/images/docs/mcp/logos/claude.svg\" %}\n{% /cards_item %}\n\n{% cards_item href=\"/docs/tooling/ai/vibe-coding/lovable\" title=\"Lovable\" image=\"/images/docs/mcp/logos/lovable.svg\" %}\n{% /cards_item %}\n\n{% cards_item href=\"/docs/tooling/ai/vibe-coding/emergent\" title=\"Emergent\" image=\"/images/docs/mcp/logos/emergent.svg\" %}\n{% /cards_item %}\n\n{% cards_item href=\"/docs/tooling/ai/vibe-coding/bolt\" title=\"Bolt\" image=\"/images/docs/mcp/logos/bolt.svg\" %}\n{% /cards_item %}\n\n{% cards_item href=\"/docs/tooling/ai/vibe-coding/zenflow\" title=\"Zenflow\" image=\"/images/docs/mcp/logos/zenflow.svg\" %}\n{% /cards_item %}\n\n{% /cards %}\n{% /only_light %}\n\n{% only_dark %}\n{% cards %}\n\n{% cards_item href=\"/docs/tooling/ai/vibe-coding/claude-desktop\" title=\"Claude Desktop\" image=\"/images/docs/mcp/logos/dark/claude.svg\" %}\n{% /cards_item %}\n\n{% cards_item href=\"/docs/tooling/ai/vibe-coding/lovable\" title=\"Lovable\" image=\"/images/docs/mcp/logos/dark/lovable.svg\" %}\n{% /cards_item %}\n\n{% cards_item href=\"/docs/tooling/ai/vibe-coding/emergent\" title=\"Emergent\" image=\"/images/docs/mcp/logos/dark/emergent.svg\" %}\n{% /cards_item %}\n\n{% cards_item href=\"/docs/tooling/ai/vibe-coding/bolt\" title=\"Bolt\" image=\"/images/docs/mcp/logos/dark/bolt.svg\" %}\n{% /cards_item %}\n\n{% cards_item href=\"/docs/tooling/ai/vibe-coding/zenflow\" title=\"Zenflow\" image=\"/images/docs/mcp/logos/dark/zenflow.svg\" %}\n{% /cards_item %}\n\n{% /cards %}\n{% /only_dark %}\n\n# Tooling {% #tooling %}\n\nThese resources help AI tools understand and work with Appwrite. Use them alongside your IDE or agent to get faster, more accurate results.\n\n{% cards %}\n{% cards_item href=\"/docs/tooling/ai/quickstart-prompts\" title=\"Quickstart prompts\" %}\nPre-built prompts to quickly generate your first Appwrite project with AI assistants.\n{% /cards_item %}\n{% cards_item href=\"/docs/tooling/ai/mcp-servers/api\" title=\"MCP servers\" %}\nGive AI tools direct access to your Appwrite project and docs.\n{% /cards_item %}\n{% cards_item href=\"/docs/tooling/ai/agents-md\" title=\"AGENTS.md\" %}\nInstruction files that help AI agents understand your project's structure and conventions.\n{% /cards_item %}\n{% cards_item href=\"/docs/tooling/ai/skills\" title=\"Agent skills\" %}\nPre-built Appwrite knowledge for AI agents across supported languages and SDKs.\n{% /cards_item %}\n{% cards_item href=\"/docs/tooling/ai/assistant\" title=\"Assistant\" %}\nAI-powered assistant in Appwrite Cloud for troubleshooting, code generation, and searching docs.\n{% /cards_item %}\n{% cards_item href=\"/docs/tooling/ai/arena\" title=\"Appwrite Arena\" %}\nOpen-source benchmark evaluating how well AI models understand Appwrite's APIs and SDKs.\n{% /cards_item %}\n{% /cards %}\n\n# Journeys {% #journeys %}\n\nGuides for building AI-powered features on top of Appwrite, from running models in Functions to building full agent pipelines.\n\n{% cards %}\n{% cards_item href=\"/docs/tooling/ai/ai-in-functions\" title=\"AI in Functions\" %}\nRun AI models inside Appwrite Functions with providers like OpenAI and Anthropic.\n{% /cards_item %}\n{% cards_item href=\"/docs/tooling/ai/vector-db-and-embeddings\" title=\"Vector DB and embeddings\" %}\nStore and query vector embeddings for semantic search and RAG pipelines.\n{% /cards_item %}\n{% cards_item href=\"/docs/tooling/ai/persistent-agents-with-realtime\" title=\"Persistent agents\" %}\nBuild stateful AI agents with continuous conversations using Appwrite Realtime.\n{% /cards_item %}\n{% /cards %}"}, {"path": "docs/tooling/ai/agents-md", "title": "AGENTS.md", "description": "Generate an AGENTS.md file to give AI agents project-specific context about Appwrite SDKs, APIs, and services.", "content": "`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.\n\nWhen 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.\n\n# Benefits {% #benefits %}\n\n- **Project-specific context**: AI agents receive tailored instructions for your exact Appwrite setup, including which SDKs, services, and frameworks you use.\n- **Accurate code generation**: With the right context, AI agents generate code that follows your project's patterns and uses the correct SDK methods.\n- **No repetitive prompting**: Instead of explaining your setup in every conversation, the `AGENTS.md` file provides this context automatically.\n- **Framework-aware**: Generated rules include framework-specific patterns like SSR authentication, server actions, and routing conventions.\n- **API references included**: The generated file links directly to the relevant Appwrite API references for your selected services.\n\n# Generate AGENTS.md {% #generate %}\n\nUse the [AGENTS.md generator](https://agentsmd.appwrite.io) to create a custom `AGENTS.md` file for your project.\n\n![AGENTS.md generator](/images/docs/ai/agents-md/generator-dark.avif)\n\n{% section #step-1 step=1 title=\"Select your SDK\" %}\nChoose the Appwrite SDK you use in your project. The generator supports all Appwrite client and server SDKs.\n{% /section %}\n\n{% section #step-2 step=2 title=\"Select your framework\" %}\nChoose the framework you are building with. The generated rules will include framework-specific patterns and best practices.\n{% /section %}\n\n{% section #step-3 step=3 title=\"Select Appwrite features\" %}\nCheck 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.\n{% /section %}\n\n{% section #step-4 step=4 title=\"Generate and download\" %}\nClick **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.\n{% /section %}"}, {"path": "docs/tooling/ai/agents/antigravity", "title": "Google Antigravity", "description": "Learn how you can add the Appwrite MCP servers to Agent Manager in Google Antigravity to interact with both the Appwrite API and documentation.", "content": "{% section #quick-start-prompts step=1 title=\"Quick start prompts\" %}\n\nGet started quickly with these pre-built prompts for common Appwrite integrations:\n\n{% cards %}\n{% cards_item href=\"/docs/tooling/ai/quickstart-prompts/nextjs\" title=\"Next.js\" icon=\"icon-nextjs\" %}\n{% /cards_item %}\n{% cards_item href=\"/docs/tooling/ai/quickstart-prompts/tanstack-start\" title=\"TanStack Start\" icon=\"web-icon-tanstack\" %}\n{% /cards_item %}\n{% /cards %}\n\n{% arrow_link href=\"/docs/tooling/ai/quickstart-prompts\" %}\nBrowse all quick start prompts\n{% /arrow_link %}\n\n{% /section %}\n\n{% section #step-2 step=2 title=\"Add MCP servers\" %}\n\nConnect Appwrite MCP servers to Antigravity for deeper integration with the Appwrite API and documentation.\n\nBefore you begin, ensure you have the following **pre-requisites** installed on your system:\n\n{% tabs %}\n{% tabsitem #api-server-prerequisites title=\"API server\" %}\n\n[uv](https://docs.astral.sh/uv/getting-started/installation/) must be installed on your system.\n\n{% /tabsitem %}\n\n{% tabsitem #docs-server-prerequisites title=\"Docs server\" %}\n\n[Node.js](https://nodejs.org/en/download) and npm must be installed on your system.\n\n{% /tabsitem %}\n{% /tabs %}\n\nTo 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.\n\n{% tabs %}\n{% tabsitem #api-only title=\"API server\" %}\n\nUpdate the `mcp_config.json` file to include the API server:\n\n```json\n{\n \"mcpServers\": {\n \"appwrite-api\": {\n \"command\": \"uvx\",\n \"args\": [\n \"mcp-server-appwrite\"\n ],\n \"env\": {\n \"APPWRITE_PROJECT_ID\": \"your-project-id\",\n \"APPWRITE_API_KEY\": \"your-api-key\",\n \"APPWRITE_ENDPOINT\": \"https://.cloud.appwrite.io/v1\"\n }\n }\n }\n}\n\n```\n\n**Configuration:**\n\n- Replace `your-project-id` with your actual Appwrite project ID\n- Replace `your-api-key` with your Appwrite API key\n- Replace `` with your Appwrite Cloud region (e.g., `nyc`, `fra`)\n\n{% /tabsitem %}\n\n{% tabsitem #docs-only title=\"Docs server\" %}\n\nUpdate the `mcp_config.json` file to include the docs server:\n\n```json\n{\n \"mcpServers\": {\n \"appwrite-docs\": {\n \"command\": \"npx\",\n \"args\": [\n \"mcp-remote\",\n \"\"\n ]\n }\n }\n}\n\n```\n\n{% /tabsitem %}\n{% /tabs %}\n\nHead back to the Managed MCP Server page and click refresh.\n\n{% /section %}\n\n{% section #step-3 step=3 title=\"Test the integration\" %}\n\nOpen **Agent Manager** in Antigravity to test your MCP integrations. You can try out the following example prompts based on the MCP server you have configured:\n\n{% tabs %}\n{% tabsitem #test-api title=\"API server\" %}\n\n**Example prompts:**\n\n- `Create a new user in my Appwrite project`\n- `List all databases in my project`\n- `Show me the collections in my database`\n- `Create a new document in my collection`\n- `Delete a specific user by ID`\n\n{% /tabsitem %}\n\n{% tabsitem #test-docs title=\"Docs server\" %}\n\n**Example prompts:**\n\n- `How do I set up real-time subscriptions in Appwrite?`\n- `Show me how to authenticate users with OAuth`\n- `What are the best practices for database queries?`\n- `How do I implement file uploads with Appwrite Storage?`\n- `Show me an example of using Appwrite Functions`\n\n{% /tabsitem %}\n\n{% /tabs %}\n\n![Antigravity Agent chat](/images/docs/mcp/antigravity/agent-chat.avif)\n\n{% /section %}"}, {"path": "docs/tooling/ai/agents/claude-code", "title": "Claude Code", "description": "Learn how to use Claude Code with Appwrite through the Appwrite plugin, quick start prompts, and MCP servers for AI-assisted development.", "content": "{% section #install-plugin step=1 title=\"Install the Appwrite plugin\" %}\n\nThe 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 and sets up MCP servers for both the Appwrite API and documentation, giving Claude Code everything it needs to work with your Appwrite projects.\n\nTo install the plugin, run the following command in your terminal:\n\n```bash\nclaude plugin install appwrite@claude-plugins-official\n```\n\nOnce installed, run Claude Code and configure the plugin:\n\n- Run `/plugins` in Claude Code.\n- Go to the **Installed** tab.\n- Select the **Appwrite** plugin from the list.\n- Choose **Configure options**.\n- Enter your Appwrite endpoint, project ID, and API key when prompted.\n\n![Configure the Appwrite plugin in Claude Code](/images/docs/mcp/claude-code/configure-plugin.avif)\n\nAfter saving the configuration, run `/reload-plugins` to apply the changes to your current session. The `appwrite-api` MCP server will be ready alongside the agent skills, and Claude Code can now interact with your Appwrite project.\n\n{% info title=\"Prefer manual setup?\" %}\n\nIf you'd rather configure MCP servers individually, skip to [Step 3](#step-3).\n\n{% /info %}\n\n{% /section %}\n\n{% section #quick-start-prompts step=2 title=\"Quick start prompts\" %}\n\nGet started quickly with these pre-built prompts for common Appwrite integrations:\n\n{% cards %}\n{% cards_item href=\"/docs/tooling/ai/quickstart-prompts/nextjs\" title=\"Next.js\" icon=\"icon-nextjs\" %}\n{% /cards_item %}\n{% cards_item href=\"/docs/tooling/ai/quickstart-prompts/tanstack-start\" title=\"TanStack Start\" icon=\"web-icon-tanstack\" %}\n{% /cards_item %}\n{% /cards %}\n\n{% arrow_link href=\"/docs/tooling/ai/quickstart-prompts\" %}\nBrowse all quick start prompts\n{% /arrow_link %}\n\n{% /section %}\n\n{% section #step-3 step=3 title=\"Add MCP servers manually\" %}\n\nIf you installed the Appwrite plugin in [Step 1](#install-plugin), MCP servers are already configured and you can skip to [Step 4](#step-4).\n\nConnect Appwrite MCP servers to Claude Code for deeper integration with the Appwrite API and documentation.\n\nBefore you begin, ensure you have the following **pre-requisites** installed on your system:\n\n{% tabs %}\n{% tabsitem #api-server-prerequisites title=\"API server\" %}\n\n[uv](https://docs.astral.sh/uv/getting-started/installation/) must be installed on your system.\n\n{% /tabsitem %}\n\n{% tabsitem #docs-server-prerequisites title=\"Docs server\" %}\n\n[Node.js](https://nodejs.org/en/download) and npm must be installed on your system.\n\n{% /tabsitem %}\n{% /tabs %}\n\nRun the following commands in your terminal to add the MCP servers:\n\n{% tabs %}\n{% tabsitem #api-only title=\"API server\" %}\n\n```bash\nclaude mcp add-json appwrite-api '{\"command\":\"uvx\",\"args\":[\"mcp-server-appwrite\"],\"env\":{\"APPWRITE_PROJECT_ID\": \"your-project-id\", \"APPWRITE_API_KEY\": \"your-api-key\", \"APPWRITE_ENDPOINT\": \"https://.cloud.appwrite.io/v1\"}}'\n\n```\n\n{% /tabsitem %}\n\n{% tabsitem #docs-only title=\"Docs server\" %}\n\n```bash\nclaude mcp add appwrite-docs -t http\n\n```\n\n{% /tabsitem %}\n{% /tabs %}\n\n{% /section %}\n\n{% section #step-4 step=4 title=\"Verify MCP tools\" %}\n\nRun the following command in your terminal (where Claude Code is running).\n\n```bash\n/mcp\n\n```\n\nYou should see the added MCP servers listed there.\n\n![Verify MCP tools](/images/docs/mcp/claude-code/verify-mcp-tools.avif)\n\n{% /section %}\n\n{% section #step-5 step=5 title=\"Test the integration\" %}\n\nTry out the following example prompts based on the MCP server you have configured:\n\n{% tabs %}\n{% tabsitem #test-api title=\"API server\" %}\n\n**Example prompts:**\n\n- `Create a new user in my Appwrite project`\n- `List all databases in my project`\n- `Show me the collections in my database`\n- `Create a new document in my collection`\n- `Delete a specific user by ID`\n\n{% /tabsitem %}\n\n{% tabsitem #test-docs title=\"Docs server\" %}\n\n**Example prompts:**\n\n- `How do I set up real-time subscriptions in Appwrite?`\n- `Show me how to authenticate users with OAuth`\n- `What are the best practices for database queries?`\n- `How do I implement file uploads with Appwrite Storage?`\n- `Show me an example of using Appwrite Functions`\n\n{% /tabsitem %}\n\n{% /tabs %}\n\n![Implement file uploads](/images/docs/mcp/claude-code/implement-file-uploads.avif)\n\n{% /section %}"}, {"path": "docs/tooling/ai/agents/codex", "title": "Codex", "description": "Learn how to use Codex with Appwrite through the Appwrite plugin, quick start prompts, and MCP servers for AI-assisted development.", "content": "{% section #install-plugin step=1 title=\"Install the Appwrite plugin\" %}\n\nThe 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 and registers the Appwrite Docs MCP server, giving Codex access to the Appwrite documentation so that it follows the latest and suggested code patterns.\n\nAdd the Appwrite marketplace to Codex by running the following command in your terminal:\n\n```bash\ncodex plugin marketplace add appwrite/codex-plugin\n```\n\nThen run `codex` and open the plugins menu to install the Appwrite plugin:\n\n- Run `/plugins` in Codex.\n- Select the **Appwrite** plugin from the marketplace listing.\n- Confirm the install.\n\nOnce installed, the language and deployment skills load automatically based on your task, and the `appwrite-docs` MCP server is registered for documentation lookups.\n\nThe Appwrite API MCP server isn't bundled in the plugin: it needs your endpoint, project ID, and API key, and Codex plugins don't currently expose a way to prompt for per-install configuration in the CLI. Add it manually in [Step 3](#step-3).\n\n{% info title=\"Prefer manual setup?\" %}\n\nIf you'd rather configure MCP servers individually, skip to [Step 3](#step-3).\n\n{% /info %}\n\n{% /section %}\n\n{% section #quick-start-prompts step=2 title=\"Quick start prompts\" %}\n\nGet started quickly with these pre-built prompts for common Appwrite integrations:\n\n{% cards %}\n{% cards_item href=\"/docs/tooling/ai/quickstart-prompts/nextjs\" title=\"Next.js\" icon=\"icon-nextjs\" %}\n{% /cards_item %}\n{% cards_item href=\"/docs/tooling/ai/quickstart-prompts/tanstack-start\" title=\"TanStack Start\" icon=\"web-icon-tanstack\" %}\n{% /cards_item %}\n{% /cards %}\n\n{% arrow_link href=\"/docs/tooling/ai/quickstart-prompts\" %}\nBrowse all quick start prompts\n{% /arrow_link %}\n\n{% /section %}\n\n{% section #step-3 step=3 title=\"Add MCP servers manually\" %}\n\nIf you installed the Appwrite plugin in [Step 1](#install-plugin), the docs MCP server is already configured and you can skip to [Step 4](#step-4).\n\nConnect Appwrite MCP servers to Codex for deeper integration with the Appwrite API and documentation.\n\nBefore you begin, ensure you have the following **pre-requisites** installed on your system:\n\n{% tabs %}\n{% tabsitem #api-server-prerequisites title=\"API server\" %}\n\n[uv](https://docs.astral.sh/uv/getting-started/installation/) must be installed on your system.\n\n{% /tabsitem %}\n\n{% tabsitem #docs-server-prerequisites title=\"Docs server\" %}\n\nNo additional prerequisites. The docs server runs as a remote HTTP endpoint.\n\n{% /tabsitem %}\n{% /tabs %}\n\nRun the following commands in your terminal to add the MCP servers:\n\n{% tabs %}\n{% tabsitem #api-only title=\"API server\" %}\n\n```bash\ncodex mcp add appwrite-api \\\n --env APPWRITE_PROJECT_ID=your-project-id \\\n --env APPWRITE_API_KEY=your-api-key \\\n --env APPWRITE_ENDPOINT=https://.cloud.appwrite.io/v1 \\\n -- uvx mcp-server-appwrite\n```\n\n**Configuration:**\n\n- Replace `your-project-id` with your actual Appwrite project ID\n- Replace `your-api-key` with your Appwrite API key\n- Replace `` with your Appwrite Cloud region (e.g., `nyc`, `fra`)\n\n{% /tabsitem %}\n\n{% tabsitem #docs-only title=\"Docs server\" %}\n\n```bash\ncodex mcp add appwrite-docs --url https://mcp-for-docs.appwrite.io\n```\n\n{% /tabsitem %}\n{% /tabs %}\n\n{% /section %}\n\n{% section #step-4 step=4 title=\"Test the integration\" %}\n\nTry out the following example prompts based on the MCP server you have configured:\n\n{% tabs %}\n{% tabsitem #test-api title=\"API server\" %}\n\n**Example prompts:**\n\n- `Create a new user in my Appwrite project`\n- `List all databases in my project`\n- `Show me the collections in my database`\n- `Create a new document in my collection`\n- `Delete a specific user by ID`\n\n{% /tabsitem %}\n\n{% tabsitem #test-docs title=\"Docs server\" %}\n\n**Example prompts:**\n\n- `How do I set up real-time subscriptions in Appwrite?`\n- `Show me how to authenticate users with OAuth`\n- `What are the best practices for database queries?`\n- `How do I implement file uploads with Appwrite Storage?`\n- `Show me an example of using Appwrite Functions`\n\n{% /tabsitem %}\n\n{% /tabs %}\n\n![Codex listing users via the Appwrite API MCP](/images/docs/mcp/codex/test-integration.avif)\n\n{% /section %}"}, {"path": "docs/tooling/ai/agents/cursor", "title": "Cursor", "description": "Learn how to use Cursor with Appwrite through the Appwrite plugin, quick start prompts, and MCP servers for AI-assisted development.", "content": "{% section #install-plugin step=1 title=\"Install the Appwrite plugin\" %}\n\nThe fastest way to get started with Appwrite in Cursor is to install the **Appwrite plugin** from the Cursor Marketplace. The plugin includes agent skills, MCP servers, and commands, giving Cursor's AI agents everything they need to work with your Appwrite projects.\n\nTo install the plugin:\n\n1. Visit the [Appwrite plugin](https://cursor.com/marketplace/appwrite) page on the Cursor Marketplace.\n2. Sign in with your Cursor account.\n3. Click **Add to Cursor**.\n4. The plugin will be added to your editor automatically.\n\nOnce installed, the plugin gives your AI agents access to Appwrite skills for all major SDKs, MCP servers for the Appwrite API and documentation, and commands for deploying functions and sites.\n\n{% info title=\"Prefer manual setup?\" %}\n\nIf you'd rather configure MCP servers individually, skip to [Step 3](#step-3).\n\n{% /info %}\n\n{% /section %}\n\n{% section #quick-start-prompts step=2 title=\"Quick start prompts\" %}\n\nGet started quickly with these pre-built prompts for common Appwrite integrations:\n\n{% cards %}\n{% cards_item href=\"/docs/tooling/ai/quickstart-prompts/nextjs\" title=\"Next.js\" icon=\"icon-nextjs\" %}\n{% /cards_item %}\n{% cards_item href=\"/docs/tooling/ai/quickstart-prompts/tanstack-start\" title=\"TanStack Start\" icon=\"web-icon-tanstack\" %}\n{% /cards_item %}\n{% /cards %}\n\n{% arrow_link href=\"/docs/tooling/ai/quickstart-prompts\" %}\nBrowse all quick start prompts\n{% /arrow_link %}\n\n{% /section %}\n\n{% section #step-3 step=3 title=\"Add MCP servers manually\" %}\n\nIf you installed the Appwrite plugin in [Step 1](#install-plugin), MCP servers are already configured and you can skip to [Step 4](#step-4).\n\nConnect Appwrite MCP servers to Cursor for deeper integration with the Appwrite API and documentation.\n\nBefore you begin, ensure you have the following **pre-requisites** installed on your system:\n\n{% tabs %}\n{% tabsitem #api-server-prerequisites title=\"API server\" %}\n\n[uv](https://docs.astral.sh/uv/getting-started/installation/) must be installed on your system.\n\n{% /tabsitem %}\n\n{% tabsitem #docs-server-prerequisites title=\"Docs server\" %}\n\n[Node.js](https://nodejs.org/en/download) and npm must be installed on your system.\n\n{% /tabsitem %}\n{% /tabs %}\n\nOpen 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.\n\nChoose which MCP server you want to configure:\n\n{% tabs %}\n{% tabsitem #api-only title=\"API server\" %}\n\nUpdate the `mcp.json` file to include the API server:\n\n```json\n{\n \"mcpServers\": {\n \"appwrite-api\": {\n \"command\": \"uvx\",\n \"args\": [\n \"mcp-server-appwrite\"\n ],\n \"env\": {\n \"APPWRITE_API_KEY\": \"your-api-key\",\n \"APPWRITE_PROJECT_ID\": \"your-project-id\",\n \"APPWRITE_ENDPOINT\": \"https://.cloud.appwrite.io/v1\"\n }\n }\n }\n}\n\n```\n\n**Configuration:**\n\n- Replace `your-project-id` with your actual Appwrite project ID\n- Replace `your-api-key` with your Appwrite API key\n- Replace `` with your Appwrite Cloud region (e.g., `nyc`, `fra`)\n\n{% /tabsitem %}\n\n{% tabsitem #docs-only title=\"Docs server\" %}\n\nUpdate the `mcp.json` file to include the docs server:\n\n```json\n{\n \"mcpServers\": {\n \"appwrite-docs\": {\n \"command\": \"npx\",\n \"args\": [\n \"mcp-remote\",\n \"\"\n ]\n }\n }\n}\n\n```\n\n{% /tabsitem %}\n{% /tabs %}\n\nYou can also **directly add the MCP servers to Cursor** using the following links:\n\n{% only_light %}\n{% cards %}\n\n{% cards_item href=\"https://apwr.dev/api-mcp-cursor?ref=docs\" title=\"API server\" image=\"/images/docs/mcp/logos/cursor-ai.svg\" %}\n{% /cards_item %}\n\n{% cards_item href=\"https://apwr.dev/docs-mcp-cursor?ref=docs\" title=\"Docs server\" image=\"/images/docs/mcp/logos/cursor-ai.svg\" %}\n{% /cards_item %}\n\n{% /cards %}\n{% /only_light %}\n\n{% only_dark %}\n{% cards %}\n\n{% cards_item href=\"https://apwr.dev/api-mcp-cursor?ref=docs\" title=\"API server\" image=\"/images/docs/mcp/logos/dark/cursor-ai.svg\" %}\n{% /cards_item %}\n\n{% cards_item href=\"https://apwr.dev/docs-mcp-cursor?ref=docs\" title=\"Docs server\" image=\"/images/docs/mcp/logos/dark/cursor-ai.svg\" %}\n{% /cards_item %}\n\n{% /cards %}\n{% /only_dark %}\n\nOnce you save the details, Cursor will connect with the MCP server(s) and load all available tools. You may need to restart Cursor if it is unable to start the MCP server.\n\n{% /section %}\n\n{% section #step-4 step=4 title=\"Test the integration\" %}\n\nOpen Cursor Agent and test your MCP integrations. You can try out the following example prompts based on the MCP server you have configured:\n\n{% tabs %}\n{% tabsitem #test-api title=\"API server\" %}\n\n**Example prompts:**\n\n- `Create a new user in my Appwrite project`\n- `List all databases in my project`\n- `Show me the tables in my database`\n- `Create a new row in my table`\n- `Delete a specific user by ID`\n\n{% /tabsitem %}\n\n{% tabsitem #test-docs title=\"Docs server\" %}\n\n**Example prompts:**\n\n- `How do I set up real-time subscriptions in Appwrite?`\n- `Show me how to authenticate users with OAuth`\n- `What are the best practices for database queries?`\n- `How do I implement file uploads with Appwrite Storage?`\n- `Show me an example of using Appwrite Functions`\n\n{% /tabsitem %}\n\n{% /tabs %}\n\n![Create a new user in Appwrite project](/images/docs/mcp/cursor/cursor-create-user.avif)\n\n{% /section %}"}, {"path": "docs/tooling/ai/agents/opencode", "title": "OpenCode", "description": "Learn how you can add the Appwrite MCP servers to OpenCode to interact with both the Appwrite API and documentation.", "content": "{% section #quick-start-prompts step=1 title=\"Quick start prompts\" %}\n\nGet started quickly with these pre-built prompts for common Appwrite integrations:\n\n{% cards %}\n{% cards_item href=\"/docs/tooling/ai/quickstart-prompts/nextjs\" title=\"Next.js\" icon=\"icon-nextjs\" %}\n{% /cards_item %}\n{% cards_item href=\"/docs/tooling/ai/quickstart-prompts/tanstack-start\" title=\"TanStack Start\" icon=\"web-icon-tanstack\" %}\n{% /cards_item %}\n{% /cards %}\n\n{% arrow_link href=\"/docs/tooling/ai/quickstart-prompts\" %}\nBrowse all quick start prompts\n{% /arrow_link %}\n\n{% /section %}\n\n{% section #step-2 step=2 title=\"Add MCP servers\" %}\n\nConnect Appwrite MCP servers to OpenCode for deeper integration with the Appwrite API and documentation.\n\nBefore you begin, ensure you have the following **pre-requisites** installed on your system:\n\n{% tabs %}\n{% tabsitem #api-server-prerequisites title=\"API server\" %}\n\n[uv](https://docs.astral.sh/uv/getting-started/installation/) must be installed on your system.\n\n{% /tabsitem %}\n\n{% tabsitem #docs-server-prerequisites title=\"Docs server\" %}\n\n[Node.js](https://nodejs.org/en/download) and npm must be installed on your system.\n\n{% /tabsitem %}\n{% /tabs %}\n\nUse the following configuration in your `opencode.json` file to use the Appwrite MCP servers.\n\n{% tabs %}\n{% tabsitem #api-only title=\"API server\" %}\n\n```json\n{\n \"$schema\": \"\",\n \"mcp\": {\n \"appwrite\": {\n \"type\": \"local\",\n \"command\": [\n \"uvx\",\n \"mcp-server-appwrite\"\n ],\n \"enabled\": true,\n \"environment\": {\n \"APPWRITE_PROJECT_ID\": \"your-project-id\",\n \"APPWRITE_API_KEY\": \"your-api-key\",\n \"APPWRITE_ENDPOINT\": \"https://.cloud.appwrite.io/v1\"\n }\n }\n }\n}\n\n```\n\n**Configuration:**\n\n- Replace `your-project-id` with your actual Appwrite project ID\n- Replace `your-api-key` with your Appwrite API key\n- Replace `` with your Appwrite Cloud region (e.g., `nyc`, `fra`)\n\n{% /tabsitem %}\n\n{% tabsitem #docs-only title=\"Docs server\" %}\n\n```json\n{\n \"$schema\": \"\",\n \"mcp\": {\n \"appwrite-docs\": {\n \"type\": \"remote\",\n \"enabled\": true,\n \"url\": \"\"\n }\n }\n}\n\n```\n\n{% /tabsitem %}\n{% /tabs %}\n\n{% /section %}\n\n{% section #step-3 step=3 title=\"Test the integration\" %}\n\nTry out the following example prompts based on the MCP server you have configured:\n\n{% tabs %}\n{% tabsitem #test-api title=\"API server\" %}\n\n**Example prompts:**\n\n- `Create a new user in my Appwrite project`\n- `List all databases in my project`\n- `Show me the collections in my database`\n- `Create a new document in my collection`\n- `Delete a specific user by ID`\n\n{% /tabsitem %}\n\n{% tabsitem #test-docs title=\"Docs server\" %}\n\n**Example prompts:**\n\n- `How do I set up real-time subscriptions in Appwrite?`\n- `Show me how to authenticate users with OAuth`\n- `What are the best practices for database queries?`\n- `How do I implement file uploads with Appwrite Storage?`\n- `Show me an example of using Appwrite Functions`\n\n{% /tabsitem %}\n\n{% /tabs %}\n\n![OAuth question in OpenCode](/images/docs/mcp/opencode/oauth-question.avif)\n\n{% /section %}"}, {"path": "docs/tooling/ai/agents/vscode", "title": "VS Code", "description": "Learn how you can use Appwrite with VS Code and GitHub Copilot for AI-assisted development. Get started quickly with pre-built prompts and connect to Appwrite MCP servers for deeper integration.", "content": "{% section #quick-start-prompts step=1 title=\"Quick start prompts\" %}\n\nGet started quickly with these pre-built prompts for common Appwrite integrations:\n\n{% cards %}\n{% cards_item href=\"/docs/tooling/ai/quickstart-prompts/nextjs\" title=\"Next.js\" icon=\"icon-nextjs\" %}\n{% /cards_item %}\n{% cards_item href=\"/docs/tooling/ai/quickstart-prompts/tanstack-start\" title=\"TanStack Start\" icon=\"web-icon-tanstack\" %}\n{% /cards_item %}\n{% /cards %}\n\n{% arrow_link href=\"/docs/tooling/ai/quickstart-prompts\" %}\nBrowse all quick start prompts\n{% /arrow_link %}\n\n{% /section %}\n\n{% section #add-mcp-servers step=2 title=\"Add MCP servers\" %}\n\nConnect Appwrite MCP servers to VS Code for deeper integration with the Appwrite API and documentation.\n\nBefore you begin, ensure you have the following **pre-requisites** installed on your system:\n\n{% tabs %}\n{% tabsitem #api-server-prerequisites title=\"API server\" %}\n\n[uv](https://docs.astral.sh/uv/getting-started/installation/) must be installed on your system.\n\n{% /tabsitem %}\n\n{% tabsitem #docs-server-prerequisites title=\"Docs server\" %}\n\n[Node.js](https://nodejs.org/en/download) and npm must be installed on your system.\n\n{% /tabsitem %}\n{% /tabs %}\n\nIn 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.\n\nChoose which MCP servers you want to configure:\n\n{% tabs %}\n{% tabsitem #api-only title=\"API server\" %}\n\nUpdate the `mcp.json` file to include the API server:\n\n```json\n{\n \"servers\": {\n \"appwrite-api\": {\n \"command\": \"uvx\",\n \"args\": [\n \"mcp-server-appwrite\"\n ],\n \"env\": {\n \"APPWRITE_PROJECT_ID\": \"your-project-id\",\n \"APPWRITE_API_KEY\": \"your-api-key\",\n \"APPWRITE_ENDPOINT\": \"https://.cloud.appwrite.io/v1\"\n }\n }\n }\n}\n\n```\n\n**Configuration:**\n\n- Replace `your-project-id` with your actual Appwrite project ID\n- Replace `your-api-key` with your Appwrite API key\n- Replace `` with your Appwrite Cloud region (e.g., `nyc`, `fra`)\n\n{% /tabsitem %}\n\n{% tabsitem #docs-only title=\"Docs server\" %}\n\nUpdate the `mcp.json` file to include the docs server:\n\n```json\n{\n \"servers\": {\n \"appwrite-docs\": {\n \"url\": \"\",\n \"type\": \"http\"\n }\n }\n}\n\n```\n\n{% /tabsitem %}\n{% /tabs %}\n\nOnce you save the configuration, Copilot Chat will connect with the MCP server(s) and load all available tools.\n\n{% /section %}\n\n{% section #test-the-integration step=3 title=\"Test the integration\" %}\n\nOpen **Copilot Chat** in VS Code and switch to **Agent Mode** to test your MCP integrations. You can try out the following example prompts based on the MCP server you have configured:\n\n{% tabs %}\n{% tabsitem #test-api title=\"API server\" %}\n\n**Example prompts:**\n\n- `Create a new user in my Appwrite project`\n- `List all databases in my project`\n- `Show me the collections in my database`\n- `Create a new document in my collection`\n- `Delete a specific user by ID`\n\n{% /tabsitem %}\n\n{% tabsitem #test-docs title=\"Docs server\" %}\n\n**Example prompts:**\n\n- `How do I set up real-time subscriptions in Appwrite?`\n- `Show me how to authenticate users with OAuth`\n- `What are the best practices for database queries?`\n- `How do I implement file uploads with Appwrite Storage?`\n- `Show me an example of using Appwrite Functions`\n\n{% /tabsitem %}\n\n{% /tabs %}\n\n![Search for portfolio site in Appwrite project](/images/docs/mcp/vscode/copilot-chat.avif)\n\n{% /section %}"}, {"path": "docs/tooling/ai/agents/windsurf", "title": "Windsurf", "description": "Learn how you can use Windsurf Editor with Appwrite by leveraging MCP servers and quick start prompts to build applications faster.", "content": "{% section #quick-start-prompts step=1 title=\"Quick start prompts\" %}\n\nGet started quickly with these pre-built prompts for common Appwrite integrations:\n\n{% cards %}\n{% cards_item href=\"/docs/tooling/ai/quickstart-prompts/nextjs\" title=\"Next.js\" icon=\"icon-nextjs\" %}\n{% /cards_item %}\n{% cards_item href=\"/docs/tooling/ai/quickstart-prompts/tanstack-start\" title=\"TanStack Start\" icon=\"web-icon-tanstack\" %}\n{% /cards_item %}\n{% /cards %}\n\n{% arrow_link href=\"/docs/tooling/ai/quickstart-prompts\" %}\nBrowse all quick start prompts\n{% /arrow_link %}\n\n{% /section %}\n\n{% section #add-mcp-servers step=2 title=\"Add MCP servers\" %}\n\nConnect Appwrite MCP servers to Windsurf for deeper integration with the Appwrite API and documentation.\n\nBefore you begin, ensure you have the following **pre-requisites** installed on your system:\n\n{% tabs %}\n{% tabsitem #api-server-prerequisites title=\"API server\" %}\n\n[uv](https://docs.astral.sh/uv/getting-started/installation/) must be installed on your system.\n\n{% /tabsitem %}\n\n{% tabsitem #docs-server-prerequisites title=\"Docs server\" %}\n\n[Node.js](https://nodejs.org/en/download) and npm must be installed on your system.\n\n{% /tabsitem %}\n{% /tabs %}\n\nOpen 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.\n\n![Windsurf add MCP server](/images/docs/mcp/windsurf/windsurf-add-mcp-server.avif)\n\nChoose which MCP server you want to configure:\n\n{% tabs %}\n{% tabsitem #api-only title=\"API server\" %}\n\nUpdate the `mcp_config.json` file to include the API server:\n\n```json\n{\n \"mcpServers\": {\n \"appwrite-api\": {\n \"command\": \"uvx\",\n \"args\": [\n \"mcp-server-appwrite\"\n ],\n \"env\": {\n \"APPWRITE_PROJECT_ID\": \"your-project-id\",\n \"APPWRITE_API_KEY\": \"your-api-key\",\n \"APPWRITE_ENDPOINT\": \"https://.cloud.appwrite.io/v1\"\n }\n }\n }\n}\n\n```\n\n**Configuration:**\n\n- Replace `your-project-id` with your actual Appwrite project ID\n- Replace `your-api-key` with your Appwrite API key\n- Replace `` with your Appwrite Cloud region (e.g., `nyc`, `fra`)\n\n{% /tabsitem %}\n\n{% tabsitem #docs-only title=\"Docs server\" %}\n\nUpdate the `mcp_config.json` file to include the docs server:\n\n```json\n{\n \"mcpServers\": {\n \"appwrite-docs\": {\n \"serverUrl\": \"\"\n }\n }\n}\n\n```\n\n{% /tabsitem %}\n{% /tabs %}\n\nOnce you save the details, head back to the MCP Servers section in the Windsurf Settings and click on the **Refresh** button.\n\n{% /section %}\n\n{% section #test-the-integration step=3 title=\"Test the integration\" %}\n\nOpen Cascade chat in the Windsurf Editor and test your MCP integrations. You can try out the following example prompts based on the MCP server you have configured:\n\n{% tabs %}\n{% tabsitem #test-api title=\"API server\" %}\n\n**Example prompts:**\n\n- `Create a new user in my Appwrite project`\n- `List all databases in my project`\n- `Show me the collections in my database`\n- `Create a new document in my collection`\n- `Delete a specific user by ID`\n\n{% /tabsitem %}\n\n{% tabsitem #test-docs title=\"Docs server\" %}\n\n**Example prompts:**\n\n- `How do I set up real-time subscriptions in Appwrite?`\n- `Show me how to authenticate users with OAuth`\n- `What are the best practices for database queries?`\n- `How do I implement file uploads with Appwrite Storage?`\n- `Show me an example of using Appwrite Functions`\n\n{% /tabsitem %}\n\n{% /tabs %}\n\n![Windsurf Cascade chat](/images/docs/mcp/windsurf/windsurf-cascade-chat.avif)\n\n{% /section %}"}, {"path": "docs/tooling/ai/agents/zed", "title": "Zed", "description": "Learn how you can use Zed with Appwrite by adding Appwrite MCP servers and installing Appwrite skills for AI-assisted development.", "content": "{% section #quick-start-prompts step=1 title=\"Quick start prompts\" %}\n\nGet started quickly with these pre-built prompts for common Appwrite integrations:\n\n{% cards %}\n{% cards_item href=\"/docs/tooling/ai/quickstart-prompts/nextjs\" title=\"Next.js\" icon=\"icon-nextjs\" %}\n{% /cards_item %}\n{% cards_item href=\"/docs/tooling/ai/quickstart-prompts/tanstack-start\" title=\"TanStack Start\" icon=\"web-icon-tanstack\" %}\n{% /cards_item %}\n{% /cards %}\n\n{% arrow_link href=\"/docs/tooling/ai/quickstart-prompts\" %}\nBrowse all quick start prompts\n{% /arrow_link %}\n\n{% /section %}\n\n{% section #step-2 step=2 title=\"Add MCP servers\" %}\n\nConnect Appwrite MCP servers to Zed for deeper integration with the Appwrite API and documentation.\n\nBefore you begin, ensure you have the following **pre-requisites** installed on your system:\n\n{% tabs %}\n{% tabsitem #api-server-prerequisites title=\"API server\" %}\n\n[uv](https://docs.astral.sh/uv/getting-started/installation/) must be installed on your system.\n\n{% /tabsitem %}\n\n{% tabsitem #docs-server-prerequisites title=\"Docs server\" %}\n\nNo additional prerequisites. The docs server runs as a remote HTTP endpoint.\n\n{% /tabsitem %}\n{% /tabs %}\n\nIn Zed, open the **Command Palette** (press `CMD + Shift + P` on MacOS or `CTRL + Shift + P` on Linux), run the `agent: add context server` action, and choose which MCP server you want to configure:\n\n{% tabs %}\n{% tabsitem #api-only title=\"API server\" %}\n\nIn the **Local** tab, update the JSON configuration to include the API server:\n\n```json\n{\n \"appwrite-api\": {\n \"command\": \"uvx\",\n \"args\": [\n \"mcp-server-appwrite\"\n ],\n \"env\": {\n \"APPWRITE_PROJECT_ID\": \"your-project-id\",\n \"APPWRITE_API_KEY\": \"your-api-key\",\n \"APPWRITE_ENDPOINT\": \"https://.cloud.appwrite.io/v1\"\n }\n }\n}\n\n```\n\n**Configuration:**\n\n- Replace `your-project-id` with your actual Appwrite project ID\n- Replace `your-api-key` with your Appwrite API key\n- Replace `` with your Appwrite Cloud region (e.g., `nyc`, `fra`)\n\n{% /tabsitem %}\n\n{% tabsitem #docs-only title=\"Docs server\" %}\n\nIn the **Remote** tab, update the JSON configuration to include the docs server:\n\n```json\n{\n \"appwrite-docs\": {\n \"url\": \"https://mcp-for-docs.appwrite.io\"\n }\n}\n\n```\n\n{% /tabsitem %}\n{% /tabs %}\n\nClick **Add Server** after adding each configuration. You can also review the configured servers in the **Model Context Protocol (MCP) Servers** section of Zed's Agent settings.\n\n{% /section %}\n\n{% section #step-3 step=3 title=\"Install Appwrite skills\" %}\n\nInstall Appwrite skills to give Zed's agent SDK-specific knowledge about Appwrite.\n\nRun the following command in your project directory:\n\n```bash\nnpx skills add appwrite/agent-skills\n```\n\nWhen prompted:\n\n1. Select the Appwrite skills that match the SDKs you use in your project.\n2. Select **Zed** as one of the AI tools that should use the installed skills.\n3. Choose whether to install the skills at the project level or globally.\n4. Select **symlink** as the installation method.\n\nZed loads skills from `~/.agents/skills/` for global installs and `/.agents/skills/` for project-local installs. Each skill must be a direct child of the `skills` folder and include a `SKILL.md` file.\n\n{% /section %}\n\n{% section #step-4 step=4 title=\"Test the integration\" %}\n\nOpen the Agent panel in Zed and test your MCP integrations. You can try out the following example prompts based on the MCP server you have configured:\n\n{% tabs %}\n{% tabsitem #test-api title=\"API server\" %}\n\n**Example prompts:**\n\n- `Create a new user in my Appwrite project`\n- `List all databases in my project`\n- `Show me the tables in my database`\n- `Create a new row in my table`\n- `Delete a specific user by ID`\n\n{% /tabsitem %}\n\n{% tabsitem #test-docs title=\"Docs server\" %}\n\n**Example prompts:**\n\n- `How do I set up real-time subscriptions in Appwrite?`\n- `Show me how to authenticate users with OAuth`\n- `What are the best practices for database queries?`\n- `How do I implement file uploads with Appwrite Storage?`\n- `Show me an example of using Appwrite Functions`\n\n{% /tabsitem %}\n\n{% /tabs %}\n\n![Zed Agent using the Appwrite docs MCP](/images/docs/mcp/zed/zed-agent-chat.avif)\n\n{% /section %}"}, {"path": "docs/tooling/ai/ai-in-functions", "title": "AI in Functions", "description": "Learn how to integrate AI capabilities into your Appwrite Functions using the Vercel AI SDK.", "content": "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.\n\nThis guide shows how to build an Appwrite Function that generates text using the Vercel AI SDK with OpenAI.\n\n{% info title=\"Streaming not yet supported\" %}\nAppwrite Functions do not currently support streaming responses. Support for streaming is coming soon. For now, use `generateText` to return complete responses.\n{% /info %}\n\n# Prerequisites {% #prerequisites %}\n\n- An Appwrite project\n- An [OpenAI API key](https://platform.openai.com/account/api-keys)\n\n{% section #step-1 step=1 title=\"Create new function\" %}\nHead 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.\n\n{% only_dark %}\n![Create function screen](/images/docs/functions/dark/template.avif)\n{% /only_dark %}\n\n{% only_light %}\n![Create function screen](/images/docs/functions/template.avif)\n{% /only_light %}\n\n1. In the Appwrite Console's sidebar, click **Functions**.\n1. Click **Create function**.\n1. Under **Connect Git repository**, select your provider.\n1. After connecting to GitHub, under **Quick start**, select the **Node.js** starter template.\n1. 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**.\n1. Follow the step-by-step wizard and create the function.\n{% /section %}\n\n{% section #step-2 step=2 title=\"Add dependencies\" %}\nOnce the function is created, navigate to the freshly created repository and clone it to your local machine.\n\nInstall the `ai` package and the OpenAI provider:\n\n```bash\nnpm install ai @ai-sdk/openai\n```\n\nThe `ai` package is the core Vercel AI SDK, and `@ai-sdk/openai` is the provider that connects it to OpenAI's API.\n{% /section %}\n\n{% section #step-3 step=3 title=\"Create the function\" %}\nReplace the contents of `src/main.js` with the following code:\n\n```js\nimport { generateText } from \"ai\";\nimport { openai } from \"@ai-sdk/openai\";\n\nexport default async ({ req, res, log, error }) => {\n if (req.path === \"/api/generate\") {\n if (req.method !== \"POST\") {\n return res.status(405).json({ error: \"Method not allowed\" });\n }\n\n const { prompt } = req.bodyJson;\n const result = await generateText({\n model: openai(\"gpt-5-mini\"),\n prompt,\n });\n\n return res.json({ text: result.text });\n }\n\n return res.status(404).json({ error: \"Not found\" });\n};\n```\n\nThe 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.\n\nThe 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.\n{% /section %}\n\n{% section #step-4 step=4 title=\"Using other providers\" %}\nThe Vercel AI SDK supports many providers beyond OpenAI. You can swap providers by installing the relevant package and changing the model import.\n\nFor example, to use Anthropic:\n\n```bash\nnpm install @ai-sdk/anthropic\n```\n\n```js\nimport { anthropic } from '@ai-sdk/anthropic';\n\nconst result = await generateText({\n model: anthropic('claude-sonnet-4-5-20250929'),\n prompt,\n});\n```\n\nOr to use Google's Gemini:\n\n```bash\nnpm install @ai-sdk/google\n```\n\n```js\nimport { google } from '@ai-sdk/google';\n\nconst result = await generateText({\n model: google('gemini-2.0-flash'),\n prompt,\n});\n```\n\nAdd the corresponding API key as an environment variable in your function settings for each provider.\n{% /section %}\n\n{% section #step-5 step=5 title=\"Test the function\" %}\nNow that the function is deployed, test it by sending a `POST` request to the function's URL:\n\n```bash\ncurl -X POST https://FUNCTION_DOMAIN/api/generate \\\n -H \"Content-Type: application/json\" \\\n -d '{\"prompt\": \"Explain quantum computing in one sentence.\"}'\n```\n\nYou should receive a JSON response with the generated text:\n\n```json\n{\n \"text\": \"Quantum computing uses quantum mechanical phenomena...\"\n}\n```\n{% /section %}"}, {"path": "docs/tooling/ai/arena", "title": "Appwrite Arena", "description": "An open-source benchmark that evaluates how well AI models understand Appwrite's services, SDKs, and APIs.", "content": "[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.\n\nArena 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.\n\nAll questions, answers, and scores are fully open source and available on [GitHub](https://github.com/appwrite/arena).\n\n# How it works {% #how-it-works %}\n\nArena evaluates each model using a pool of **191 questions** spanning **9 Appwrite service categories**:\n\n- Foundation\n- Auth\n- Databases\n- Functions\n- Storage\n- Sites\n- Messaging\n- Realtime\n- CLI\n\nEach model is tested in two contexts:\n\n- **Without Skills**: The model answers using only its built-in training data.\n- **With Skills**: The model answers with access to Appwrite's [skills files](/docs/tooling/skills), which provide up-to-date SDK and API context.\n\nThe gap between these two scores reveals how effectively a model can leverage provided documentation to improve its responses.\n\nAll benchmark runs are configured with:\n\n- **Temperature 0**: Makes the entire benchmark deterministic, so identical inputs always produce identical outputs.\n- **Extended thinking set to high**: Matches the configuration most commonly used in real-world programming contexts.\n\n# Scoring {% #scoring %}\n\nArena uses two complementary scoring methods to evaluate model performance:\n\n## Deterministic (MCQ) {% #deterministic %}\n\n165 multiple-choice questions structured like a typical exam, each with a single correct answer:\n\n- The model receives a system prompt and the question.\n- Four tools are provided, one for each possible answer.\n- The model submits its response by calling the correct tool, avoiding issues with verbose or unparseable output.\n\nScores are fully reproducible with no judge bias.\n\n## AI-judged (open-ended) {% #ai-judged %}\n\n26 open-ended questions scored from 0 to 1 by an AI judge using rubrics and reference answers:\n\n- Tests reasoning and real-world usage patterns that multiple-choice cannot capture.\n- Scores may have slight variance due to the nature of AI-based evaluation.\n\n# Use cases {% #use-cases %}\n\n- **Choosing a model**: Compare models to find the one that best fits your Appwrite development workflow and budget.\n- **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.\n- **Measuring skill impact**: See how much a model improves when given Appwrite skills, helping you decide whether to install skills for your AI tools.\n- **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.\n- **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.\n- **Contributing**: Since Arena is open source, you can submit new questions, suggest improvements to scoring rubrics, or add new models to the benchmark."}, {"path": "docs/tooling/ai/assistant", "title": "Assistant", "description": "AI-powered assistant for precise Appwrite tasks. Troubleshoot issues faster, generate code snippets, and search Appwrite's docs with the use of AI.", "content": "The **Appwrite Assistant** is an AI-powered tool engineered to augment Appwrite-related tasks with technical precision. It operates on a foundation of training data sourced from the Appwrite documentation website, enabling it to furnish insights into Appwrite's features, APIs, and documentation. Additionally, it offers functionality for code snippet generation, sample project creation, and problem troubleshooting.\n\n{% only_dark %}\n![Using Appwrite Assistant's AI features](/images/docs/assistant/dark/ask-ai.avif)\n{% /only_dark %}\n{% only_light %}\n![Using Appwrite Assistant's AI features](/images/docs/assistant/ask-ai.avif)\n{% /only_light %}\n\n{% info title=\"Development\" %}\nWhile the Appwrite Assistant remains under active development and is considered experimental, it undergoes incremental refinement. Its proficiency in comprehending user queries and delivering relevant responses improves with sustained usage.\n{% /info %}\n\n# Getting started {% #getting-started %}\n\nTo engage the Appwrite Assistant, access the Command Center within your Appwrite Console, and proceed to the `Ask the AI` tab in the navigation.\n\n# Querying {% #querying %}\n\nThe Appwrite Assistant accommodates queries related to Appwrite, encompassing topics such as:\n\n- How to add platform in the console?\n- How to integrate Appwrite in my Flutter project?\n- How can I create a user account?\n- How to store a file in a bucket?\n\n# Optimizations {% #optimizations %}\n\nFor optimal utilization of the Appwrite Assistant, consider the following recommendations:\n\n- **Precision**: Articulate queries with specificity.\n- **Natural Language**: The Assistant comprehends plain English, obviating the need for technical terminology.\n- **Query Refinement**: In cases of misinterpretation, rephrase queries.\n- **Manual Documentation Search**: In scenarios where the Assistant falls short, manually search the Appwrite documentation.\n\n# Privacy {% #privacy %}\n\nWhen you use the Appwrite Assistant, your questions are sent to OpenAI to generate a response. OpenAI may collect and store your questions and responses for the purposes of improving their services."}, {"path": "docs/tooling/ai/docs-as-markdown", "title": "Docs as Markdown", "description": "Access Appwrite documentation as Markdown for AI consumption.", "content": "Appwrite documentation is available as Markdown, making it easy to use with AI-powered development tools, code editors, and LLMs.\n\nMarkdown 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.\n\n# Copy as Markdown\n\nEvery page in the Appwrite documentation includes a **Copy page** button that copies the entire page content as Markdown to your clipboard.\n\nThis is useful when you want to:\n\n- Paste documentation into an AI chat for context\n- Include documentation in your project's reference files\n\n{% only_dark %}\n![AI-ready prompts in quick start guides](/images/docs/ai/docs-as-markdown/copy-page-dark.avif)\n{% /only_dark %}\n{% only_light %}\n![AI-ready prompts in quick start guides](/images/docs/ai/docs-as-markdown/copy-page-light.avif)\n{% /only_light %}\n\n# AI-ready prompts\n\nSome 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.\n\n{% only_dark %}\n![AI-ready prompts in quick start guides](/images/docs/ai/docs-as-markdown/quickstart-dark.avif)\n{% /only_dark %}\n{% only_light %}\n![AI-ready prompts in quick start guides](/images/docs/ai/docs-as-markdown/quickstart-light.avif)\n{% /only_light %}\n\n# Markdown URLs\n\nYou can access any documentation page as raw Markdown by appending `.md` to the URL. Here are some examples:\n\n- `https://appwrite.io/docs/apis/graphql` → `https://appwrite.io/docs/apis/graphql.md`\n- `https://appwrite.io/docs/products/auth` → `https://appwrite.io/docs/products/auth.md`\n- `https://appwrite.io/docs/quick-starts/nextjs` → `https://appwrite.io/docs/quick-starts/nextjs.md`\n\nThis allows AI tools and scripts to fetch documentation directly without parsing HTML."}, {"path": "docs/tooling/ai/mcp-servers", "title": "Model Context Protocol", "description": "Enable LLMs and code-generation tools to interact with your Appwrite project", "content": "Appwrite offers [Model Context Protocol](https://modelcontextprotocol.io) (MCP) servers that allow LLMs to directly interact with Appwrite's API and docs. Using MCP servers, you can use applications such as Claude 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.\n\n# What is MCP?\n\nThe 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.\n\nThe key benefits of using MCP servers include:\n\n- **Enhanced capabilities**: LLMs can perform complex tasks by interacting with APIs, going beyond simple text generation.\n- **Improved context**: By accessing up-to-date documentation and API definitions, LLMs can provide more accurate and relevant responses.\n- **Seamless integration**: MCP servers can be easily integrated with popular AI tools and code editors, enhancing their functionality.\n\n# Available MCP servers\n\nAppwrite currently offers the following MCP servers:\n\n{% cards %}\n\n{% cards_item href=\"/docs/tooling/ai/mcp-servers/api\" title=\"MCP server for Appwrite API\" icon=\"icon-globe-alt\"%}\n{% /cards_item %}\n\n{% cards_item href=\"/docs/tooling/ai/mcp-servers/docs\" title=\"MCP server for Appwrite docs\" icon=\"icon-document-text\" %}\n{% /cards_item %}\n\n{% /cards %}\n\n## Why use Appwrite's MCP servers?\n\nSome **popular use cases** for Appwrite's MCP servers include:\n\n- **Code generation**: Automatically generate code snippets or entire files based on user input and context.\n- **Documentation lookup**: Quickly find relevant documentation for specific API endpoints or SDK features.\n- **Project management**: Create, update, or delete resources in your Appwrite project using natural language commands.\n- **Debugging assistance**: Get help with debugging issues by providing context about your project and recent changes.\n- **Learning and exploration**: Explore Appwrite's features and capabilities through interactive conversations with LLMs."}, {"path": "docs/tooling/ai/mcp-servers/api", "title": "MCP server for Appwrite API", "description": "Enable LLMs and code-generation tools to interact with the Appwrite API", "content": "The MCP server for Appwrite API allows LLMs and code-generation tools to interact with the Appwrite platform and perform various operations on your Appwrite resources, such as creating users, managing databases, and more, using natural language commands.\n\nHere are some of the key benefits of using the MCP server:\n\n- **Direct API interaction**: Enables LLMs to perform actions directly on your Appwrite project\n- **Real-time data access**: Allows LLMs to fetch and manipulate live data from your Appwrite instance\n- **Simplified workflows**: Facilitates complex operations through simple natural language prompts\n- **Automatic service discovery**: All supported Appwrite services are automatically registered, no configuration needed\n\n# Pre-requisites {% #pre-requisites %}\n\n## Appwrite API key\n\nBefore launching the MCP server, you must [set up an Appwrite project](https://cloud.appwrite.io) and create an **API key** with the necessary scopes enabled.\n\n{% only_light %}\n![Appwrite API key](/images/docs/mcp/appwrite/appwrite-api-secret.avif)\n{% /only_light %}\n{% only_dark %}\n![Appwrite API key](/images/docs/mcp/appwrite/dark/appwrite-api-secret.avif)\n{% /only_dark %}\n\nEnsure you save the **API key** along with the **project ID**, **region** and **endpoint URL** from the Settings page of your project as you'll need them later.\n\n## Install uv\n\nInstall [uv](https://docs.astral.sh/uv/getting-started/installation/) on your system with:\n\n{% tabs %}\n{% tabsitem #uv-linux-macos title=\"Linux and MacOS\" %}\n\n```bash\ncurl -LsSf https://astral.sh/uv/install.sh | sh\n```\n\n{% /tabsitem %}\n\n{% tabsitem #uv-windows title=\"Windows\" %}\n\n```powershell\npowershell -ExecutionPolicy ByPass -c \"irm https://astral.sh/uv/install.ps1 | iex\"\n``` \n\n{% /tabsitem %}\n{% /tabs %}\n\nYou can verify the installation by running the following command in your terminal:\n\n```bash\nuv\n```\n\n# Installation {% #installation %}\n\n{% partial file=\"mcp-add-ides-tools.md\" /%}\n\n## How it works\n\nThe MCP server starts in a compact workflow where only two MCP tools are exposed to the model:\n\n- `appwrite_search_tools` - Searches the full Appwrite tool catalog at runtime\n- `appwrite_call_tool` - Calls a specific Appwrite tool by name\n\nThe full Appwrite tool catalog stays internal and is searched at runtime, using less of the model's context.\n\n# Usage {% #usage %}\n\nOnce configured, your AI assistant will have access to your Appwrite project. You can ask questions like:\n\n## Example 1: List users\n\nRun the following prompt in your preferred code editor/LLM after enabling the MCP server: \n\n```\nList users in my Appwrite project\n```\n\n![List users in Appwrite project](/images/docs/mcp/claude-desktop/claude-list-users.avif)\n\n## Example 2: Search a site\n\nRun the following prompt in your preferred code editor/LLM after enabling the MCP server: \n\n```\nGet the details of my portfolio site from Appwrite\n```\n\n![Search for portfolio site in Appwrite project](/images/docs/mcp/vscode/copilot-chat.avif)\n\n## Example 3: Create a user\n\nRun the following prompt in your preferred code editor/LLM after enabling the MCP server: \n\n```\nAdd a user john.doe@example.com to the Appwrite project\n```\n\n![Create user in Appwrite project](/images/docs/mcp/cursor/cursor-create-user.avif)"}, {"path": "docs/tooling/ai/mcp-servers/docs", "title": "MCP server for Appwrite docs", "description": "Enable LLMs and code-generation tools to interact with the Appwrite docs", "content": "The MCP server for Appwrite documentation allows LLMs and code-generation tools to interact with comprehensive Appwrite documentation, enabling intelligent code generation for Appwrite's APIs and SDKs, troubleshooting assistance, and implementation guidance using natural language commands.\n\nHere are some of the key benefits of using the MCP server:\n\n- **Complete documentation access**: Provides AI assistants with access to all Appwrite documentation\n- **Real-time context**: Ensures AI responses are based on the latest documentation\n- **Intelligent search**: Enables semantic search across documentation content\n- **Code examples**: Includes access to code samples and implementation guides\n- **Best practices**: Shares recommended patterns and practices from official documentation\n\n# Pre-requisites {% #pre-requisites %}\n\nInstall [Node.js](https://nodejs.org/en/download) and npm on your system. You can verify the installation by running the following commands in your terminal:\n\n```bash\nnode -v\nnpm -v\n```\n\n# Installation {% #installation %}\n\n{% partial file=\"mcp-add-ides-tools.md\" /%}\n\n# Usage {% #usage %}\n\nOnce configured, your AI assistant will have access to Appwrite documentation context. You can ask questions like:\n\n## Example 1: Code generation\n\nRun the following prompt in your preferred code editor/LLM after enabling the MCP server: \n\n```\nShow me how to set up real-time subscriptions that trigger on creation of a user\n```\n\n![Code generation example](/images/docs/mcp/mcp-for-docs/code-generation.avif)\n\n## Example 2: Troubleshooting\n\nRun the following prompt in your preferred code editor/LLM after enabling the MCP server: \n\n```\nI'm getting a 401 error when trying to delete a user. What could be wrong?\n```\n\n![Troubleshooting example](/images/docs/mcp/mcp-for-docs/troubleshooting.avif)\n\n## Example 3: Best practices\n\nRun the following prompt in your preferred code editor/LLM after enabling the MCP server: \n\n```\nWhat are some of the best security practices for Appwrite Auth in a web app with SSR?\n```\n\n![Best practices example](/images/docs/mcp/mcp-for-docs/best-practices.avif)\n\n## Example 4: API reference\n\nRun the following prompt in your preferred code editor/LLM after enabling the MCP server: \n\n```\nI want an example of how I can list all users in a Python app\n```\n\n![API reference example](/images/docs/mcp/mcp-for-docs/api-reference.avif)"}, {"path": "docs/tooling/ai/persistent-agents-with-realtime", "title": "Persistent Agents with Realtime", "description": "Building persistent AI agents using Appwrite Realtime.", "content": "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.\n\n## Architecture\n\n1. **Store messages**: Save user messages and LLM responses in an Appwrite table\n2. **Subscribe to changes**: Use Realtime to listen for new messages\n3. **Maintain context**: Load conversation history to provide context to the LLM\n\n## Set up the messages table\n\nCreate a table to store conversation messages with the following columns:\n\n| Key | Type | Description |\n| --- | --- | --- |\n| `conversationId` | varchar | Groups messages by conversation |\n| `role` | varchar | Either \"user\" or \"assistant\" |\n| `content` | longtext | The message content |\n| `$createdAt` | automatic | Timestamp for ordering |\n\n## Store messages in the database\n\nWhen a user sends a message or the LLM responds, save it to the database:\n\n```js\nimport { Client, TablesDB, ID } from 'node-appwrite';\n\nconst client = new Client()\n .setEndpoint(process.env.APPWRITE_ENDPOINT ?? 'https://.cloud.appwrite.io/v1')\n .setProject(process.env.APPWRITE_FUNCTION_PROJECT_ID)\n .setKey(process.env.APPWRITE_API_KEY);\n\nconst tablesDB = new TablesDB(client);\n\n// Save user message\nawait tablesDB.createRow({\n databaseId: process.env.DATABASE_ID,\n tableId: process.env.MESSAGES_TABLE_ID,\n rowId: ID.unique(),\n data: {\n conversationId: conversationId,\n role: 'user',\n content: userMessage,\n }\n});\n\n// Generate LLM response\nconst response = await generateLLMResponse(userMessage, conversationHistory);\n\n// Save assistant message\nawait tablesDB.createRow({\n databaseId: process.env.DATABASE_ID,\n tableId: process.env.MESSAGES_TABLE_ID,\n rowId: ID.unique(),\n data: {\n conversationId: conversationId,\n role: 'assistant',\n content: response,\n }\n});\n```\n\n## Subscribe to messages with Realtime\n\nOn 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.\n\n```js\nimport { Client, Realtime, Channel, Query } from 'appwrite';\n\nconst client = new Client()\n .setEndpoint('https://.cloud.appwrite.io/v1')\n .setProject('');\n\nconst realtime = new Realtime(client);\n\n// Subscribe to new messages for the current conversation\nconst subscription = await realtime.subscribe(\n Channel.tablesdb('').table('').row().create(),\n (response) => {\n displayMessage(response.payload);\n },\n [Query.equal('conversationId', [currentConversationId])]\n);\n```\n\nThe `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.\n\nBy 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.\n\n## Load conversation history for context\n\nBefore generating an LLM response, load recent messages to provide context:\n\n```js\nimport { Query } from 'node-appwrite';\n\nconst { rows } = await tablesDB.listRows({\n databaseId: process.env.DATABASE_ID,\n tableId: process.env.MESSAGES_TABLE_ID,\n queries: [\n Query.equal('conversationId', conversationId),\n Query.orderAsc('$createdAt'),\n Query.limit(10),\n ]\n});\n\nconst conversationHistory = rows.map((row) => ({\n role: row.role,\n content: row.content,\n}));\n```\n\n## Generate responses with context\n\nPass the conversation history to the LLM:\n\n```js\nconst response = await fetch('https://api.openai.com/v1/chat/completions', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,\n },\n body: JSON.stringify({\n model: 'gpt-4',\n messages: conversationHistory,\n }),\n});\n\nconst data = await response.json();\nconst assistantMessage = data.choices[0].message.content;\n```\n\n## Benefits\n\n- **Persistence**: Conversations survive page refreshes and app restarts\n- **Multi-device sync**: Users can continue conversations on different devices\n- **Real-time updates**: Multiple users or clients see messages instantly\n- **Audit trail**: All messages are stored and can be reviewed later"}, {"path": "docs/tooling/ai/quickstart-prompts", "title": "Quick start prompts", "description": "Use AI assistants and code-generation tools to build Appwrite-powered applications faster using quick start prompts.", "content": "**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.\n\n\nQuick start prompts offer several advantages when building with Appwrite:\n\n- **Faster setup**: Skip the manual configuration and let AI handle the boilerplate code and SDK integration.\n- **Best practices**: Prompts are crafted to follow Appwrite's recommended patterns and conventions.\n- **Interactive guidance**: AI assistants will ask for necessary details like your project ID and region rather than making assumptions.\n\n# How to use quick start prompts {% #how-to-use-quick-start-prompts %}\n\n1. 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.\n2. Copy the quickstart prompt for your chosen framework.\n3. Paste the prompt and let the AI guide you through the setup process.\n4. When prompted, provide your Appwrite project details (region and project ID) from the Appwrite Console.\n\n{% info title=\"MCP servers\" %}\nFor the best experience, consider using Appwrite's [MCP servers](/docs/tooling/ai/mcp-servers) alongside quick start prompts. MCP servers allow AI assistants to directly interact with your Appwrite project and access up-to-date documentation.\n{% /info %}\n\n# Available quick start prompts {% #available-quick-start-prompts %}\n\nUse the following quick start prompts with your preferred AI assistant to integrate Appwrite into your project:\n\n## Web {% #web %}\n\n{% cards %}\n\n{% cards_item href=\"/docs/tooling/ai/quickstart-prompts/nextjs\" title=\"Next.js\" icon=\"icon-nextjs\" %}\n{% /cards_item %}\n\n{% cards_item href=\"/docs/tooling/ai/quickstart-prompts/react\" title=\"React\" icon=\"icon-react\" %}\n{% /cards_item %}\n\n{% cards_item href=\"/docs/tooling/ai/quickstart-prompts/vue\" title=\"Vue\" icon=\"web-icon-vue\" %}\n{% /cards_item %}\n\n{% cards_item href=\"/docs/tooling/ai/quickstart-prompts/sveltekit\" title=\"SvelteKit\" icon=\"icon-svelte\" %}\n{% /cards_item %}\n\n{% cards_item href=\"/docs/tooling/ai/quickstart-prompts/angular\" title=\"Angular\" icon=\"icon-angular\" %}\n{% /cards_item %}\n\n{% cards_item href=\"/docs/tooling/ai/quickstart-prompts/nuxt\" title=\"Nuxt\" icon=\"web-icon-nuxt\" %}\n{% /cards_item %}\n\n{% cards_item href=\"/docs/tooling/ai/quickstart-prompts/solid\" title=\"Solid\" icon=\"icon-solidjs\" %}\n{% /cards_item %}\n\n{% cards_item href=\"/docs/tooling/ai/quickstart-prompts/refine\" title=\"Refine\" icon=\"web-icon-refine\" %}\n{% /cards_item %}\n\n{% cards_item href=\"/docs/tooling/ai/quickstart-prompts/tanstack-start\" title=\"TanStack Start\" icon=\"web-icon-tanstack\" %}\n{% /cards_item %}\n\n{% cards_item href=\"/docs/tooling/ai/quickstart-prompts/web\" title=\"Web\" icon=\"icon-js\" %}\n{% /cards_item %}\n\n{% /cards %}\n\n## Mobile {% #mobile %}\n\n{% cards %}\n\n{% cards_item href=\"/docs/tooling/ai/quickstart-prompts/react-native\" title=\"React Native\" icon=\"icon-react-native\" %}\n{% /cards_item %}\n\n{% cards_item href=\"/docs/tooling/ai/quickstart-prompts/flutter\" title=\"Flutter\" icon=\"icon-flutter\" %}\n{% /cards_item %}\n\n{% cards_item href=\"/docs/tooling/ai/quickstart-prompts/android-kotlin\" title=\"Android (Kotlin)\" icon=\"icon-android\" %}\n{% /cards_item %}\n\n{% cards_item href=\"/docs/tooling/ai/quickstart-prompts/android-java\" title=\"Android (Java)\" icon=\"icon-android\" %}\n{% /cards_item %}\n\n{% cards_item href=\"/docs/tooling/ai/quickstart-prompts/apple\" title=\"Apple (Swift)\" icon=\"icon-apple\" %}\n{% /cards_item %}\n\n{% /cards %}\n\n## Server {% #server %}\n\n{% cards %}\n\n{% cards_item href=\"/docs/tooling/ai/quickstart-prompts/node\" title=\"Node.js\" icon=\"icon-node_js\" %}\n{% /cards_item %}\n\n{% cards_item href=\"/docs/tooling/ai/quickstart-prompts/python\" title=\"Python\" icon=\"icon-python\" %}\n{% /cards_item %}\n\n{% cards_item href=\"/docs/tooling/ai/quickstart-prompts/php\" title=\"PHP\" icon=\"icon-php\" %}\n{% /cards_item %}\n\n{% cards_item href=\"/docs/tooling/ai/quickstart-prompts/dart\" title=\"Dart\" icon=\"icon-dart\" %}\n{% /cards_item %}\n\n{% cards_item href=\"/docs/tooling/ai/quickstart-prompts/ruby\" title=\"Ruby\" icon=\"icon-ruby\" %}\n{% /cards_item %}\n\n{% cards_item href=\"/docs/tooling/ai/quickstart-prompts/deno\" title=\"Deno\" icon=\"icon-deno\" %}\n{% /cards_item %}\n\n{% cards_item href=\"/docs/tooling/ai/quickstart-prompts/go\" title=\"Go\" icon=\"icon-go\" %}\n{% /cards_item %}\n\n{% cards_item href=\"/docs/tooling/ai/quickstart-prompts/dotnet\" title=\".NET\" icon=\"icon-dotnet\" %}\n{% /cards_item %}\n\n{% cards_item href=\"/docs/tooling/ai/quickstart-prompts/swift\" title=\"Swift\" icon=\"icon-swift\" %}\n{% /cards_item %}\n\n{% cards_item href=\"/docs/tooling/ai/quickstart-prompts/kotlin\" title=\"Kotlin\" icon=\"icon-kotlin\" %}\n{% /cards_item %}\n\n{% cards_item href=\"/docs/tooling/ai/quickstart-prompts/rust\" title=\"Rust\" icon=\"web-icon-rust\" %}\n{% /cards_item %}\n\n{% /cards %}"}, {"path": "docs/tooling/ai/quickstart-prompts/android-java", "title": "Android (Java)", "description": "Quickstart prompt for integrating Appwrite with Android using Java.", "content": "{% prompt_content /%}"}, {"path": "docs/tooling/ai/quickstart-prompts/android-kotlin", "title": "Android (Kotlin)", "description": "Quickstart prompt for integrating Appwrite with Android using Kotlin.", "content": "{% prompt_content /%}"}, {"path": "docs/tooling/ai/quickstart-prompts/angular", "title": "Angular", "description": "Quickstart prompt for integrating Appwrite with Angular.", "content": "{% prompt_content /%}"}, {"path": "docs/tooling/ai/quickstart-prompts/apple", "title": "Apple (Swift)", "description": "Quickstart prompt for integrating Appwrite with Apple platforms using Swift.", "content": "{% prompt_content /%}"}, {"path": "docs/tooling/ai/quickstart-prompts/dart", "title": "Dart", "description": "Quickstart prompt for integrating Appwrite with Dart.", "content": "{% prompt_content /%}"}, {"path": "docs/tooling/ai/quickstart-prompts/deno", "title": "Deno", "description": "Quickstart prompt for integrating Appwrite with Deno.", "content": "{% prompt_content /%}"}, {"path": "docs/tooling/ai/quickstart-prompts/dotnet", "title": ".NET", "description": "Quickstart prompt for integrating Appwrite with .NET.", "content": "{% prompt_content /%}"}, {"path": "docs/tooling/ai/quickstart-prompts/flutter", "title": "Flutter", "description": "Quickstart prompt for integrating Appwrite with Flutter.", "content": "{% prompt_content /%}"}, {"path": "docs/tooling/ai/quickstart-prompts/go", "title": "Go", "description": "Quickstart prompt for integrating Appwrite with Go.", "content": "{% prompt_content /%}"}, {"path": "docs/tooling/ai/quickstart-prompts/kotlin", "title": "Kotlin", "description": "Quickstart prompt for integrating Appwrite with Kotlin.", "content": "{% prompt_content /%}"}, {"path": "docs/tooling/ai/quickstart-prompts/nextjs", "title": "Next.js", "description": "Quickstart prompt for integrating Appwrite with Next.js.", "content": "{% prompt_content /%}"}, {"path": "docs/tooling/ai/quickstart-prompts/node", "title": "Node.js", "description": "Quickstart prompt for integrating Appwrite with Node.js.", "content": "{% prompt_content /%}"}, {"path": "docs/tooling/ai/quickstart-prompts/nuxt", "title": "Nuxt", "description": "Quickstart prompt for integrating Appwrite with Nuxt.", "content": "{% prompt_content /%}"}, {"path": "docs/tooling/ai/quickstart-prompts/php", "title": "PHP", "description": "Quickstart prompt for integrating Appwrite with PHP.", "content": "{% prompt_content /%}"}, {"path": "docs/tooling/ai/quickstart-prompts/python", "title": "Python", "description": "Quickstart prompt for integrating Appwrite with Python.", "content": "{% prompt_content /%}"}, {"path": "docs/tooling/ai/quickstart-prompts/react", "title": "React", "description": "Quickstart prompt for integrating Appwrite with React.", "content": "{% prompt_content /%}"}, {"path": "docs/tooling/ai/quickstart-prompts/react-native", "title": "React Native", "description": "Quickstart prompt for integrating Appwrite with React Native.", "content": "{% prompt_content /%}"}, {"path": "docs/tooling/ai/quickstart-prompts/refine", "title": "Refine", "description": "Quickstart prompt for integrating Appwrite with Refine.", "content": "{% prompt_content /%}"}, {"path": "docs/tooling/ai/quickstart-prompts/ruby", "title": "Ruby", "description": "Quickstart prompt for integrating Appwrite with Ruby.", "content": "{% prompt_content /%}"}, {"path": "docs/tooling/ai/quickstart-prompts/rust", "title": "Rust", "description": "Quickstart prompt for integrating Appwrite with Rust.", "content": "{% prompt_content /%}"}, {"path": "docs/tooling/ai/quickstart-prompts/solid", "title": "Solid", "description": "Quickstart prompt for integrating Appwrite with Solid.", "content": "{% prompt_content /%}"}, {"path": "docs/tooling/ai/quickstart-prompts/sveltekit", "title": "SvelteKit", "description": "Quickstart prompt for integrating Appwrite with SvelteKit.", "content": "{% prompt_content /%}"}, {"path": "docs/tooling/ai/quickstart-prompts/swift", "title": "Swift", "description": "Quickstart prompt for integrating Appwrite with Swift.", "content": "{% prompt_content /%}"}, {"path": "docs/tooling/ai/quickstart-prompts/tanstack-start", "title": "TanStack Start", "description": "Quickstart prompt for integrating Appwrite with TanStack Start.", "content": "{% prompt_content /%}"}, {"path": "docs/tooling/ai/quickstart-prompts/vue", "title": "Vue", "description": "Quickstart prompt for integrating Appwrite with Vue.", "content": "{% prompt_content /%}"}, {"path": "docs/tooling/ai/quickstart-prompts/web", "title": "Web", "description": "Quickstart prompt for integrating Appwrite with Web.", "content": "{% prompt_content /%}"}, {"path": "docs/tooling/ai/responsible-ai", "title": "Responsible AI", "description": "Best practices for responsible AI usage with Appwrite. Learn how to protect user data, secure API keys, and build transparent AI-powered applications.", "content": "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.\n\n# Protect user data {% #protect-user-data %}\n\nWhen sending data to AI providers like OpenAI, Anthropic, or others, be mindful of what information leaves your application.\n\n- **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.\n- **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.\n- **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.\n\n# Secure your API keys {% #secure-api-keys %}\n\nAI provider API keys grant access to paid services and should be treated with the same care as any other secret.\n\n- **Store API keys as environment variables** in your Appwrite Functions. Never hardcode keys in your source code or expose them to the client side.\n- **Use scoped keys** when your AI provider supports them. Restrict keys to only the permissions and models your application needs.\n- **Rotate keys regularly** and revoke any keys that may have been exposed.\n\nLearn more about managing secrets in [Appwrite Functions](/docs/products/functions/develop#environment-variables).\n\n# Validate inputs and outputs {% #validate-inputs-outputs %}\n\nAI models can produce unexpected or inappropriate results. Build safeguards into your application to handle these cases.\n\n- **Validate user inputs** before sending them to an AI provider. Set character limits, sanitize content, and reject malicious prompts to prevent prompt injection attacks.\n- **Review AI outputs** before displaying them to users or storing them in your database. Implement content filters or moderation layers for user-facing features.\n- **Handle errors gracefully** when AI providers are unavailable or return unexpected responses. Your application should function even when AI features fail.\n\n# Be transparent with users {% #transparency %}\n\nUsers should understand when they are interacting with AI-generated content or AI-powered features.\n\n- **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.\n- **Provide opt-out options** where possible. Give users control over whether their data is used in AI-powered features.\n- **Set expectations** about AI limitations. AI-generated content can be inaccurate, and users should understand that responses may not always be correct.\n\n# AI-assisted development {% #ai-assisted-development %}\n\nWhen using AI development tools like [Cursor, VS Code, or Claude Code](/docs/tooling/ai/) to build with Appwrite, keep the following in mind.\n\n- **Review generated code** before committing. AI-generated code may contain security vulnerabilities, incorrect API usage, or outdated patterns.\n- **Keep API keys out of prompts** when chatting with AI assistants. Avoid pasting secrets, credentials, or sensitive configuration into AI chat interfaces.\n- **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."}, {"path": "docs/tooling/ai/skills", "title": "Agent skills", "description": "Install Appwrite skills to give AI agents pre-built knowledge of Appwrite SDKs and services for your preferred language.", "content": "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/agent-skills/).\n\nSkills 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:\n\n- Claude Code\n- Codex\n- Cursor\n- Google Antigravity\n- OpenCode\n- Zed\n- *and more...*\n\n# Benefits {% #benefits %}\n\n- **Accurate code generation**: Skills provide AI agents with correct SDK usage patterns, method signatures, and best practices for your chosen language.\n- **No manual context needed**: Instead of pasting documentation into prompts, skills give agents the context they need automatically.\n- **Language-specific**: Each skill is tailored to a specific SDK, so agents generate idiomatic code for your language and framework.\n- **Always up to date**: Skills are maintained alongside the SDKs, so agents always have access to the latest APIs and patterns.\n- **Works with any compatible agent**: Skills are not locked to a single tool. Install once and use across any AI agent that supports them.\n\n# Install skills {% #install-skills %}\n\n{% info title=\"Automatic installation\" %}\nWhen 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.\n{% /info %}\n\n{% section #step-1 step=1 title=\"Run the install command\" %}\nRun the following command in your project directory:\n\n```bash\nnpx skills add appwrite/agent-skills\n```\n{% /section %}\n\n{% section #step-2 step=2 title=\"Select skills\" %}\nYou will be prompted to select which skills to install. Skills are available for the Appwrite CLI and the following SDKs:\n\n- TypeScript\n- Dart\n- .NET\n- Go\n- Kotlin\n- PHP\n- Python\n- Ruby\n- Swift\n\nSelect the skills that match the SDKs you use in your project.\n\n![Skill selection prompt](/images/docs/skills/skills.avif)\n{% /section %}\n\n{% section #step-3 step=3 title=\"Select tools\" %}\nChoose which AI tools should use the installed skills. This configures the skills for the agents you work with.\n\n![Tool selection prompt](/images/docs/skills/tools.avif)\n{% /section %}\n\n{% section #step-4 step=4 title=\"Select scope\" %}\nChoose whether to install the skills at the **project** level or **globally**:\n\n- **Project**: Skills are available only in the current project. This is useful when different projects use different Appwrite SDKs.\n- **Global**: Skills are available across all your projects. This is useful if you use the same SDKs everywhere.\n\n![Scope selection prompt](/images/docs/skills/scope.avif)\n{% /section %}\n\n{% section #step-5 step=5 title=\"Choose installation method\" %}\nSelect **symlink** as the installation method. This creates a symbolic link to the skills, so they stay in sync across all your AI tools.\n\n![Installation method prompt](/images/docs/skills/method.avif)\n{% /section %}"}, {"path": "docs/tooling/ai/vector-db-and-embeddings", "title": "Vector DB and embeddings", "description": "Using vector databases and embeddings with Appwrite.", "content": "Vector databases store high-dimensional vectors (embeddings) that represent text, images, or other data. They enable semantic search, where results are based on meaning rather than exact keyword matches. This makes them essential for AI applications like recommendation systems, search engines, and retrieval-augmented generation (RAG).\n\nEmbeddings are numerical representations of data that capture semantic meaning. Text with similar meanings will have embeddings that are close together in vector space. Appwrite integrates with vector databases through Functions, allowing you to index your data and perform similarity searches.\n\nThis guide uses Pinecone as the vector database, but the patterns apply to other providers like Weaviate, Milvus, Qdrant, Chroma, and Upstash Vector.\n\n# Prerequisites {% #prerequisites %}\n\n- An Appwrite project\n- An Appwrite table\n- An [OpenAI API key](https://platform.openai.com/account/api-keys)\n- A [Pinecone API key](https://docs.pinecone.io/guides/getting-started/quickstart#2-get-your-api-key)\n- A Pinecone index\n\n{% section #step-1 step=1 title=\"Create new function\" %}\nHead 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.\n\n{% only_dark %}\n![Create function screen](/images/docs/functions/dark/template.avif)\n{% /only_dark %}\n\n{% only_light %}\n![Create function screen](/images/docs/functions/template.avif)\n{% /only_light %}\n\n1. In the Appwrite Console's sidebar, click **Functions**.\n1. Click **Create function**.\n1. Under **Connect Git repository**, select your provider.\n1. After connecting to GitHub, under **Quick start**, select the **Node.js** starter template.\n1. 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**.\n1. Follow the step-by-step wizard and create the function.\n{% /section %}\n\n{% section #step-2 step=2 title=\"Add dependencies\" %}\nOnce the function is created, navigate to the freshly created repository and clone it to your local machine.\n\nInstall 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.\n\n```bash\nnpm install @pinecone-database/pinecone openai\n```\n{% /section %}\n\n{% section #step-3 step=3 title=\"Create utility function\" %}\nFor this example, the function will be able to take both `GET` and `POST` requests.\n\nCreate a new `src/utils.js` file with the following code:\n\n```js\nimport path from 'path';\nimport { fileURLToPath } from 'url';\nimport fs from 'fs';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\nconst staticFolder = path.join(__dirname, '../static');\n\nexport function getStaticFile(fileName) {\n return fs.readFileSync(path.join(staticFolder, fileName)).toString();\n}\n```\n{% /section %}\n\n{% section #step-4 step=4 title=\"Handle GET request\" %}\nWrite the `GET` request handler in the `src/main.js` file.\n\n```js\nimport { getStaticFile } from './utils.js';\n\nexport default async ({ req, res, error }) => {\n if (req.method === 'GET') {\n const html = getStaticFile('index.html');\n return res.text(html, 200, { 'Content-Type': 'text/html; charset=utf-8' });\n }\n};\n```\n\nThe code checks if all required environment variables are present and then returns the static HTML page when a `GET` request is made.\n{% /section %}\n\n{% section #step-5 step=5 title=\"Create web page\" %}\nCreate a HTML web page that the function will serve. Create a new file at `static/index.html` with some HTML boilerplate:\n\n```html\n\n\n\n```\n\nWithin the `` tag, Add a `` tag that will define the style and scripts.\n\n```html\n\n \n \n \n Pinecone Demo\n\n \n \n\n \n \n\n \n```\n\nAnd after the `` tag add this `` which will contain the actual form:\n\n```html\n\n
\n
\n
\n \n

Pinecone Demo

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

\n
\n \n { results = await onSearch(value) })\"\n >\n
\n
\n \n \n
\n
\n
\n
\n \n
\n \n \n
\n \n\n```\n\nThis will render a form that will submit your search query to the function and display the results.\n{% /section %}\n\n{% section #step-6 step=6 title=\"Setup SDKs\" %}\nAdd methods necessary to integrate with the OpenAI and Pinecone APIs\n\nImport `openai` and `@pinecone-database/pinecone` at the top of the `main.js` file:\n\n```js\nimport { Pinecone } from '@pinecone-database/pinecone';\nimport { OpenAI } from 'openai';\n```\n\nAdd the following code at the end of request handler in the `main.js` file:\n\n```js\nconst openai = new OpenAI();\n\nconst pinecone = new Pinecone();\nconst pineconeIndex = pinecone.index(process.env.PINECONE_INDEX_ID);\n```\n\nThe functions checks the request method, and then initializes the OpenAI and Pinecone SDKs.\n{% /section %}\n\n{% section #step-7 step=7 title=\"Handle search requests\" %}\nTo handle the search requests, add the following code to the end of the request handler in the `main.js` file:\n\n```js\nif (req.path === '/search') {\n const queryEmbedding = await openai.embeddings.create({\n model: 'text-embedding-ada-002',\n input: req.body.prompt,\n });\n\n const searchResults = await pineconeIndex.query({\n vector: queryEmbedding.data[0].embedding,\n topK: 5,\n });\n\n return res.json(searchResults);\n}\n```\n\nFor 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.\n{% /section %}\n\n{% section #step-8 step=8 title=\"Handle indexing requests\" %}\nThe Appwrite table needs to be indexed into the Pinecone index. Create a new file at `src/appwrite.js` with the following code:\n\n```js\nimport { Client, TablesDB, Query } from 'node-appwrite';\n\nexport default class AppwriteService {\n constructor() {\n const client = new Client();\n client\n .setEndpoint(\n process.env.APPWRITE_ENDPOINT ?? 'https://.cloud.appwrite.io/v1'\n )\n .setProject(process.env.APPWRITE_FUNCTION_PROJECT_ID)\n .setKey(process.env.APPWRITE_API_KEY);\n\n this.tablesDB = new TablesDB(client);\n }\n\n async getAllRows(databaseId, tableId) {\n const cumulative = [];\n\n let cursor = null;\n do {\n const queries = [Query.limit(100)];\n\n if (cursor) {\n queries.push(Query.cursorAfter(cursor));\n }\n\n const { rows } = await this.tablesDB.listRows({\n databaseId,\n tableId,\n queries\n });\n\n if (rows.length === 0) {\n break;\n }\n\n cursor = rows[rows.length - 1].$id;\n\n cumulative.push(...rows);\n } while (cursor);\n\n return cumulative;\n }\n}\n```\n\nThe service provides a method to iterate the rows contained within an entire table, fetching the limit of 100 rows per request.\n\n```js\nconst appwrite = new AppwriteService();\n\nconst rows = await appwrite.getAllRows(\n process.env.APPWRITE_DATABASE_ID,\n process.env.APPWRITE_TABLE_ID\n);\n\nconst embeddings = await Promise.all(\n rows.map(async (row) => {\n const record = await openai.embeddings.create({\n model: 'text-embedding-ada-002',\n input: JSON.stringify(row),\n });\n return {\n id: row.$id,\n values: record.data[0].embedding,\n metadata: row,\n };\n })\n);\n\nawait pineconeIndex.upsert(embeddings);\n```\n\nThe 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.\n{% /section %}\n\n{% section #step-9 step=9 title=\"Test the function\" %}\nNow that the function is deployed, test it by visiting the function URL in your browser.\nThis 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.\n\n![Pinecone search demo](/images/docs/ai/vector-db/search-demo.avif)\n{% /section %}"}, {"path": "docs/tooling/ai/vibe-coding/bolt", "title": "Bolt", "description": "Learn how to connect the Appwrite docs MCP server to Bolt for AI-assisted development with access to Appwrite documentation.", "content": "{% section #add-mcp-server step=1 title=\"Add MCP server\" %}\n\nTo connect the Appwrite docs MCP server to Bolt:\n\n1. Go to **Settings** → **Connectors (MCP)**.\n2. Click **Custom MCP server**.\n\n{% only_dark %}\n![Bolt MCP settings](/images/docs/mcp/bolt/bolt-mcp-settings.avif)\n{% /only_dark %}\n{% only_light %}\n![Bolt MCP settings](/images/docs/mcp/bolt/light/bolt-mcp-settings.avif)\n{% /only_light %}\n\n3. Enter the following details:\n - **Name**: `Appwrite Docs`\n - **URL**: `https://mcp-for-docs.appwrite.io`\n - **Transport Type**: HTTP\n - **Authentication**: None\n4. Click **Add MCP server**.\n\n{% only_dark %}\n![Bolt add MCP server](/images/docs/mcp/bolt/bolt-add-mcp.avif)\n{% /only_dark %}\n{% only_light %}\n![Bolt add MCP server](/images/docs/mcp/bolt/light/bolt-add-mcp.avif)\n{% /only_light %}\n\nThe Appwrite docs MCP server will now be available in your Bolt projects.\n\n{% /section %}\n\n{% section #test-the-integration step=2 title=\"Test the integration\" %}\n\nOnce connected, Bolt has access to Appwrite documentation context. You can use prompts like:\n\n**Example prompts:**\n\n- `How do I set up real-time subscriptions in Appwrite?`\n- `Show me how to authenticate users with OAuth`\n- `What are the best practices for database queries?`\n- `How do I implement file uploads with Appwrite Storage?`\n- `Show me an example of using Appwrite Functions`\n\n{% /section %}"}, {"path": "docs/tooling/ai/vibe-coding/claude-desktop", "title": "Claude Desktop", "description": "Learn how to use Claude Desktop with Appwrite through quick start prompts and MCP servers for AI-assisted development.", "content": "{% section #quick-start-prompts step=1 title=\"Quick start prompts\" %}\n\nGet started quickly with these pre-built prompts for common Appwrite integrations:\n\n{% cards %}\n{% cards_item href=\"/docs/tooling/ai/quickstart-prompts/nextjs\" title=\"Next.js\" icon=\"icon-nextjs\" %}\n{% /cards_item %}\n{% cards_item href=\"/docs/tooling/ai/quickstart-prompts/tanstack-start\" title=\"TanStack Start\" icon=\"web-icon-tanstack\" %}\n{% /cards_item %}\n{% /cards %}\n\n{% arrow_link href=\"/docs/tooling/ai/quickstart-prompts\" %}\nBrowse all quick start prompts\n{% /arrow_link %}\n\n{% /section %}\n\n{% section #step-2 step=2 title=\"Add MCP servers\" %}\n\nConnect Appwrite MCP servers to Claude Desktop for deeper integration with the Appwrite API and documentation.\n\nBefore you begin, ensure you have the following **pre-requisites** installed on your system:\n\n{% tabs %}\n{% tabsitem #api-server-prerequisites title=\"API server\" %}\n\n[uv](https://docs.astral.sh/uv/getting-started/installation/) must be installed on your system.\n\n{% /tabsitem %}\n\n{% tabsitem #docs-server-prerequisites title=\"Docs server\" %}\n\n[Node.js](https://nodejs.org/en/download) and npm must be installed on your system.\n\n{% /tabsitem %}\n{% /tabs %}\n\nIn the Claude Desktop app, open the app's **Settings** page (press `CTRL + ,` on Windows or `CMD + ,` on MacOS) and head to the **Developer** tab.\n\n![Claude Settings](/images/docs/mcp/claude-desktop/claude-settings.avif)\n\nClicking 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.\n\nChoose which MCP server you want to configure:\n\n{% tabs %}\n{% tabsitem #api-only title=\"API server\" %}\n\nAdd the API server to your configuration:\n\n```json\n{\n \"mcpServers\": {\n \"appwrite-api\": {\n \"command\": \"uvx\",\n \"args\": [\n \"mcp-server-appwrite\"\n ],\n \"env\": {\n \"APPWRITE_PROJECT_ID\": \"your-project-id\",\n \"APPWRITE_API_KEY\": \"your-api-key\",\n \"APPWRITE_ENDPOINT\": \"https://.cloud.appwrite.io/v1\"\n }\n }\n }\n}\n```\n\n**Configuration:**\n\n- Replace `your-project-id` with your actual Appwrite project ID\n- Replace `your-api-key` with your Appwrite API key\n- Replace `` with your Appwrite Cloud region (e.g., `nyc`, `fra`)\n\n{% /tabsitem %}\n\n{% tabsitem #docs-only title=\"Docs server\" %}\n\nAdd the docs server to your configuration:\n\n```json\n{\n \"mcpServers\": {\n \"appwrite-docs\": {\n \"command\": \"npx\",\n \"args\": [\n \"mcp-remote\",\n \"https://mcp-for-docs.appwrite.io\"\n ]\n }\n }\n}\n```\n\n**Why do we use the `mcp-remote` package?**\n\nUnlike other IDEs, Claude Desktop only supports local (stdio) MCP servers and not remote servers. The `mcp-remote` package acts as a proxy to connect to the docs MCP server.\n\n{% /tabsitem %}\n{% /tabs %}\n\n{% /section %}\n\n{% section #step-3 step=3 title=\"Verify MCP tools\" %}\n\nRestart 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.\n\n![Appwrite MCP tools](/images/docs/mcp/claude-desktop/claude-mcp-tools.avif)\n\n{% info title=\"uvx ENOENT error\" %}\n\nIn case you see a `uvx ENOENT` error, ensure that you either add `uvx` to the `PATH` environment variable on your system or use the full path to your `uvx` installation in the config file.\n\n{% /info %}\n\n{% /section %}\n\n{% section #step-4 step=4 title=\"Test the integration\" %}\n\nTry out the following example prompts based on the MCP server you have configured:\n\n{% tabs %}\n{% tabsitem #test-api title=\"API server\" %}\n\n**Example prompts:**\n\n- `Create a new user in my Appwrite project`\n- `List all databases in my project`\n- `Show me the collections in my database`\n- `Create a new document in my collection`\n- `Delete a specific user by ID`\n\n{% /tabsitem %}\n\n{% tabsitem #test-docs title=\"Docs server\" %}\n\n**Example prompts:**\n\n- `How do I set up real-time subscriptions in Appwrite?`\n- `Show me how to authenticate users with OAuth`\n- `What are the best practices for database queries?`\n- `How do I implement file uploads with Appwrite Storage?`\n- `Show me an example of using Appwrite Functions`\n\n{% /tabsitem %}\n\n{% /tabs %}\n\n![List users in Appwrite project](/images/docs/mcp/claude-desktop/claude-list-users.avif)\n\n{% /section %}"}, {"path": "docs/tooling/ai/vibe-coding/emergent", "title": "Emergent", "description": "Learn how to connect Appwrite MCP servers to Emergent for AI-assisted development with access to the Appwrite API and documentation.", "content": "{% section #add-mcp-server step=1 title=\"Add MCP server\" %}\n\nTo connect Appwrite MCP servers to Emergent:\n\n1. On the homepage, click **Advanced Controls**.\n2. Click **Select MCP Tools**.\n3. Click **New MCP Server**.\n4. Enter a name for your server (e.g., `appwrite` or `appwrite-docs`).\n5. Paste one of the following JSON configurations:\n\n{% tabs %}\n{% tabsitem #api-server title=\"API server\" %}\n\n```json\n{\n \"mcpServers\": {\n \"appwrite-api\": {\n \"command\": \"uvx\",\n \"args\": [\n \"mcp-server-appwrite\"\n ],\n \"env\": {\n \"APPWRITE_PROJECT_ID\": \"your-project-id\",\n \"APPWRITE_API_KEY\": \"your-api-key\",\n \"APPWRITE_ENDPOINT\": \"https://.cloud.appwrite.io/v1\"\n }\n }\n }\n}\n\n```\n\n**Configuration:**\n\n- Replace `your-project-id` with your actual Appwrite project ID\n- Replace `your-api-key` with your Appwrite API key\n- Replace `` with your Appwrite Cloud region (e.g., `fra`, `nyc`)\n\n{% /tabsitem %}\n\n{% tabsitem #docs-server title=\"Docs server\" %}\n\n```json\n{\n \"mcpServers\": {\n \"appwrite-docs\": {\n \"command\": \"npx\",\n \"args\": [\n \"mcp-remote\",\n \"\"\n ]\n }\n }\n}\n\n```\n\n{% /tabsitem %}\n{% /tabs %}\n\n{% /section %}\n\n{% section #test-the-integration step=2 title=\"Test the integration\" %}\n\nOnce connected, you can use natural language to interact with Appwrite. Try prompts like:\n\n{% tabs %}\n{% tabsitem #test-api title=\"API server\" %}\n\n**Example prompts:**\n\n- `Create a new user in my Appwrite project`\n- `List all databases in my project`\n- `Show me the collections in my database`\n- `Create a new document in my collection`\n- `Delete a specific user by ID`\n\n{% /tabsitem %}\n\n{% tabsitem #test-docs title=\"Docs server\" %}\n\n**Example prompts:**\n\n- `How do I set up real-time subscriptions in Appwrite?`\n- `Show me how to authenticate users with OAuth`\n- `What are the best practices for database queries?`\n- `How do I implement file uploads with Appwrite Storage?`\n- `Show me an example of using Appwrite Functions`\n\n{% /tabsitem %}\n{% /tabs %}\n\n{% /section %}"}, {"path": "docs/tooling/ai/vibe-coding/lovable", "title": "Lovable", "description": "Learn how to connect the Appwrite docs MCP server to Lovable for AI-assisted development with access to Appwrite documentation.", "content": "{% section #add-mcp-server step=1 title=\"Add MCP server\" %}\n\nTo connect the Appwrite docs MCP server to Lovable:\n\n1. Go to **Settings** → **Connectors** → **Personal connectors**.\n2. Click **New MCP server**.\n3. Enter the following details:\n - **Server name**: `Appwrite Docs`\n - **Server URL**: `https://mcp-for-docs.appwrite.io`\n - **Authentication**: Select **No authentication**\n4. Click **Add server**.\n\nThe Appwrite docs MCP server will now appear in your list of personal connectors.\n\n{% /section %}\n\n{% section #test-the-integration step=2 title=\"Test the integration\" %}\n\nOnce connected, Lovable has access to Appwrite documentation context. You can use prompts like:\n\n**Example prompts:**\n\n- `How do I set up real-time subscriptions in Appwrite?`\n- `Show me how to authenticate users with OAuth`\n- `What are the best practices for database queries?`\n- `How do I implement file uploads with Appwrite Storage?`\n- `Show me an example of using Appwrite Functions`\n\n{% /section %}"}, {"path": "docs/tooling/ai/vibe-coding/zenflow", "title": "Zenflow", "description": "Learn how to add the Appwrite MCP servers to agents in Zenflow to interact with both the Appwrite API and documentation.", "content": "{% section #quick-start-prompts step=1 title=\"Quick start prompts\" %}\n\nGet started quickly with these pre-built prompts for common Appwrite integrations:\n\n{% cards %}\n{% cards_item href=\"/docs/tooling/ai/quickstart-prompts/nextjs\" title=\"Next.js\" icon=\"icon-nextjs\" %}\n{% /cards_item %}\n{% cards_item href=\"/docs/tooling/ai/quickstart-prompts/tanstack-start\" title=\"TanStack Start\" icon=\"web-icon-tanstack\" %}\n{% /cards_item %}\n{% /cards %}\n\n{% arrow_link href=\"/docs/tooling/ai/quickstart-prompts\" %}\nBrowse all quick start prompts\n{% /arrow_link %}\n\n{% /section %}\n\n{% section #step-2 step=2 title=\"Add MCP servers\" %}\n\nConnect Appwrite MCP servers to Zenflow for deeper integration with the Appwrite API and documentation.\n\nBefore you begin, ensure you have the following **pre-requisites** installed on your system:\n{% tabs %}\n{% tabsitem #api-server-prerequisites title=\"API server\" %}\n\n[uv](https://docs.astral.sh/uv/getting-started/installation/) must be installed on your system.\n\n{% /tabsitem %}\n\n{% tabsitem #docs-server-prerequisites title=\"Docs server\" %}\n\n[Node.js](https://nodejs.org/en/download) and npm must be installed on your system.\n\n{% /tabsitem %}\n{% /tabs %}\n\nTo 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.\n\n{% tabs %}\n{% tabsitem #api-only title=\"API server\" %}\n\n```json\n{\n \"mcpServers\": {\n \"appwrite-api\": {\n \"command\": \"uvx\",\n \"args\": [\n \"mcp-server-appwrite\"\n ],\n \"env\": {\n \"APPWRITE_PROJECT_ID\": \"your-project-id\",\n \"APPWRITE_API_KEY\": \"your-api-key\",\n \"APPWRITE_ENDPOINT\": \"https://.cloud.appwrite.io/v1\"\n }\n }\n }\n}\n```\n\n**Configuration:**\n- Replace `your-project-id` with your actual Appwrite project ID\n- Replace `your-api-key` with your Appwrite API key\n- Replace `` with your Appwrite Cloud region (e.g., `nyc`, `fra`)\n\n{% /tabsitem %}\n\n{% tabsitem #docs-only title=\"Docs server\" %}\n\nUpdate the\n```json\n{\n \"mcpServers\": {}\n}\n```\nto include the docs server:\n\n```json\n{\n \"mcpServers\": {\n \"appwrite-docs\": {\n \"url\": \"https://mcp-for-docs.appwrite.io\",\n \"type\": \"http\"\n }\n }\n}\n```\n\n{% /tabsitem %}\n{% /tabs %}\n\nClick **Save**. Once you save the configuration, Zenflow will connect with the MCP server(s) and load all available tools.\n\n{% /section %}\n\n{% section #step-3 step=3 title=\"Test the integration\" %}\n\nOpen **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**.\nIf 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).\n\nYou can try out the following example prompts based on the MCP server you have configured:\n\n{% tabs %}\n{% tabsitem #test-api title=\"API server\" %}\n\n**Example prompts:**\n- `Create a new user in my Appwrite project`\n- `List all databases in my project`\n- `Show me the collections in my database`\n- `Create a new document in my collection`\n- `Delete a specific user by ID`\n\n{% /tabsitem %}\n\n{% tabsitem #test-docs title=\"Docs server\" %}\n\n**Example prompts:**\n- `How do I set up real-time subscriptions in Appwrite?`\n- `Show me how to authenticate users with OAuth`\n- `What are the best practices for database queries?`\n- `How do I implement file uploads with Appwrite Storage?`\n- `Show me an example of using Appwrite Functions`\n\n{% /tabsitem %}\n\n{% /tabs %}\n\n![Search for portfolio site in Appwrite project](/images/docs/mcp/zenflow/zenflow-chat.avif)\n\n{% /section %}"}, {"path": "docs/tooling/appwriter", "title": "The Appwriter", "description": "Learn about the custom Appwriter mechanical keyboard and its specifications", "content": "{% only_dark %}\n![The Appwriter](/images/docs/keyboard/dark/appwriter.avif)\n{% /only_dark %}\n\n{% only_light %}\n![The Appwriter](/images/docs/keyboard/appwriter.avif)\n{% /only_light %}\n\nThe 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.\n\nThe 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.\n\n# What's in the box {% #whats-in-the-box %}\n\n- Appwriter keyboard\n- USB-C data/charging cable\n- 2-in-1 keycap and switch puller\n- Extra PC layout keycaps\n- Information card with a QR code\n\nThe keyboard comes with Mac layout keys pre-installed for macOS which can also be swapped with the included PC keycaps.\n\n# Specifications {% #specifications %}\n\n|Specification|Details|\n|----|----|\n|Battery|3000mAh rechargeable battery|\n|Design|75% compact design keyboard|\n|Keycaps|84 custom dye sublimated PBT keycaps|\n|Switches|Gateron G Pro yellow pre-lubed switches|\n|Connections|Tri-mode connectivity (2.4Ghz wireless / bluetooth / wired USB-C)|\n|Case|Noise dampening case-foam|\n|RGB backlight|16 Million colors & multiple lighting effects|\n|Switch support|Hot-swappable switches with 3 & 5 pin|\n|Rollover|N-key rollover|\n\n# Quick start {% #quick-start %}\n\nThere 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.\n\n## Wired mode {% #wired-mode %}\n\nTo use the wired mode, connect the USB-C data cable from the keyboard to the computer and push the Appwriter switch towards the T.\nWhen connecting the keyboard for the first time using wired mode, your computer will install all required drivers on a successful connection.\n\nThe 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.\n\n## Wireless mode {% #wireless-mode %}\n\nPush 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.\n\n## Bluetooth mode {% #bluetooth-mode %}\n\nTo switch the keyboard to Bluetooth mode, push the switch to B. The keyboard can pair to three different devices.\n\nTo 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.\n\nTo connect to a device, short press the `FN + 1/2/3` keys depending on the device you want to connect the keyboard with.\n\n# General keyboard controls {% #general-keyboard-controls %}\n\n|Shortcut|Action|\n|---|---|\n| `FN + Esc` | Hold for 3 seconds to reset to factory defaults |\n| `FN + Win` | Disable/enable Win key |\n| `FN + S` | Set keyboard to Mac mode |\n| `FN + A` | Set keyboard to Windows mode |\n| `FN + Backspace` | System power/sleep |\n| `FN + Delete` | Change keyboard backlight effect |\n| `FN + Home` | Change keyboard backlight color |\n| `FN + Pg Up` | Toggle keyboard backlight |\n| `FN + Up Arrow` | Increase keyboard brightness |\n| `FN + Down Arrow` | Decrease keyboard brightness |\n| `FN + Left Arrow` | Slow down keyboard backlight effect |\n| `FN + Right Arrow` | Speed up keyboard backlight effect |\n\n## Windows mode {% #windows-mode %}\n\nTo 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`.\n\n## MacOS mode {% #macos-mode %}\n\nTo 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.\n\n# How to get the Appwriter {% #how-to-get-the-appwriter %}\n\nThe Appwriter is available for preorder in the [Appwrite Store](https://appwrite.store/products/preorder-the-appwriter).\n\nYou can also participate in our giveaways and win the Appwriter.\nLook out for events & giveaways on our social media:\n- [Discord](https://appwrite.io/discord)\n- [Github](https://github.com/appwrite/appwrite)\n- [X](https://x.com/appwrite)\n- [YouTube](https://www.youtube.com/c/appwrite)"}, {"path": "docs/tooling/arena", "title": "Arena", "description": "An open-source benchmark that evaluates how well AI models understand Appwrite's services, SDKs, and APIs.", "content": "[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.\n\nArena 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.\n\nAll questions, answers, and scores are fully open source and available on [GitHub](https://github.com/appwrite/arena).\n\n# How it works {% #how-it-works %}\n\nArena evaluates each model using a pool of **191 questions** spanning **9 Appwrite service categories**:\n\n- Foundation\n- Auth\n- Databases\n- Functions\n- Storage\n- Sites\n- Messaging\n- Realtime\n- CLI\n\nEach model is tested in two contexts:\n\n- **Without Skills**: The model answers using only its built-in training data.\n- **With Skills**: The model answers with access to Appwrite's [skills files](/docs/tooling/skills), which provide up-to-date SDK and API context.\n\nThe gap between these two scores reveals how effectively a model can leverage provided documentation to improve its responses.\n\nAll benchmark runs are configured with:\n\n- **Temperature 0**: Makes the entire benchmark deterministic, so identical inputs always produce identical outputs.\n- **Extended thinking set to high**: Matches the configuration most commonly used in real-world programming contexts.\n\n# Scoring {% #scoring %}\n\nArena uses two complementary scoring methods to evaluate model performance:\n\n## Deterministic (MCQ) {% #deterministic %}\n\n165 multiple-choice questions structured like a typical exam, each with a single correct answer:\n\n- The model receives a system prompt and the question.\n- Four tools are provided, one for each possible answer.\n- The model submits its response by calling the correct tool, avoiding issues with verbose or unparseable output.\n\nScores are fully reproducible with no judge bias.\n\n## AI-judged (open-ended) {% #ai-judged %}\n\n26 open-ended questions scored from 0 to 1 by an AI judge using rubrics and reference answers:\n\n- Tests reasoning and real-world usage patterns that multiple-choice cannot capture.\n- Scores may have slight variance due to the nature of AI-based evaluation.\n\n# Use cases {% #use-cases %}\n\n- **Choosing a model**: Compare models to find the one that best fits your Appwrite development workflow and budget.\n- **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.\n- **Measuring skill impact**: See how much a model improves when given Appwrite skills, helping you decide whether to install skills for your AI tools.\n- **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.\n- **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.\n- **Contributing**: Since Arena is open source, you can submit new questions, suggest improvements to scoring rubrics, or add new models to the benchmark."}, {"path": "docs/tooling/command-center", "title": "Command Center", "description": "Appwrite Command Center enhances developer experience with AI, keyboard shortcuts, and context-aware search for efficient navigation and task execution.", "content": "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.\n\n{% only_dark %}\n![Command center](/images/docs/command-center/dark/command-center.avif)\n{% /only_dark %}\n{% only_light %}\n![Command center](/images/docs/command-center/command-center.avif)\n{% /only_light %}\n\n# Getting started {% #getting-started %}\n\nYou 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.\n\nThe 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.\n\n# Navigation {% #navigation %}\n\nThe 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.\n\n# Resource creation {% #resource-creation %}\n\nThe 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.\n\n# AI Assistant {% #ai-assistant %}\n\nAn 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.\n\n# Keyboard optimization {% #keyboard-optimization %}\n\nMany 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.\n\nYou 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."}, {"path": "docs/tooling/command-center/shortcuts", "title": "Keyboard shortcuts", "description": "Learn to navigate the Appwrite Console efficiently and effectively with your keyboard", "content": "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.\n\n# Shortcuts {% #shortcuts %}\n\nThe 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.\n\n## Global shortcuts {% #global-shortcuts %}\n\nDevelopers can also utilize global shortcuts anywhere in the Appwrite Console, allowing instant access to these features from any page on the Console.\n\n|Shortcut|Action|\n|----|----|\n|`T` then `L`|Set theme to light|\n|`T` then `D`|Set theme to dark|\n|`T` then `A`|Set theme to dark|\n|`A` then `I`|Open Appwrite Assistant|\n|`C` then `O`|Create organization|\n\n## Command center\n\nAll the Appwrite shortcuts can be found in the [command center](/docs/tooling/command-center).\nUse the command center to search for commands or the current page's content.\n\n|PC (Windows / Linux)|macOS|Action|\n|----|----|----|\n|`Ctrl` + `K`|`⌘` + `K`|Access Command Center|\n\n## Project shortcuts {% #project-shortcuts %}\n\nWithin the context of a project, developers can utilize project shortcuts. These shortcuts are automatically enabled once a project is selected on the Console. \nThese shortcuts allow quick access to the project's pages making it easier and faster to navigate the Console more.\n\n|Shortcut|Action|\n|----|----|\n|`G` then `P`|Go to projects|\n|`G` then `O`|Go to overview|\n|`G` then `A`|Go to auth|\n|`G` then `D`|Go to databases|\n|`G` then `F`|Go to functions|\n|`G` then `M`|Go to messaging|\n|`G` then `S`|Go to storage|\n|`G` then `E`|Go to settings|\n\n# Primary Actions\n\nEach 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.\n\n|Shortcut|Action|\n|----|----|\n|`C` then `P`|Create project|\n|`C` then `U`|Create user|\n|`C` then `A`|Create database|\n|`C` then `C`|Create table|\n|`C` then `D`|Create row|\n|`C` then `T`|Create column|\n|`C` then `F`|Create function|\n|`C` then `S`|Create storage|\n\n# Accessibility {% #accessibility %}\n\nUsing 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. \n\nGood 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."}, {"path": "docs/tooling/command-line/buckets", "title": "Buckets", "description": "Efficiently deploy your Appwrite buckets using the Command-Line Tool (CLI).", "content": "{% partial file=\"cli-disclaimer.md\" /%}\n\nThe Appwrite CLI allows you to configure and deploy buckets across projects. You can also configure your files using the CLI commands.\n\n# Initialize bucket {% #initialize-bucket %}\n\nCreate a new bucket using the following command:\n\n```sh\nappwrite init buckets\n```\n\n# Pull bucket {% #pull-bucket %}\n\nYou can also pull your existing Appwrite buckets from the Appwrite Console using the `pull` command in the folder containing your `appwrite.config.json` file.\n\n```sh\nappwrite pull buckets\n```\n\n# appwrite.config.json {% #appwritejson %}\n\nAfter [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:\n\n```json\n{\n \"projectId\": \"\",\n \"endpoint\": \"https://.cloud.appwrite.io/v1\",\n \"buckets\": [\n {\n \"$id\": \"\",\n \"$createdAt\": \"2024-06-21T16:20:25.516+00:00\",\n \"$updatedAt\": \"2024-06-21T16:21:16.855+00:00\",\n \"$permissions\": [\n \"create(\\\"any\\\")\",\n \"read(\\\"any\\\")\",\n \"update(\\\"any\\\")\",\n \"delete(\\\"any\\\")\"\n ],\n \"fileSecurity\": false,\n \"name\": \"test\",\n \"enabled\": true,\n \"maximumFileSize\": 5368709120,\n \"allowedFileExtensions\": [],\n \"compression\": \"none\",\n \"encryption\": true,\n \"antivirus\": true\n }\n ]\n}\n```\n\nYou can also move the `buckets` array into a separate JSON file with the `includes` field.\n\n{% arrow_link href=\"/docs/tooling/command-line/installation#multi-file-configuration\" %}\nLearn more about multi-file configuration\n{% /arrow_link %}\n\n# Push bucket {% #push-bucket %}\n\n{% partial file=\"cli-push-command.md\" /%}\n\n```sh\nappwrite push buckets\n```\n\n# Commands {% #commands %}\n\nThe storage command allows you to manage your project's buckets and files. Appwrite storage CLI commands generally follow the following syntax:\n\n```sh\nappwrite storage [COMMAND] [OPTIONS]\n```\n\n{% table %}\n* Command\n* Description\n---\n* `list-buckets [options]` \n* Get a list of all the storage buckets. You can use the query params to filter your results.\n---\n* `create-bucket [options]` \n* Create a new storage bucket.\n---\n* `get-bucket [options]` \n* Get a storage bucket by its unique ID. This endpoint response returns a JSON object with the storage bucket metadata.\n---\n* `update-bucket [options]` \n* Update a storage bucket by its unique ID.\n---\n* `delete-bucket [options]` \n* Delete a storage bucket by its unique ID.\n---\n* `list-files [options]` \n* Get a list of all the user files. You can use the query params to filter your results.\n---\n* `create-file [options]`\n* 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.\n---\n* `get-file [options]` \n* Get a file by its unique ID. This endpoint response returns a JSON object with the file metadata.\n---\n* `update-file [options]` \n* Update a file by its unique ID. Only users with write permissions have access to update this resource.\n---\n* `delete-file [options]` \n* Delete a file by its unique ID. Only users with write permissions have access to delete this resource.\n---\n* `get-file-download [options]`\n* 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.\n---\n* `get-file-preview [options]` \n* 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.\n---\n* `get-file-view [options]`\n* Get a file content by its unique ID. This endpoint is similar to the download method but returns with no 'Content-Disposition: attachment' header.\n---\n{% /table %}"}, {"path": "docs/tooling/command-line/commands", "title": "Commands", "description": "Learn about Appwrites CLI and the powerful, feature complete commands to manage Appwrite's auth, databases, functions, storage, and more.", "content": "{% info title=\"CLI Version\" %}\nAll 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).\n{% /info %}\n\n\nOther 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.\n\nCommands generally follow the following syntax:\n\n```sh\nappwrite [COMMAND] [OPTIONS]\n```\n\n# Commands {% #commands %}\n\nBelow is a list of the available commands in the Appwrite CLI. You can get more information on each command by running `appwrite [COMMAND] --help`.\n\n## General commands {% #general-commands %}\n\n{% table %}\n* Command\n* Description\n---\n* `client [options]`\n* The client command allows you to configure your CLI.\n---\n* `locale`\n* The locale command allows you to customize your app based on your users' location.\n---\n* `graphql`\n* The graphql command allows you to query and mutate any resource type on your Appwrite server.\n---\n* `types [options] `\n* The types command generates type definitions based on your Appwrite database schema. Learn more about [type generation](/docs/products/databases/type-generation).\n---\n* `generate`\n* 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).\n---\n{% /table %}\n\n## Account commands {% #account-commands %}\n\n{% table %}\n* Command\n* Description\n---\n* `login [options]`\n* 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.\n---\n* `logout`\n* The logout command allows you to log out of your Appwrite account.\n---\n* `register`\n* Prints link to register an Appwrite account.\n---\n* `whoami`\n* The whomai command gives information about the currently logged-in user.\n---\n{% /table %}\n\n## Deployment commands {% #deployment-commands %}\n\n{% table %}\n* Command\n* Description\n---\n* `init [options]`\n* The init command provides a convenient wrapper for creating and initializing projects, functions, tables, buckets, teams, and messaging-topics in Appwrite.\n---\n* `pull`\n* The pull command helps you pull your Appwrite project, functions, tables, buckets, teams, and messaging-topics.\n---\n* `push`\n* The push command provides a convenient wrapper for pushing your functions, tables, buckets, teams, and topics.\n---\n* `run`\n* The run command allows you to run projects locally to allow easy development and quick debugging.\n---\n{% /table %}\n\nThe `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.\n\n## Project commands {% #appwrite-project-commands %}\n\n{% table %}\n\n* Command\n* Description\n---\n* `account`\n* The account command allows you to authenticate and manage a user account.\n---\n* `users`\n* The users command allows you to manage your project users.\n---\n* `teams`\n* The teams command allows you to group users of your project and enable them to share read and write access to your project resources.\n---\n* `databases`\n* The databases command allows you to create structured tables of rows and query and filter lists of rows.\n---\n* `functions`\n* The functions command allows you to view, create, and manage your Appwrite Functions.\n---\n* `messaging`\n* The messaging command allows you to send, create, edit, and delete messages.\n---\n* `storage`\n* The storage command allows you to manage your project files.\n---\n* `avatars`\n* The avatars command provides utilities to manage images, icons, and avatars.\n---\n{% /table %}\n\n## Command options {% #command-options %}\n\n{% table %}\n\n* Command\n* Description\n---\n* `-v, --version` \n* Output the version number\n---\n* `-V, --verbose` \n* Show complete error log\n---\n* `-j, --json` \n* Output in JSON format\n---\n* `-f,--force` \n* Flag to confirm all warnings\n---\n* `-a,--all` \n* Flag to push all resources\n---\n* `--id [id...]` \n* Flag to pass a list of ids for a given action\n---\n* `--report` \n* Enable reporting in case of CLI errors\n---\n* `-h, --help` \n* Display help for command\n---\n{% /table %}\n\n# Verbose {% #verbose %}\nIn case of errors with any command, you can get more information about what went wrong using the `--verbose` flag\n\n```sh\nappwrite users list --verbose\n```\n\n# JSON {% #json %}\nBy default, output is rendered in a tabular format. To format the output as JSON, use the `--json` flag.\n\n```sh\nappwrite users list --json\n```\n\n# Force {% #force %}\nBy default, when pushing or pulling resources, the Appwrite CLI will ask you to confirm destructive operations. Use the `--force` flag to verify all questions.\n\n```sh\nappwrite push tables --force\n```\n\n# All {% #all %}\nBy default, when pushing or pulling resources, Appwrite CLI would ask you to select specific resources. Use the `--all` flag to select all available options.\n\n```sh\nappwrite pull functions --all\n```\n\n# Error reporting {% #report %}\nIf you encounter errors with any command, you can use the --report flag to generate a GitHub reporting link.\n\n```sh\nappwrite login --report\n```\n\n# View on console {% #console-flow %}\nMany 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.\n\n```sh\nappwrite tables-db get-row \\\n --database-id \"\" \\\n --table-id \"\" \\\n --row-id \"\" \\\n --console --open\n```\n\n# Filter, sort, and paginate {% #filter-sort-paginate %}\n\nList 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.\n\n{% table %}\n* Flag\n* Description\n---\n* `--where `\n* Filter using a simple comparison expression. Supports `field=value`, `field!=value`, `field>value`, `field>=value`, `field1999'`) so that `>` and `<` are not interpreted as shell redirection.\n---\n* `--sort-asc `\n* Sort results by an attribute in ascending order. Repeat for multiple sort fields.\n---\n* `--sort-desc `\n* Sort results by an attribute in descending order. Repeat for multiple sort fields.\n---\n* `--limit `\n* Maximum number of results to return.\n---\n* `--offset `\n* Number of results to skip from the beginning.\n---\n* `--cursor-after `\n* Return results after this cursor ID. Use for forward [cursor pagination](/docs/products/databases/pagination).\n---\n* `--cursor-before `\n* Return results before this cursor ID. Use for backward cursor pagination.\n---\n* `--select `\n* Limit returned attributes on row and document list commands such as `tables-db list-rows`. Repeat the flag to include multiple attributes.\n---\n{% /table %}\n\nFor example, to fetch the 10 most recently created rows where `year` is greater than 1999:\n\n```sh\nappwrite tables-db list-rows \\\n --database-id \"\" \\\n --table-id \"\" \\\n --where 'year>1999' \\\n --sort-desc '$createdAt' \\\n --limit 10\n```\n\nThe `--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.\n\n```sh\nappwrite tables-db list-rows \\\n --database-id \"\" \\\n --table-id \"\" \\\n --queries '[{\"method\":\"search\",\"attribute\":\"title\",\"values\":[\"Avatar\"]}]' \\\n --limit 10\n```\n\n# Examples {% #examples %}\n\n## Create user {% #create-user %}\n\nTo create a new user in your project, you can use the create command.\n\n```sh\nappwrite users create --user-id \"unique()\" \\\n --email hello@appwrite.io \\\n --password very_strong_password\n```\n\n## List users {% #list-users %}\n\nTo get a list of all your project users, you can use the list command.\n\n```sh\nappwrite users list\n```\n\nYou 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:\n\n```sh\nappwrite users list \\\n --where 'emailVerification=true' \\\n --sort-desc '$createdAt' \\\n --limit 25\n```\n\n## List tables {% #list-tables %}\n\nTo get a list of all your [tables](/docs/tooling/command-line/tables), you can use the `list-tables` command.\n\n```sh\nappwrite tables-db list-tables --database-id \"\"\n```\n\nIf you wish to parse the output from the CLI, you can request the CLI output in JSON format using the `--json` flag\n\n```sh\nappwrite tables-db list-tables --database-id \"\" --json\n```\n\n## Get table {% #get-table %}\n\nTo get more information on a particular table, you can make use of the `get-table` command and pass in the table-id.\n\n```sh\nappwrite tables-db get-table --database-id \"\" --table-id \"\"\n```\n\n## Create row {% #create-row %}\n\nTo create a new row in an existing table, use the `create-row` command.\n\n```sh\nappwrite tables-db create-row \\\n --database-id \"\" --table-id \"\" \\\n --row-id 'unique()' --data '{ \"Name\": \"Iron Man\" }' \\\n --permissions 'read(\"any\")' 'write(\"team:abc\")' \n```"}, {"path": "docs/tooling/command-line/functions", "title": "Functions", "description": "Efficiently deploy your Appwrite functions using the Command-Line Tool (CLI).", "content": "{% partial file=\"cli-disclaimer.md\" /%}\n\nThe 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.\n\n# Initialize function {% #initialize-function %}\n\nCreate a new function using the following command:\n\n```sh\nappwrite init functions\n```\n\n# Pull function {% #pull-function %}\n\nYou can also pull your existing Appwrite Functions from the Appwrite Console using the `pull` command in the folder containing your `appwrite.config.json` file.\n\n```sh\nappwrite pull functions\n```\n\n# appwrite.config.json {% #appwritejson %}\n\nAfter [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:\n\n```json\n{\n \"projectId\": \"\",\n \"endpoint\": \"https://.cloud.appwrite.io/v1\",\n \"functions\": [\n {\n \"$id\": \"\",\n \"$createdAt\": \"2024-04-22T22:29:31.427+00:00\",\n \"$updatedAt\": \"2024-06-26T19:08:26.582+00:00\",\n \"execute\": [\n \"any\"\n ],\n \"name\": \"userAuth\",\n \"enabled\": true,\n \"live\": true,\n \"logging\": true,\n \"runtime\": \"node-18.0\",\n \"deployment\": \"\",\n \"vars\": [\n {\n \"$id\": \"eyJhbGciOiJIUzI1N\",\n \"$createdAt\": \"2024-04-22T22:51:51.745+00:00\",\n \"$updatedAt\": \"2024-04-23T00:13:10.886+00:00\",\n \"key\": \"\",\n \"value\": \"\",\n \"resourceType\": \"function\",\n \"resourceId\": \"eyJhbGciOiJIUzI1N\"\n },\n {\n \"$id\": \"N1IzUIJiOicGbhJye\",\n \"$createdAt\": \"2024-04-22T23:32:12.901+00:00\",\n \"$updatedAt\": \"2024-04-22T23:32:12.901+00:00\",\n \"key\": \"\",\n \"value\": \"\",\n \"resourceType\": \"function\",\n \"resourceId\": \"N1IzUIJiOicGbhJye\"\n },\n {\n \"$id\": \"OicGbhJyeN1IzUIJi\",\n \"$createdAt\": \"2024-04-22T23:32:12.910+00:00\",\n \"$updatedAt\": \"2024-04-22T23:32:12.910+00:00\",\n \"key\": \"\",\n \"value\": \"\",\n \"resourceType\": \"function\",\n \"resourceId\": \"OicGbhJyeN1IzUIJi\"\n },\n {\n \"$id\": \"bhJyIJiON1IzUicGe\",\n \"$createdAt\": \"2024-04-22T23:32:12.912+00:00\",\n \"$updatedAt\": \"2024-04-22T23:32:12.912+00:00\",\n \"key\": \"\",\n \"value\": \"\",\n \"resourceType\": \"function\",\n \"resourceId\": \"bhJyIJiON1IzUicGe\"\n }\n ],\n \"events\": [],\n \"schedule\": \"\",\n \"timeout\": 15,\n \"entrypoint\": \"userAuth.js\",\n \"commands\": \"npm install\",\n \"version\": \"v3\",\n \"installationId\": \"a0e499a8\",\n \"providerRepositoryId\": \"7389\",\n \"providerBranch\": \"user-appwrite-funcs\",\n \"providerRootDirectory\": \"functions\",\n \"providerSilentMode\": false,\n \"path\": \"functions/49dbf3\"\n }\n ]\n}\n```\n\nYou 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.\n\n{% arrow_link href=\"/docs/tooling/command-line/installation#multi-file-configuration\" %}\nLearn more about multi-file configuration\n{% /arrow_link %}\n\n# Push function {% #push-function %}\n\n{% partial file=\"cli-push-command.md\" /%}\n\n```sh\nappwrite push functions\n```\n\n# Commands {% #commands %}\n\nThe functions command lets you view, create, and manage your Appwrite Functions. Appwrite functions CLI commands generally follow the following syntax:\n\n```sh\nappwrite functions [COMMAND] [OPTIONS]\n```\n\n{% table %}\n* Command\n* Description\n---\n* `list [options]`\n* Get a list of all the project's functions. You can use the query params to filter your results.\n---\n* `create [options]`\n* 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.\n---\n* `list-runtimes`\n* Get a list of all runtimes that are currently active on your instance.\n---\n* `get [options]`\n* Get a function by its unique ID.\n---\n* `update [options]`\n* Update function by its unique ID.\n---\n* `delete [options]`\n* Delete a function by its unique ID.\n---\n* `list-deployments [options]`\n* Get a list of all the project's code deployments. You can use the query params to filter your results.\n---\n* `create-deployment [options]`\n* 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.\n---\n* `get-deployment [options]`\n* Get a code deployment by its unique ID.\n---\n* `update-deployment [options]`\n* 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.\n---\n* `delete-deployment [options]`\n* Delete a code deployment by its unique ID.\n---\n* `download-deployment [options]`\n* Get a Deployment's contents by its unique ID. This endpoint supports range requests for partial or streaming file download.\n---\n* `list-executions [options]`\n* Get a list of all the current user function execution logs. You can use the query params to filter your results.\n---\n* `create-execution [options]`\n* Trigger a function execution.\n---\n* `get-execution [options]`\n* Get a function execution log by its unique ID.\n---\n* `list-variables [options]`\n* Get a list of all variables of a specific function.\n---\n* `create-variable [options]`\n* Create a new function environment variable. These variables can be accessed in the function at runtime as environment variables.\n---\n* `get-variable [options]`\n* Get a variable by its unique ID.\n---\n* `update-variable [options]`\n* Update a variable by its unique ID.\n---\n* `delete-variable [options]`\n* Delete a variable by its unique ID.\n---\n{% /table %}"}, {"path": "docs/tooling/command-line/generate", "title": "Generate SDK", "description": "Generate a type-safe SDK for your Appwrite project using the Command-Line Tool (CLI). Automatically create typed helpers based on your database schema.", "content": "{% partial file=\"cli-disclaimer.md\" /%}\n\nThe `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.\n\n# Generate SDK {% #generate-sdk %}\n\nRun the following command in your project directory:\n\n```sh\nappwrite generate\n```\n\nThe CLI automatically detects your project's language and generates the SDK to a `generated/appwrite/` directory.\n\n# Options {% #options %}\n\n{% table %}\n* Option\n* Description\n---\n* `-o, --output `\n* Output directory for generated files (default: `\"generated\"`)\n---\n* `-l, --language `\n* Target language for SDK generation (supported: `typescript`)\n---\n* `--server `\n* Override server-side generation (`auto`|`true`|`false`) (default: `\"auto\"`)\n---\n* `-h, --help`\n* Display help for command\n---\n{% /table %}\n\n# Generated files {% #generated-files %}\n\nThe generated SDK includes the following files:\n\n{% table %}\n* File\n* Description\n---\n* `types.ts`\n* Type definitions based on your database schema.\n---\n* `databases.ts`\n* Typed database helpers for querying and mutating rows.\n---\n* `index.ts`\n* Entry point that exports all generated helpers.\n---\n* `constants.ts`\n* Configuration constants such as your project endpoint and project ID. Update these values before using the SDK.\n---\n{% /table %}\n\n# Usage {% #usage %}\n\nAfter generating the SDK, import it into your project:\n\n```ts\nimport { databases } from \"./generated/appwrite\";\n```\n\nConfigure your SDK constants by setting the values in `./generated/appwrite/constants.ts`.\n\nUse the generated helpers to interact with your tables:\n\n```ts\nconst customers = databases.use(\"main\").use(\"customers\");\n\nconst customer = await customers.create({\n name: \"Walter O' Brian\",\n email: \"walter@example.com\"\n});\n```\n\nThe generated helpers provide auto-completion and type checking based on your database schema, reducing errors and improving developer experience.\n\n# Examples {% #examples %}\n\nThe generated SDK supports all common database operations. Below are examples across different use cases.\n\n## Get a row {% #get-row %}\n\n```ts\nconst customer = await customers.get(\"customer-id-123\");\n```\n\n## List rows with queries {% #list-rows %}\n\nThe `list` method accepts a typed query builder that provides auto-completion for your table's columns.\n\n```ts\nconst results = await customers.list({\n queries: (q) => [\n q.equal(\"name\", \"Walter O' Brian\"),\n q.orderDesc(\"$createdAt\"),\n q.limit(10)\n ]\n});\n```\n\n## Update a row {% #update-row %}\n\n```ts\nawait customers.update(\"customer-id-123\", {\n email: \"walter@scorpion.com\"\n});\n```\n\n## Delete a row {% #delete-row %}\n\n```ts\nawait customers.delete(\"customer-id-123\");\n```\n\n## Bulk operations {% #bulk-operations %}\n\nCreate, update, or delete multiple rows at once.\n\n```ts\nawait customers.createMany([\n { name: \"Walter O' Brian\", email: \"walter@example.com\" },\n { name: \"Paige Dineen\", email: \"paige@example.com\" }\n]);\n```\n\n```ts\nawait customers.updateMany(\n { email: \"updated@example.com\" },\n {\n queries: (q) => [q.equal(\"name\", \"Walter O' Brian\")]\n }\n);\n```\n\n```ts\nawait customers.deleteMany({\n queries: (q) => [q.equal(\"name\", \"Paige Dineen\")]\n});\n```\n\n## Permissions {% #permissions %}\n\nSet row-level permissions when creating or updating rows.\n\n```ts\nawait customers.create(\n { name: \"Walter O' Brian\", email: \"walter@example.com\" },\n {\n permissions: (permission, role) => [\n permission.read(role.any()),\n permission.write(role.user(\"user-id-123\"))\n ]\n }\n);\n```"}, {"path": "docs/tooling/command-line/installation", "title": "Installation", "description": "Get started with the Appwrite CLI by following the installation guide. Learn how to set up and configure the CLI on your development environment.", "content": "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.\n\n# Getting started {% #getting-started %}\n\nThe 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.\n\nIf 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.\n\n## Install with npm {% #install-with-npm %}\n\nIf you have npm set up, run the command below to install the CLI.\n\n```sh\nnpm install -g appwrite-cli\n```\n\n## Install with script {% #install-with-script %}\n\nFor a completely dependency-free installation, the CLI also ships with a convenient installation script for your operating system\n\n{% tabs %}\n{% tabsitem #macos title=\"macOS\" %}\n\nUsing [Homebrew](https://brew.sh/)\n\n```sh\nbrew install appwrite\n```\n\nor terminal\n\n```sh\ncurl -sL https://appwrite.io/cli/install.sh | bash\n```\n{% /tabsitem %}\n\n{% tabsitem #windows title=\"Windows\" %}\n\nUsing [Powershell](https://learn.microsoft.com/en-us/powershell/)\n\n```sh\niwr -useb https://appwrite.io/cli/install.ps1 | iex\n```\n\nor [Scoop](https://scoop.sh/)\n\n```sh\nscoop install https://raw.githubusercontent.com/appwrite/sdk-for-cli/master/scoop/appwrite.config.json\n```\n{% /tabsitem %}\n\n{% tabsitem #linux title=\"Linux\" %}\n```sh\ncurl -sL https://appwrite.io/cli/install.sh | bash\n```\n{% /tabsitem %}\n\n{% /tabs %}\n\n# Update your CLI {% #update-your-cli %}\n\n{% tabs %}\n{% tabsitem #npm title=\"npm\" %}\n```sh\nnpm install -g appwrite-cli\n```\n{% /tabsitem %}\n\n{% tabsitem #macos title=\"macOS\" %}\n\nUsing [Homebrew](https://brew.sh/)\n\n```sh\nbrew install appwrite\n```\n\nor terminal\n\n```sh\ncurl -sL https://appwrite.io/cli/install.sh | bash\n```\n{% /tabsitem %}\n\n{% tabsitem #windows title=\"Windows\" %}\n```sh\niwr -useb https://appwrite.io/cli/install.ps1 | iex\n```\n{% /tabsitem %}\n\n{% tabsitem #linux title=\"Linux\" %}\n```sh\ncurl -sL https://appwrite.io/cli/install.sh | bash\n```\n{% /tabsitem %}\n\n{% tabsitem #scoop title=\"Scoop\" %}\n```sh\nscoop install https://raw.githubusercontent.com/appwrite/sdk-for-cli/master/scoop/appwrite.config.json\n```\n{% /tabsitem %}\n\n{% /tabs %}\n\n## Verify installation {% #verify-installation %}\n\nAfter the installation or the update is complete, you can verify the Appwrite CLI is available by checking its version number.\n\n```sh\nappwrite -v\n```\n\n# Login {% #login %}\n\nBefore you can use the CLI, you need to login to your Appwrite account using\n\n```sh\nappwrite login\n```\n\nAdd 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.\n\n```sh\nappwrite login --endpoint \"\"\n```\nYou can log in to multiple accounts or change the **current** account by re-running the command.\n\n# Initialization {% #initialization %}\n\nAfter you're logged in, the CLI needs to be initialized with your Appwrite project. You can initialize the CLI using:\n\n```sh\nappwrite init project\n```\n\nThis will create your `appwrite.config.json` file, where you will configure your various services like tables, functions, teams, topics, and buckets.\n\n```json\n{\n \"projectId\": \"\",\n \"endpoint\": \"https://.cloud.appwrite.io/v1\"\n}\n```\n\nThe CLI will also auto-detect your project configuration and automatically install relevant [Appwrite agent skills](/docs/tooling/ai/skills).\n\nYou can run your first CLI command after logging in. Try fetching information about your Appwrite project.\n\n```sh\nappwrite projects get --project-id \"\"\n```\n\n# Multi-file configuration {% #multi-file-configuration %}\n\nBy 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.\n\nUse 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.\n\n```json\n{\n \"projectId\": \"\",\n \"endpoint\": \"https://.cloud.appwrite.io/v1\",\n \"includes\": {\n \"functions\": \"./appwrite/functions.json\",\n \"sites\": \"./appwrite/sites.json\",\n \"buckets\": \"./appwrite/buckets.json\",\n \"teams\": \"./appwrite/teams.json\",\n \"topics\": \"./appwrite/topics.json\"\n }\n}\n```\n\nEach included file contains the JSON array that would normally live under that key in `appwrite.config.json`.\n\n```json\n[\n {\n \"$id\": \"\",\n \"name\": \"api\",\n \"runtime\": \"node-22\",\n \"path\": \"functions/api\",\n \"entrypoint\": \"src/main.js\",\n \"commands\": \"npm install\",\n \"execute\": [],\n \"events\": [],\n \"schedule\": \"\",\n \"timeout\": 15,\n \"enabled\": true,\n \"logging\": true,\n \"ignore\": [\n \"node_modules\",\n \".git\"\n ],\n \"scopes\": [],\n \"vars\": []\n }\n]\n```\n\nThe 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`.\n\nThe single-file format continues to work. You can split any supported resource arrays over time and keep other arrays in the root config.\n\nSupported include keys are `functions`, `sites`, `databases`, `collections`, `tablesDB`, `tables`, `topics`, `teams`, `buckets`, `webhooks`, and `messages`.\n\nInclude 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.\n\n{% info title=\"Self-signed certificates\" %}\nBy 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\n\n```sh\nappwrite client --self-signed true\n```\n{% /info %}\n\n## Next steps {% #next-steps %}\n\nYou 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.\n\n[Learn more about deployment](/docs/tooling/command-line/tables)\n\nBesides utility commands, the CLI can be used to execute commands like a Server SDK.\n\n[Find a full list of commands](/docs/tooling/command-line/commands)\n\nYou 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.\n\n[Learn more about CI mode](/docs/tooling/command-line/non-interactive)\n\n# Help {% #help %}\n\nIf you get stuck anywhere, you can always use the `help` command to get the usage examples.\n\n```sh\nappwrite help\n```\n\n# Configuration {% #configuration %}\n\nAt any point, if you would like to change your server's endpoint, project ID, or self-signed certificate acceptance, use the `client` command.\n\n```sh\nappwrite client --endpoint https://.cloud.appwrite.io/v1\nappwrite client --key 23f24gwrhSDgefaY\nappwrite client --self-signed true\nappwrite client --reset // Resets your CLI configuration\nappwrite client --debug // Prints your current configuration\n```\n\n# Uninstall {% #uninstall %}\n\nIf you installed Appwrite CLI using NPM, you can use the following command to uninstall it.\n\n```sh\nnpm uninstall -g appwrite-cli\n```\n\nIf you installed the Appwrite CLI with brew or the installation script for your operating system, use the following command to uninstall it.\n\n{% tabs %}\n{% tabsitem #macos title=\"macOS\" %}\n\nUsing [Homebrew](https://brew.sh/)\n\n```sh\nbrew uninstall appwrite\n```\n\nor terminal\n\n```sh\nrm -f /usr/local/bin/appwrite | bash\n```\n{% /tabsitem %}\n\n{% tabsitem #windows title=\"Windows\" %}\n\nUsing [Powershell](https://learn.microsoft.com/en-us/powershell/)\n\n```sh\n$APPWRITE_INSTALL_DIR = Join-Path -Path $env:LOCALAPPDATA -ChildPath \"Appwrite\"; Remove-Item -Force -Path $APPWRITE_INSTALL_DIR\n```\n\nor [Scoop](https://scoop.sh/)\n\n```sh\nscoop uninstall appwrite.config\n```\n{% /tabsitem %}\n\n{% tabsitem #linux title=\"Linux\" %}\n```sh\nrm -f /usr/local/bin/appwrite | bash\n```\n{% /tabsitem %}\n\n{% /tabs %}\n\nYou can also remove the configuration, cookies, and API Keys the Appwrite CLI stored. To remove those, run the following command.\n\n{% tabs %}\n{% tabsitem #macos title=\"macOS\" %}\n```sh\nrm -rf ~/.appwrite | bash\n```\n{% /tabsitem %}\n\n{% tabsitem #windows title=\"Windows\" %}\n\nUsing [Powershell](https://learn.microsoft.com/en-us/powershell/)\n\n```sh\n$APPWRITE_CONFIG_DIR = Join-Path -Path $env:UserProfile -ChildPath \".appwrite\"; Remove-Item -Recurse -Force -Path $APPWRITE_CONFIG_DIR\n```\n\nor [Scoop](https://scoop.sh/)\n\n```sh\nappwrite client --reset\n```\n\n{% /tabsitem %}\n\n{% tabsitem #linux title=\"Linux\" %}\n```sh\nrm -rf ~/.appwrite | bash\n```\n{% /tabsitem %}\n\n{% /tabs %}"}, {"path": "docs/tooling/command-line/non-interactive", "title": "Non-interactive", "description": "Deploy changes to Appwrite projects to migrate databases and tables schema, functions, teams, buckets, and more.", "content": "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`:\n\n```sh\nappwrite client \\\n --endpoint https://.cloud.appwrite.io/v1 \\\n --project-id \\\n --key YOUR_API_KEY\n```\n\nWhen 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.\n\nIn this mode, the CLI can only interact with one project at a time.\n\n# API Keys {% #api-keys %}\nIn non-interactive mode, the CLI uses an API key to authenticate. Your API key must have sufficient permissions to execute the commands you plan to use. [Learn more about API Keys](/docs/advanced/security/api-keys).\n\n# Deployment {% #deployment %}\nAppwrite'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.\n\nYou can push a resource non-interactively by using the `--force` option to skip all warnings and specify which resources you want to deploy.\n\nTo push all available resources:\n\n```sh\nappwrite push all --all --force\n```\n\nTo push a single function by ID:\n```sh\nappwrite push functions --function-id [FUNCTION ID] --force\n```\n\nPush all functions:\n\n```sh\nappwrite push functions --all --force\n```\n\nYou can push databases, tables, teams, and buckets non-interactively in a similar way by using the `--all` and `--force` option.\n\nPush all databases and tables:\n\n```sh\nappwrite push tables --all --force\n```\n\nPush all teams:\n\n```sh\nappwrite push teams --all --force\n```\n\nPush all buckets:\n\n```sh\nappwrite push buckets --all --force\n```\n\n# CI/CD integrations {% #ci-cd-integrations %}\n\nUse providers like Github actions to create continuous integrations and continuous delivery or deployment (CI/CD).\n\n## Github\n\nYou 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."}, {"path": "docs/tooling/command-line/sites", "title": "Sites", "description": "Efficiently deploy your Appwrite Sites using the Command-Line Tool (CLI).", "content": "{% partial file=\"cli-disclaimer.md\" /%}\n\nThe CLI handles the creation, deployment, and execution of Appwrite Sites, as well as the configuration of the variables.\n\n# Initialize site {% #initialize-site %}\n\nCreate a new site using the following command:\n\n```sh\nappwrite init sites\n```\n\n# Pull site {% #pull-site %}\n\nYou can also pull your existing Appwrite Sites from the Appwrite Console using the `pull` command in the folder containing your `appwrite.config.json` file.\n\n```sh\nappwrite pull sites\n```\n\n# appwrite.config.json {% #appwritejson %}\n\nAfter [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:\n\n```json\n{\n \"projectId\": \"\",\n \"endpoint\": \"https://.cloud.appwrite.io/v1\",\n \"sites\": [\n {\n \"$id\": \"\",\n \"name\": \"Documentation template\",\n \"enabled\": true,\n \"logging\": true,\n \"framework\": \"astro\",\n \"timeout\": 30,\n \"installCommand\": \"npm install\",\n \"buildCommand\": \"npm run build\",\n \"outputDirectory\": \"./dist\",\n \"specification\": \"s-1vcpu-512mb\",\n \"buildRuntime\": \"node-22\",\n \"adapter\": \"ssr\",\n \"fallbackFile\": \"\",\n \"path\": \"sites/documentation-template\"\n }\n ]\n}\n```\n\nYou 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.\n\n{% arrow_link href=\"/docs/tooling/command-line/installation#multi-file-configuration\" %}\nLearn more about multi-file configuration\n{% /arrow_link %}\n\n# Push site {% #push-site %}\n\n{% partial file=\"cli-push-command.md\" /%}\n\n```sh\nappwrite push sites\n```\n\n# Commands {% #commands %}\n\nThe functions command lets you view, create, and manage your Appwrite Sites. Appwrite Sites CLI commands generally follow the following syntax:\n\n```sh\nappwrite sites [COMMAND] [OPTIONS]\n```\n\n{% table %}\n* Command\n* Description\n---\n* `list [options]`\n* Get a list of all the project's sites. You can use the query params to filter your results.\n---\n* `create [options]`\n* Create a new site.\n---\n* `list-frameworks [options]`\n* Get a list of all frameworks that are currently available on the server instance.\n---\n* `list-specifications [options]`\n* List allowed site specifications for this instance.\n---\n* `list-templates [options]`\n* List available site templates. You can use template details in [createSite](/docs/references/cloud/server-nodejs/sites#create) method.\n---\n* `get-template [options]`\n* Get a site template using ID. You can use template details in [createSite](/docs/references/cloud/server-nodejs/sites#create) method.\n---\n* `list-usage [options]`\n* 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.\n---\n* `get [options]`\n* Get a site by its unique ID.\n---\n* `update [options]`\n* Update site by its unique ID.\n---\n* `delete [options]`\n* Delete a site by its unique ID.\n---\n* `update-site-deployment [options]`\n* Update the site active deployment. Use this endpoint to switch the code deployment that should be used when visitor opens your site.\n---\n* `list-deployments [options]`\n* Get a list of all the site's code deployments. You can use the query params to filter your results.\n---\n* `create-deployment [options]`\n* 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.\n---\n* `create-duplicate-deployment [options]`\n* 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.\n---\n* `create-template-deployment [options]`\n* 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.\n---\n* `create-vcs-deployment [options]`\n* Create a deployment when a site is connected to VCS. This endpoint lets you create deployment from a branch, commit, or a tag.\n---\n* `get-deployment [options]`\n* Get a site deployment by its unique ID.\n---\n* `delete-deployment [options]`\n* Delete a site deployment by its unique ID.\n---\n* `get-deployment-download [options]`\n* 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.\n---\n* `update-deployment-status [options]`\n* 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.\n---\n* `list-logs [options]`\n* Get a list of all site logs. You can use the query params to filter your results.\n---\n* `get-log [options]`\n* Get a site request log by its unique ID.\n---\n* `delete-log [options]`\n* Delete a site log by its unique ID.\n---\n* `get-usage [options]`\n* 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.\n---\n* `list-variables [options]`\n* Get a list of all variables of a specific site.\n---\n* `create-variable [options]`\n* Create a new site variable. These variables can be accessed during build and runtime (server-side rendering) as environment variables.\n---\n* `get-variable [options]`\n* Get a variable by its unique ID.\n---\n* `update-variable [options]`\n* Update variable by its unique ID.\n---\n* `delete-variable [options]`\n* Delete a variable by its unique ID.\n---\n{% /table %}"}, {"path": "docs/tooling/command-line/tables", "title": "Tables", "description": "Efficiently deploy your Appwrite tables using the Command-Line Tool (CLI).", "content": "{% partial file=\"cli-disclaimer.md\" /%}\n\nCreate 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.\n\n# Initialize table {% #initialize-table %}\n\nCreate a new table using the following command:\n\n```sh\nappwrite init tables\n```\n\n# Pull table {% #pull-table %}\n\nYou 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.\n\n```sh\nappwrite pull tables\n```\n\n# appwrite.config.json {% #appwritejson %}\n\nAfter [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:\n\n```json\n{\n \"projectId\": \"\",\n \"endpoint\": \"https://.cloud.appwrite.io/v1\",\n \"tablesDB\": [\n {\n \"$id\": \"\",\n \"name\": \"songs\",\n \"$createdAt\": \"2023-07-01T18:35:27.802+00:00\",\n \"$updatedAt\": \"2023-08-01T21:41:41.663+00:00\",\n \"enabled\": true\n }\n ],\n \"tables\": [\n {\n \"$id\": \"\",\n \"$permissions\": [\n \"create(\\\"any\\\")\",\n \"read(\\\"any\\\")\",\n \"update(\\\"any\\\")\",\n \"delete(\\\"any\\\")\"\n ],\n \"databaseId\": \"\",\n \"name\": \"music\",\n \"enabled\": true,\n \"rowSecurity\": false,\n \"columns\": [\n {\n \"key\": \"userID\",\n \"type\": \"varchar\",\n \"status\": \"available\",\n \"error\": \"\",\n \"required\": false,\n \"array\": false,\n \"size\": 100,\n \"default\": null\n },\n {\n \"key\": \"name\",\n \"type\": \"varchar\",\n \"status\": \"available\",\n \"error\": \"\",\n \"required\": false,\n \"array\": false,\n \"size\": 100,\n \"default\": null\n },\n {\n \"key\": \"cloudinaryId\",\n \"type\": \"varchar\",\n \"status\": \"available\",\n \"error\": \"\",\n \"required\": false,\n \"array\": false,\n \"size\": 100,\n \"default\": null\n },\n {\n \"key\": \"user\",\n \"type\": \"varchar\",\n \"status\": \"available\",\n \"error\": \"\",\n \"required\": false,\n \"array\": false,\n \"size\": 100,\n \"default\": null\n },\n {\n \"key\": \"audio\",\n \"type\": \"varchar\",\n \"status\": \"available\",\n \"error\": \"\",\n \"required\": false,\n \"array\": false,\n \"size\": 200,\n \"default\": null\n },\n {\n \"key\": \"genre\",\n \"type\": \"varchar\",\n \"status\": \"available\",\n \"error\": \"\",\n \"required\": false,\n \"array\": false,\n \"size\": 500,\n \"default\": null\n },\n {\n \"key\": \"artist\",\n \"type\": \"varchar\",\n \"status\": \"available\",\n \"error\": \"\",\n \"required\": false,\n \"array\": false,\n \"size\": 500,\n \"default\": null\n }\n ],\n \"indexes\": []\n }\n ]\n}\n```\n\nYou can also move the `tablesDB` and `tables` arrays into separate JSON files with the `includes` field.\n\n{% arrow_link href=\"/docs/tooling/command-line/installation#multi-file-configuration\" %}\nLearn more about multi-file configuration\n{% /arrow_link %}\n\n# Push table {% #push-table %}\n\n{% partial file=\"cli-push-command.md\" /%}\n\n```sh\nappwrite push tables\n```\n\n# Commands {% #commands %}\n\nThe 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:\n\n```sh\nappwrite tables-db [COMMAND] [OPTIONS]\n```\n\n{% table %}\n* Command\n* Description\n---\n* `list-tables [options]`\n* Get a list of all tables that belong to the provided databaseId. You can use the search parameter to filter your results.\n---\n* `create-table [options]`\n* 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.\n--- \n* `get-table [options]`\n* Get a table by its unique ID. This endpoint response returns a JSON object with the table metadata.\n---\n* `update-table [options]`\n* Update a table by its unique ID.\n---\n* `delete-table [options]`\n* Delete a table by its unique ID. Only users with write permissions have access to delete this resource.\n---\n* `list-columns [options]`\n* List columns in the table.\n---\n* `create-boolean-column [options]`\n* Create a boolean column.\n---\n* `update-boolean-column [options]`\n* Update a boolean column. Changing the 'default' value will not update already existing rows.\n---\n* `create-datetime-column [options]`\n* Create a date time column according to the ISO 8601 standard.\n---\n* `update-datetime-column [options]`\n* Update a date time column. Changing the 'default' value will not update already existing rows.\n---\n* `create-email-column [options]`\n* Create an email column.\n---\n* `update-email-column [options]`\n* Update an email column. Changing the 'default' value will not update already existing rows.\n---\n* `create-enum-column [options]`\n* Create an enumeration column. The 'elements' param acts as a white-list of accepted values for this column.\n---\n* `update-enum-column [options]`\n* Update an enum column. Changing the 'default' value will not update already existing rows.\n--- \n* `create-float-column [options]`\n* Create a float column. Optionally, minimum and maximum values can be provided.\n---\n* `update-float-column [options]`\n* Update a float column. Changing the 'default' value will not update already existing rows.\n--- \n* `create-integer-column [options]`\n* Create an integer column. Optionally, minimum and maximum values can be provided.\n---\n* `update-integer-column [options]`\n* Update an integer column. Changing the 'default' value will not update already existing rows.\n---\n* `create-ip-column [options]` \n* Create IP address column.\n---\n* `update-ip-column [options]`\n* Update an ip column. Changing the 'default' value will not update already existing rows.\n---\n* `create-relationship-column [options]`\n* Create relationship column. [Learn more about relationship columns](https://appwrite.io/docs/databases-relationships#relationship-columns).\n---\n* `create-string-column [options]`\n* Create a string column.\n---\n* `update-string-column [options]`\n* Update a string column. Changing the 'default' value will not update already existing rows.\n---\n* `create-url-column [options]` \n* Create a URL column.\n---\n* `update-url-column [options]`\n* Update an url column. Changing the 'default' value will not update already existing rows.\n---\n* `get-column [options]`\n* Get column by ID.\n---\n* `delete-column [options]` \n* Deletes an column.\n---\n* `update-relationship-column [options]` \n* Update relationship column. [Learn more about relationship columns](https://appwrite.io/docs/databases-relationships#relationship-columns).\n---\n* `list-indexes [options]`\n* List indexes in the table.\n---\n* `create-index [options]`\n* 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'.\n---\n* `get-index [options]`\n* Get index by ID.\n---\n* `delete-index [options]`\n* Delete an index.\n---\n* `list-table-logs [options]`\n* Get the table activity logs list by its unique ID.\n---\n{% /table %}"}, {"path": "docs/tooling/command-line/teams", "title": "Teams", "description": "Efficiently deploy your Appwrite teams using the Command-Line Tool (CLI).", "content": "{% partial file=\"cli-disclaimer.md\" /%}\n\nThe 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.\n\n# Initialize team {% #initialize-team %}\n\nCreate a new team using the following command:\n\n```sh\nappwrite init teams\n```\n\n# Pull team {% #pull-team %}\n\nYou can also pull your existing Appwrite teams from the Appwrite Console using the `pull` command in the folder containing your `appwrite.config.json` file.\n\n```sh\nappwrite pull teams\n```\n\n# appwrite.config.json {% #appwritejson %}\nAfter [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:\n\n```json\n{\n \"projectId\": \"\",\n \"endpoint\": \"https://.cloud.appwrite.io/v1\",\n \"teams\": [\n {\n \"$id\": \"eyJhbGciOiJIUzI1N\",\n \"name\": \"hat\"\n },\n {\n \"$id\": \"N1IzUIJiOicGbhJye\",\n \"name\": \"sun\"\n },\n {\n \"$id\": \"OicGbhJyeN1IzUIJi\",\n \"name\": \"emit\"\n },{\n \"$id\": \"bhJyIJiON1IzUicGe\",\n \"name\": \"kue\"\n }\n ]\n}\n```\n\nYou can also move the `teams` array into a separate JSON file with the `includes` field.\n\n{% arrow_link href=\"/docs/tooling/command-line/installation#multi-file-configuration\" %}\nLearn more about multi-file configuration\n{% /arrow_link %}\n\n# Push team {% #push-teams %}\n\n{% partial file=\"cli-push-command.md\" /%}\n\n```sh\nappwrite push teams\n```\n\n# Commands {% #commands %}\n\nThe 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:\n\n```sh\nappwrite teams [COMMAND] [OPTIONS]\n```\n\n{% table %}\n* Command\n* Description\n---\n* `list [options]` \n* Get a list of all the teams in which the current user is a member. You can use the parameters to filter your results.\n---\n* `create [options]` \n* 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.\n---\n* `get [options]` \n* Get a team by its ID. All team members have read access to this resource.\n---\n* `update-name [options]` \n* Update the team's name by its unique ID.\n---\n* `delete [options]` \n* Delete a team using its ID. Only team members with the owner role can delete the team.\n---\n* `list-logs [options]` \n* Get the team activity logs list by its unique ID.\n---\n* `list-memberships [options]` \n* Use this endpoint to list a team's members using the team's ID. All team members have read access to this endpoint.\n--- \n* `create-membership [options]`\n* 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. \n---\n* `get-membership [options]` \n* Get a team member by using the membership unique ID. All team members have read access to this resource.\n---\n* `update-membership [options]` \n* 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).\n---\n* `delete-membership [options]` \n* 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.\n---\n* `update-membership-status [options]` \n* 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.\n---\n* `get-prefs [options]` \n* 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).\n---\n* `update-prefs [options]` \n* 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.\n---\n{% /table %}"}, {"path": "docs/tooling/command-line/topics", "title": "Topics", "description": "Efficiently deploy your Appwrite topics using the Command-Line Tool (CLI).", "content": "{% partial file=\"cli-disclaimer.md\" /%}\n\nThe Appwrite CLI can create, update, delete, and get topics, as well as configure the provider and the subscribers.\n\n# Initialize topic {% #initialize-topic %}\n\nCreate a new topic using the following command:\n\n```\nappwrite init topics\n```\n\n# Pull topics {% #pull-topics %}\n\nYou can also pull your existing Appwrite topics from the Appwrite Console using the `pull` command in the folder containing your `appwrite.config.json` file.\n\n```sh\nappwrite pull topics\n```\n\n# appwrite.config.json {% #appwritejson %}\n\nAfter [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:\n\n```json\n{\n \"projectId\": \"\",\n \"endpoint\": \"https://.cloud.appwrite.io/v1\",\n \"topics\": [\n {\n \"$id\": \"N1IzUIJiOicGbhJye\",\n \"$createdAt\": \"2024-07-01T14:40:43.381+00:00\",\n \"$updatedAt\": \"2024-07-01T14:40:43.381+00:00\",\n \"name\": \"Anime\",\n \"emailTotal\": 3,\n \"smsTotal\": 0,\n \"pushTotal\": 0,\n \"subscribe\": [\n \"users\"\n ]\n },\n {\n \"$id\": \"eyJhbGciOiJIUzI1N\",\n \"$createdAt\": \"2024-07-01T14:41:19.029+00:00\",\n \"$updatedAt\": \"2024-07-01T14:41:28.751+00:00\",\n \"name\": \"Music\",\n \"emailTotal\": 2,\n \"smsTotal\": 0,\n \"pushTotal\": 0,\n \"subscribe\": [\n \"users\",\n \"any\"\n ]\n }\n ]\n}\n```\n\nYou can also move the `topics` array into a separate JSON file with the `includes` field.\n\n{% arrow_link href=\"/docs/tooling/command-line/installation#multi-file-configuration\" %}\nLearn more about multi-file configuration\n{% /arrow_link %}\n\n# Push topics {% #push-topics %}\n\n{% partial file=\"cli-push-command.md\" /%}\n\n```sh\nappwrite push topics\n```\n\n# Commands {% #commands %}\n\nThe 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:\n```sh\nappwrite messaging [COMMAND] [OPTIONS]\n```\n\n{% table %}\n* Command\n* Description\n---\n* `list-messages [options]` \n* Get a list of all messages from the current Appwrite project.\n---\n* `create-email [options]` \n* Create a new email message.\n---\n* `update-email [options]` \n* Update an email message by its unique ID.\n---\n* `create-push [options]` \n* Create a new push notification.\n---\n* `update-push [options]` \n* Update a push notification by its unique ID.\n---\n* `create-sms [options]` \n* Create a new SMS message.\n---\n* `updateSms [options]` \n* Update an email message by its unique ID.\n---\n* `get-message [options]` \n* Get a message by its unique ID.\n---\n* `delete [options]` \n* Delete a message. If the message is not a draft or scheduled, but has been sent, this will not recall the message.\n---\n* `list-message-logs [options]` \n* Get the message activity logs listed by its unique ID.\n---\n* `list-targets [options]`\n* Get a list of the targets associated with a message.\n---\n* `list-providers [options]` \n* Get a list of all providers from the current Appwrite project.\n---\n* `create-apns-provider [options]`\n* Create a new Apple Push Notification service provider.\n---\n* `update-apns-provider [options]` \n* Update an Apple Push Notification service provider by its unique ID.\n---\n* `create-fcm-provider [options]` \n* Create a new Firebase Cloud Messaging provider.\n---\n* `update-fcm-provider [options]` \n* Update a Firebase Cloud Messaging provider by its unique ID.\n---\n* `create-mailgun-provider [options]` \n* Create a new Mailgun provider.\n---\n* `update-mailgun-provider [options]` \n* Update a Mailgun provider by its unique ID.\n---\n* `create-msg91-provider [options]` \n* Create a new MSG91 provider.\n---\n* `update-msg91-provider [options]`\n* Update a MSG91 provider by its unique ID.\n---\n* `create-sendgrid-provider [options]` \n* Create a new Sendgrid provider.\n---\n* `update-sendgrid-provider [options]`\n* Update a Sendgrid provider by its unique ID.\n---\n* `create-smtp-provider [options]` \n* Create a new SMTP provider.\n---\n* `update-smtp-provider [options]`\n* Update a SMTP provider by its unique ID.\n---\n* `create-telesign-provider [options]` \n* Create a new Telesign provider.\n---\n* `update-telesign-provider [options]`\n* Update a Telesign provider by its unique ID.\n---\n* `create-textmagic-provider [options]`\n* Create a new Textmagic provider.\n---\n* `update-textmagic-provider [options]`\n* Update a Textmagic provider by its unique ID.\n---\n* `create-twilio-provider [options]` \n* Create a new Twilio provider.\n---\n* `update-twilio-provider [options]`\n* Update a Twilio provider by its unique ID.\n---\n* `create-vonage-provider [options]` \n* Create a new Vonage provider.\n---\n* `update-vonage-provider [options]`\n* Update a Vonage provider by its unique ID.\n---\n* `get-provider [options]` \n* Get a provider by its unique ID.\n---\n* `delete-provider [options]`\n* Delete a provider by its unique ID.\n---\n* `list-provider-logs [options]` \n* Get the provider activity logs listed by its unique ID.\n---\n* `list-subscriber-logs [options]`\n* Get the subscriber activity logs listed by its unique ID.\n---\n* `list-topics [options]` \n* Get a list of all topics from the current Appwrite project.\n---\n* `create-topic [options]` \n* Create a new topic.\n---\n* `get-topic [options]` \n* Get a topic by its unique ID.\n---\n* `update-topic [options]` \n* Update a topic by its unique ID.\n---\n* `delete-topic [options]` \n* Delete a topic by its unique ID.\n---\n* `list-topic-logs [options]` \n* Get the topic activity logs listed by its unique ID.\n---\n* `list-subscribers [options]` \n* Get a list of all subscribers from the current Appwrite project.\n---\n* `create-subscriber [options]`\n* Create a new subscriber.\n---\n* `get-subscriber [options]` \n* Get a subscriber by its unique ID.\n---\n* `delete-subscriber [options]`\n* Delete a subscriber by its unique ID.\n---\n{% /table %}"}, {"path": "docs/tooling/terraform", "title": "Terraform provider", "description": "Manage Appwrite infrastructure as code with the official Terraform provider. Works with Appwrite Cloud and Community Edition.", "content": "The [Terraform provider for Appwrite](https://github.com/appwrite/terraform-provider-appwrite) lets you declare **TablesDB** (databases, tables, columns, indexes, rows), **Storage** (buckets and files), **Auth** (users and teams), **Functions** (functions and variables), **Sites** (sites and variables), **Messaging** (providers, topics, subscribers), **webhooks**, **backup policies**, and more in `.tf` files, and apply those changes through [HashiCorp Terraform](https://www.terraform.io/). It is the official way to automate Appwrite project configuration alongside the rest of your stack.\n\n# Resources {% #resources %}\n\nResource types use the `appwrite_` prefix and match the [Terraform Registry](https://registry.terraform.io/providers/appwrite/appwrite/latest/docs) documentation.\n\n| Area | Resources |\n|------|-----------|\n| TablesDB | `appwrite_tablesdb`, `appwrite_tablesdb_table`, `appwrite_tablesdb_column`, `appwrite_tablesdb_index`, `appwrite_tablesdb_row` |\n| Storage | `appwrite_storage_bucket`, `appwrite_storage_file` |\n| Auth | `appwrite_auth_user`, `appwrite_auth_team` |\n| Functions | `appwrite_function`, `appwrite_function_variable`, `appwrite_function_deployment` |\n| Sites | `appwrite_site`, `appwrite_site_variable`, `appwrite_site_deployment` |\n| Messaging | `appwrite_messaging_provider`, `appwrite_messaging_topic`, `appwrite_messaging_subscriber` |\n| Webhooks | `appwrite_webhook` |\n| Backups | `appwrite_backup_policy` |\n\n# Data sources {% #data-sources %}\n\nData 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.\n\n| Name | Description |\n|------|-------------|\n| `appwrite_tablesdb` | Look up a database by ID |\n| `appwrite_storage_bucket` | Look up a storage bucket by ID |\n| `appwrite_auth_user` | Look up a user by ID |\n| `appwrite_auth_team` | Look up a team by ID |\n| `appwrite_function` | Look up a function by ID |\n| `appwrite_site` | Look up a site by ID |\n| `appwrite_messaging_topic` | Look up a messaging topic by ID |\n| `appwrite_webhook` | Look up a webhook by ID |\n\n{% info title=\"Looking for self-hosting?\" %}\nThis 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.\n{% /info %}\n\n# What is Terraform? {% #what-is-terraform %}\n\n[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.\n\nIf you are new to the idea:\n\n- **Provider**: A plugin that teaches Terraform how to talk to a specific API (here, Appwrite).\n- **Resource**: Something Terraform should create and manage (for example an Appwrite database or table).\n- **State**: Terraform remembers what it already created so the next run can update or destroy only what changed.\n- **Plan / apply**: You run `terraform plan` to preview changes, then `terraform apply` to execute them.\n\nYou 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.\n\n# Official repository and registry {% #official-repository-and-registry %}\n\nSource code, issues, and contribution guidelines:\n\n{% cards %}\n{% cards_item href=\"https://github.com/appwrite/terraform-provider-appwrite\" title=\"GitHub\" icon=\"web-icon-github\" %}\n`appwrite/terraform-provider-appwrite`: source and issues.\n{% /cards_item %}\n{% cards_item href=\"https://registry.terraform.io/providers/appwrite/appwrite/latest/docs\" title=\"Terraform Registry\" icon=\"web-icon-terraform\" %}\n`appwrite/appwrite`: install, versions, generated schemas.\n{% /cards_item %}\n{% /cards %}\n\nThe 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.\n\n# Next steps {% #next-steps %}\n\n{% cards %}\n{% cards_item href=\"/docs/tooling/terraform/provider\" title=\"Configuration\" %}\nInstall the provider, set endpoints, environment variables, and optional per-resource `project_id`.\n{% /cards_item %}\n{% cards_item href=\"/docs/tooling/terraform/resources/databases\" title=\"Databases\" %}\nTablesDB: databases, tables, columns, indexes, and rows.\n{% /cards_item %}\n{% cards_item href=\"/docs/tooling/terraform/resources/storage\" title=\"Storage\" %}\nBuckets and uploaded files.\n{% /cards_item %}\n{% cards_item href=\"/docs/tooling/terraform/resources/messaging\" title=\"Messaging\" %}\nProviders, topics, and subscribers.\n{% /cards_item %}\n{% cards_item href=\"/docs/tooling/terraform/resources/auth\" title=\"Auth\" %}\nUsers and teams.\n{% /cards_item %}\n{% cards_item href=\"/docs/tooling/terraform/resources/functions\" title=\"Functions\" %}\nFunctions and environment variables.\n{% /cards_item %}\n{% cards_item href=\"/docs/tooling/terraform/resources/sites\" title=\"Sites\" %}\nSites and site variables.\n{% /cards_item %}\n{% cards_item href=\"/docs/tooling/terraform/resources/webhooks\" title=\"Webhooks\" %}\nEvent delivery to your HTTP endpoints.\n{% /cards_item %}\n{% cards_item href=\"/docs/tooling/terraform/resources/backups\" title=\"Backups\" %}\nBackup policies where supported.\n{% /cards_item %}\n{% /cards %}"}, {"path": "docs/tooling/terraform/provider", "title": "Configuration", "description": "Configure the Appwrite Terraform provider for Cloud or Community Edition using endpoints, API keys, and optional environment variables.", "content": "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).\n\n# Terraform block {% #terraform-block %}\n\nDeclare 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).\n\n```hcl\nterraform {\n required_providers {\n appwrite = {\n source = \"appwrite/appwrite\"\n }\n }\n}\n```\n\n# Appwrite Cloud {% #appwrite-cloud %}\n\nReplace `` with your project’s region subdomain (see [Regions](/docs/products/network/regions)).\n\n```hcl\nprovider \"appwrite\" {\n endpoint = \"https://.cloud.appwrite.io/v1\"\n project_id = \"project-id\"\n api_key = \"api-key\"\n}\n```\n\n# Community Edition {% #community-edition %}\n\nFor 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):\n\n```hcl\nprovider \"appwrite\" {\n endpoint = \"https://appwrite-instance.com/v1\"\n project_id = \"project-id\"\n api_key = \"api-key\"\n self_signed = true\n}\n```\n\n# Environment variables {% #environment-variables %}\n\nYou can supply credentials via environment variables instead of hard-coding them in `.tf` files (recommended for CI and local development):\n\n```bash\nexport APPWRITE_ENDPOINT=\"https://.cloud.appwrite.io/v1\"\nexport APPWRITE_PROJECT_ID=\"project-id\"\nexport APPWRITE_API_KEY=\"api-key\"\n```\n\nWhen an environment variable is set, the matching provider argument does not need to appear in the configuration.\n\n| Provider argument | Environment variable | Required | Description |\n|-------------|------------------------|----------|-------------|\n| `endpoint` | `APPWRITE_ENDPOINT` | yes | Appwrite API endpoint |\n| `project_id` | `APPWRITE_PROJECT_ID` | no | Default project ID for resources (omit if you set `project_id` on each resource) |\n| `api_key` | `APPWRITE_API_KEY` | yes | API key with permissions for the resources you manage |\n| `self_signed` | - | no | Accept self-signed TLS certificates (Community Edition) |\n\n# Project scoping {% #project-scoping %}\n\nYou 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.\n\n# API keys {% #api-keys %}\n\nUse 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.\n\n# Related {% #related %}\n\n- [Overview](/docs/tooling/terraform): full resource list\n- [Databases](/docs/tooling/terraform/resources/databases): TablesDB resources\n- [Storage](/docs/tooling/terraform/resources/storage): buckets and files\n- [Messaging](/docs/tooling/terraform/resources/messaging): providers, topics, and subscribers\n- [Self-hosting](/docs/advanced/self-hosting): install and run the Appwrite server (not the same as configuring project resources with this provider)"}, {"path": "docs/tooling/terraform/resources/auth", "title": "Auth", "description": "Manage Appwrite users and teams with the Terraform provider.", "content": "The provider exposes **Auth** resources so you can align users and teams with the rest of your infrastructure-as-code workflow.\n\nFor 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.\n\n# Resources {% #resources %}\n\n| Resource | Purpose |\n|----------|---------|\n| `appwrite_auth_user` | Create and manage users |\n| `appwrite_auth_team` | Create and manage teams |\n\nUse these together with your normal [Auth](/docs/products/auth) and permission models; scope API keys appropriately when Terraform manages identity resources.\n\n# Examples {% #examples %}\n\n## Users {% #users %}\n\nPass 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.\n\n```hcl\nresource \"appwrite_auth_user\" \"john\" {\n name = \"john doe\"\n email = \"john@example.com\"\n password = var.user_password\n}\n\nresource \"appwrite_auth_user\" \"admin\" {\n name = \"admin\"\n email = \"admin@example.com\"\n password = var.admin_password\n labels = [\"admin\", \"staff\"]\n}\n```\n\n## Teams {% #teams %}\n\n`roles` sets the default roles granted to new team members. Omit it to use the provider default (`[\"owner\"]`).\n\n```hcl\nresource \"appwrite_auth_team\" \"engineering\" {\n name = \"engineering\"\n}\n\nresource \"appwrite_auth_team\" \"marketing\" {\n name = \"marketing\"\n roles = [\"owner\", \"editor\"]\n}\n```\n\n# Data sources {% #data-sources %}\n\nThe **`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.\n\n```hcl\ndata \"appwrite_auth_user\" \"admin\" {\n id = \"64f2cd7e27bda9f23ab6\"\n}\n\ndata \"appwrite_auth_team\" \"engineers\" {\n id = \"engineers\"\n}\n\noutput \"admin_email\" {\n value = data.appwrite_auth_user.admin.email\n}\n```\n\nSee 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).\n\n# Related {% #related %}\n\n- [Configuration](/docs/tooling/terraform/provider): authentication and endpoints\n- [Auth product docs](/docs/products/auth)"}, {"path": "docs/tooling/terraform/resources/backups", "title": "Backups", "description": "Configure Appwrite backup policies with Terraform where your plan supports them.", "content": "The `appwrite_backup_policy` resource configures **backup policies** for supported resources. Availability depends on your Appwrite Cloud plan or self-hosted setup.\n\nSee 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.\n\n# Resource {% #resource %}\n\n| Resource | Purpose |\n|----------|---------|\n| `appwrite_backup_policy` | Configure backup policies for supported resources |\n\nPolicies 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.\n\n# Example {% #example %}\n\n```hcl\nresource \"appwrite_backup_policy\" \"daily\" {\n name = \"daily database backup\"\n services = [\"databases\"]\n retention = 7\n schedule = \"0 2 * * *\"\n}\n\nresource \"appwrite_tablesdb\" \"main\" {\n name = \"main\"\n}\n\nresource \"appwrite_backup_policy\" \"production\" {\n name = \"production database backup\"\n services = [\"databases\"]\n resource_id = appwrite_tablesdb.main.id\n retention = 14\n schedule = \"0 */6 * * *\"\n}\n```\n\n# Related {% #related %}\n\n- [Databases](/docs/tooling/terraform/resources/databases): TablesDB resources you can target with `resource_id`\n- [Configuration](/docs/tooling/terraform/provider): authentication and endpoints"}, {"path": "docs/tooling/terraform/resources/databases", "title": "Databases", "description": "Use Terraform to manage Appwrite TablesDB databases, tables, columns, indexes, and rows with the official Appwrite provider.", "content": "The provider exposes Appwrite **TablesDB** as Terraform resources. Typical order: create a **database** (`appwrite_tablesdb`), then **tables**, then **columns** and **indexes**, and optionally **rows**.\n\nFor 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.\n\n# Resources {% #resources %}\n\n| Resource | Purpose |\n|----------|---------|\n| `appwrite_tablesdb` | Create a database in your project |\n| `appwrite_tablesdb_table` | Create a table within a database |\n| `appwrite_tablesdb_column` | Define columns (types, constraints, defaults) |\n| `appwrite_tablesdb_index` | Add indexes on one or more columns |\n| `appwrite_tablesdb_row` | Insert and manage rows in a table |\n\nRelationships use `database_id` and `table_id` references, often wired through Terraform resource attributes (for example `appwrite_tablesdb.main.id`).\n\n# Example {% #example %}\n\nThis pattern matches the upstream documentation: a database, a `users` table, several columns, and a unique index on email.\n\n```hcl\nresource \"appwrite_tablesdb\" \"main\" {\n id = \"main\"\n name = \"main\"\n}\n\nresource \"appwrite_tablesdb_table\" \"users\" {\n database_id = appwrite_tablesdb.main.id\n id = \"users\"\n name = \"users\"\n}\n\nresource \"appwrite_tablesdb_column\" \"name\" {\n database_id = appwrite_tablesdb.main.id\n table_id = appwrite_tablesdb_table.users.id\n key = \"name\"\n type = \"varchar\"\n size = 255\n required = true\n}\n\nresource \"appwrite_tablesdb_column\" \"email\" {\n database_id = appwrite_tablesdb.main.id\n table_id = appwrite_tablesdb_table.users.id\n key = \"email\"\n type = \"email\"\n required = true\n}\n\nresource \"appwrite_tablesdb_column\" \"age\" {\n database_id = appwrite_tablesdb.main.id\n table_id = appwrite_tablesdb_table.users.id\n key = \"age\"\n type = \"integer\"\n min = 0\n max = 150\n}\n\nresource \"appwrite_tablesdb_column\" \"role\" {\n database_id = appwrite_tablesdb.main.id\n table_id = appwrite_tablesdb_table.users.id\n key = \"role\"\n type = \"enum\"\n elements = [\"admin\", \"editor\", \"viewer\"]\n default = \"viewer\"\n}\n\nresource \"appwrite_tablesdb_column\" \"tags\" {\n database_id = appwrite_tablesdb.main.id\n table_id = appwrite_tablesdb_table.users.id\n key = \"tags\"\n type = \"varchar\"\n size = 64\n array = true\n}\n\nresource \"appwrite_tablesdb_column\" \"location\" {\n database_id = appwrite_tablesdb.main.id\n table_id = appwrite_tablesdb_table.users.id\n key = \"location\"\n type = \"point\"\n}\n\nresource \"appwrite_tablesdb_index\" \"email_unique\" {\n database_id = appwrite_tablesdb.main.id\n table_id = appwrite_tablesdb_table.users.id\n key = \"email_unique\"\n type = \"unique\"\n columns = [appwrite_tablesdb_column.email.key]\n}\n```\n\nColumn `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.\n\n# Seeding rows {% #seeding-rows %}\n\nThe `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.\n\n```hcl\nresource \"appwrite_tablesdb_row\" \"alice\" {\n database_id = appwrite_tablesdb.main.id\n table_id = appwrite_tablesdb_table.users.id\n data = jsonencode({\n name = \"alice\"\n email = \"alice@example.com\"\n })\n\n depends_on = [\n appwrite_tablesdb_column.name,\n appwrite_tablesdb_column.email,\n ]\n}\n```\n\nUse this for fixtures, default content, or small reference tables. For bulk data loads, prefer the SDKs or the Appwrite CLI.\n\n# Data sources {% #data-sources %}\n\n**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).\n\nSee the [Terraform Registry](https://registry.terraform.io/providers/appwrite/appwrite/latest/docs/data-sources/tablesdb) for the full argument list.\n\n```hcl\ndata \"appwrite_tablesdb\" \"existing\" {\n id = \"main\"\n}\n\nresource \"appwrite_tablesdb_table\" \"example\" {\n database_id = data.appwrite_tablesdb.existing.id\n id = \"example\"\n name = \"example\"\n}\n```\n\n# Related {% #related %}\n\n- [Databases product docs](/docs/products/databases): concepts and Console workflows\n- [Configuration](/docs/tooling/terraform/provider): authentication and endpoints"}, {"path": "docs/tooling/terraform/resources/functions", "title": "Functions", "description": "Manage Appwrite Functions, environment variables, and deployments with Terraform.", "content": "[Functions](/docs/products/functions) can be declared as Terraform resources, including **runtime**, **entrypoint**, **build commands**, **events**, and **per-function environment variables**. The provider also exposes an **`appwrite_function_deployment`** resource so you can ship code from a local tar archive or from a Git template alongside the rest of your configuration.\n\nSee the Terraform Registry: [function](https://registry.terraform.io/providers/appwrite/appwrite/latest/docs/resources/function), [function_variable](https://registry.terraform.io/providers/appwrite/appwrite/latest/docs/resources/function_variable), and [function_deployment](https://registry.terraform.io/providers/appwrite/appwrite/latest/docs/resources/function_deployment). The [provider repository](https://github.com/appwrite/terraform-provider-appwrite) includes examples.\n\n# Resources {% #resources %}\n\n| Resource | Purpose |\n|----------|---------|\n| `appwrite_function` | Create and update a function (runtime, entrypoint, commands, events, timeout, and related settings) |\n| `appwrite_function_variable` | Set environment variables scoped to a function |\n| `appwrite_function_deployment` | Push a new deployment from local code (`tar.gz`) or a Git template and optionally activate it |\n\n# Examples {% #examples %}\n\n## Basic function {% #basic-function %}\n\n```hcl\nresource \"appwrite_function\" \"hello_world\" {\n name = \"hello-world\"\n runtime = \"node-22\"\n entrypoint = \"index.js\"\n commands = \"npm install\"\n}\n```\n\n## Scheduled function {% #scheduled-function %}\n\nRun a function on a CRON schedule. Set `timeout` to cap the execution window.\n\n```hcl\nresource \"appwrite_function\" \"daily_cleanup\" {\n name = \"daily-cleanup\"\n runtime = \"node-22\"\n schedule = \"0 0 * * *\"\n timeout = 60\n}\n```\n\n## Event-driven function {% #event-driven-function %}\n\nTrigger the function from Appwrite platform events. `execute` controls who (or what) is allowed to invoke the function outside of the event trigger.\n\n```hcl\nresource \"appwrite_function\" \"on_user_create\" {\n name = \"on-user-create\"\n runtime = \"node-22\"\n events = [\"users.*.create\"]\n entrypoint = \"index.js\"\n execute = [\"any\"]\n}\n```\n\n## Environment variables {% #environment-variables %}\n\n`appwrite_function_variable` attaches environment variables to a function. Mark a variable with `secret = true` to prevent the value from being read back from the API after creation; update and delete continue to work.\n\n```hcl\nresource \"appwrite_function_variable\" \"api_url\" {\n function_id = appwrite_function.hello_world.id\n key = \"API_URL\"\n value = \"https://api.example.com\"\n}\n\nresource \"appwrite_function_variable\" \"secret_key\" {\n function_id = appwrite_function.hello_world.id\n key = \"SECRET_KEY\"\n value = var.secret_key\n secret = true\n}\n```\n\n## VCS integration {% #vcs-integration %}\n\nConnect a function to a Git repository so Appwrite deploys automatically on push. Obtain the `installation_id` and `provider_repository_id` from your Appwrite GitHub (or other VCS) integration.\n\n```hcl\nresource \"appwrite_function\" \"api\" {\n name = \"api\"\n runtime = \"node-22\"\n\n installation_id = \"your-github-installation-id\"\n provider_repository_id = \"your-repository-id\"\n provider_branch = \"main\"\n provider_root_directory = \"functions/api\"\n}\n```\n\n## Deploy from local code {% #deploy-from-code %}\n\nUse **`source_type = \"code\"`** to upload a `tar.gz` artifact from disk. Use `filesha256()` on the same path so Terraform detects code changes and triggers a new deployment.\n\n```hcl\nresource \"appwrite_function_deployment\" \"hello_world\" {\n function_id = appwrite_function.hello_world.id\n source_type = \"code\"\n code_path = \"dist/hello-world.tar.gz\"\n code_hash = filesha256(\"dist/hello-world.tar.gz\")\n entrypoint = \"index.js\"\n commands = \"npm install\"\n activate = true\n}\n```\n\n## Deploy from a template repository {% #deploy-from-template %}\n\nUse **`source_type = \"template\"`** to pull from a Git repository (for example one of Appwrite's starter templates). Pin `type` to `branch`, `tag`, or `commit` and set `reference` accordingly.\n\n```hcl\nresource \"appwrite_function_deployment\" \"from_template\" {\n function_id = appwrite_function.hello_world.id\n source_type = \"template\"\n repository = \"starter-template\"\n owner = \"appwrite\"\n root_directory = \"node\"\n type = \"branch\"\n reference = \"main\"\n activate = true\n}\n```\n\nBy default the resource waits for the deployment to reach a ready state before it returns; set `wait_for_ready = false` if you want `terraform apply` to return as soon as the deployment is created.\n\n# Data sources {% #data-sources %}\n\nThe **`appwrite_function`** data source reads an existing function by ID. Use it when the function was created outside Terraform or in another state, and you need to reference attributes such as `runtime` or the active `deployment_id`.\n\n```hcl\ndata \"appwrite_function\" \"existing\" {\n id = \"on-signup\"\n}\n\noutput \"function_runtime\" {\n value = data.appwrite_function.existing.runtime\n}\n```\n\nSee the [Terraform Registry](https://registry.terraform.io/providers/appwrite/appwrite/latest/docs/data-sources/function) for the full attribute list.\n\n# Related {% #related %}\n\n- [Functions product docs](/docs/products/functions)\n- [Configuration](/docs/tooling/terraform/provider)"}, {"path": "docs/tooling/terraform/resources/messaging", "title": "Messaging", "description": "Configure Appwrite Messaging providers, topics, and subscribers with Terraform for email, SMS, and push delivery.", "content": "[Messaging](/docs/products/messaging) integrates email, SMS, and push providers. The Terraform provider exposes **providers** (credentials and channel configuration), **topics** (groupings of subscribers for broadcasts), and **subscribers** (who receives messages on a topic).\n\nSee the Terraform Registry for generated schemas: [messaging_provider](https://registry.terraform.io/providers/appwrite/appwrite/latest/docs/resources/messaging_provider), [messaging_topic](https://registry.terraform.io/providers/appwrite/appwrite/latest/docs/resources/messaging_topic), and [messaging_subscriber](https://registry.terraform.io/providers/appwrite/appwrite/latest/docs/resources/messaging_subscriber). The [provider repository](https://github.com/appwrite/terraform-provider-appwrite) lists every `type` and optional field in source; provider-specific arguments apply only to the matching provider (for example Twilio `account_sid`, SMTP `host` and `encryption`, FCM `service_account_json`).\n\n# Resources {% #resources %}\n\n| Resource | Purpose |\n|----------|---------|\n| `appwrite_messaging_provider` | Register an email, SMS, or push provider (Sendgrid, SMTP, Twilio, FCM, and others) |\n| `appwrite_messaging_topic` | Define a topic for targeting subscribers |\n| `appwrite_messaging_subscriber` | Attach subscribers to a topic |\n\nThe `type` argument on `appwrite_messaging_provider` must be one of the supported provider kinds (for example `sendgrid`, `mailgun`, `smtp`, `resend`, `twilio`, `vonage`, `msg91`, `telesign`, `textmagic`, `apns`, `fcm`).\n\n# Examples {% #examples %}\n\n## Providers {% #providers %}\n\n```hcl\nresource \"appwrite_messaging_provider\" \"sendgrid\" {\n id = \"sendgrid\"\n name = \"sendgrid\"\n type = \"sendgrid\"\n api_key = var.sendgrid_api_key\n from_email = \"noreply@example.com\"\n from_name = \"application\"\n}\n\nresource \"appwrite_messaging_provider\" \"smtp\" {\n id = \"smtp\"\n name = \"smtp\"\n type = \"smtp\"\n host = \"smtp.example.com\"\n port = 587\n username = \"user@example.com\"\n password = var.smtp_password\n encryption = \"tls\"\n from_email = \"noreply@example.com\"\n}\n\nresource \"appwrite_messaging_provider\" \"twilio\" {\n id = \"twilio\"\n name = \"twilio\"\n type = \"twilio\"\n account_sid = var.twilio_account_sid\n auth_token = var.twilio_auth_token\n from = \"+1234567890\"\n}\n\nresource \"appwrite_messaging_provider\" \"fcm\" {\n id = \"fcm\"\n name = \"fcm\"\n type = \"fcm\"\n service_account_json = file(\"firebase-service-account.json\")\n}\n```\n\n## Topics {% #topics %}\n\n```hcl\nresource \"appwrite_messaging_topic\" \"announcements\" {\n id = \"announcements\"\n name = \"announcements\"\n}\n\nresource \"appwrite_auth_team\" \"engineering\" {\n id = \"engineering\"\n name = \"engineering\"\n}\n\nresource \"appwrite_messaging_topic\" \"engineering_alerts\" {\n id = \"engineering-alerts\"\n name = \"engineering-alerts\"\n subscribe = [\"team:${appwrite_auth_team.engineering.id}\"]\n}\n```\n\n## Subscribers {% #subscribers %}\n\nAttach a target (a user's email, phone, or push target ID) to a topic so it receives messages broadcast to that topic.\n\n```hcl\nresource \"appwrite_messaging_subscriber\" \"user_email\" {\n topic_id = appwrite_messaging_topic.announcements.id\n target_id = \"user-email-target-id\"\n}\n```\n\nTopics and subscribers support import by ID where documented; see the Registry for each resource.\n\n# Data sources {% #data-sources %}\n\nThe **`appwrite_messaging_topic`** data source reads a topic that already exists by ID. Use it to attach subscribers without taking ownership of the topic itself in Terraform.\n\n```hcl\ndata \"appwrite_messaging_topic\" \"announcements\" {\n id = \"announcements\"\n}\n\noutput \"topic_name\" {\n value = data.appwrite_messaging_topic.announcements.name\n}\n```\n\nSee the [Terraform Registry](https://registry.terraform.io/providers/appwrite/appwrite/latest/docs/data-sources/messaging_topic) for the full attribute list.\n\n# Related {% #related %}\n\n- [Messaging product docs](/docs/products/messaging)\n- [Configuration](/docs/tooling/terraform/provider)"}, {"path": "docs/tooling/terraform/resources/sites", "title": "Sites", "description": "Manage Appwrite Sites, environment variables, and deployments with Terraform.", "content": "[Sites](/docs/products/sites) supports Terraform resources for the **site** definition, **build-time environment variables**, and **deployments** that publish your site from a local artifact or a Git template.\n\nSee the Terraform Registry: [site](https://registry.terraform.io/providers/appwrite/appwrite/latest/docs/resources/site), [site_variable](https://registry.terraform.io/providers/appwrite/appwrite/latest/docs/resources/site_variable), and [site_deployment](https://registry.terraform.io/providers/appwrite/appwrite/latest/docs/resources/site_deployment). The [provider repository](https://github.com/appwrite/terraform-provider-appwrite) includes examples.\n\n# Resources {% #resources %}\n\n| Resource | Purpose |\n|----------|---------|\n| `appwrite_site` | Create and update a site (framework, build and install commands, runtimes, and related settings) |\n| `appwrite_site_variable` | Set environment variables for a site (for example `NEXT_PUBLIC_*` keys) |\n| `appwrite_site_deployment` | Push a new deployment from local code (`tar.gz`) or a Git template and optionally activate it |\n\n# Examples {% #examples %}\n\nThe `framework` argument accepts `nextjs`, `nuxt`, `sveltekit`, `astro`, `remix`, `analog`, `react`, `vue`, `vite`, `flutter`, and `other`. Use `other` for static sites or unsupported frameworks.\n\n## Static site {% #static-site %}\n\n```hcl\nresource \"appwrite_site\" \"landing_page\" {\n name = \"landing-page\"\n framework = \"other\"\n build_runtime = \"node-22\"\n}\n```\n\n## Next.js site {% #nextjs-site %}\n\n```hcl\nresource \"appwrite_site\" \"dashboard\" {\n name = \"dashboard\"\n framework = \"nextjs\"\n build_runtime = \"node-22\"\n install_command = \"npm install\"\n build_command = \"npm run build\"\n}\n```\n\n## Astro site {% #astro-site %}\n\nAstro and other static-output frameworks need an explicit `output_directory` pointing at the build artifact.\n\n```hcl\nresource \"appwrite_site\" \"docs\" {\n name = \"docs\"\n framework = \"astro\"\n build_runtime = \"node-22\"\n install_command = \"npm install\"\n build_command = \"npm run build\"\n output_directory = \"dist\"\n}\n```\n\n## Environment variables {% #environment-variables %}\n\n`appwrite_site_variable` attaches build-time environment variables (for example `NEXT_PUBLIC_*` keys). Mark sensitive values with `secret = true` so the value is not readable from the API after creation.\n\n```hcl\nresource \"appwrite_site_variable\" \"api_url\" {\n site_id = appwrite_site.dashboard.id\n key = \"NEXT_PUBLIC_API_URL\"\n value = \"https://api.example.com\"\n}\n\nresource \"appwrite_site_variable\" \"secret_key\" {\n site_id = appwrite_site.dashboard.id\n key = \"SECRET_KEY\"\n value = var.secret_key\n secret = true\n}\n```\n\n## VCS integration {% #vcs-integration %}\n\nConnect a site to a Git repository so Appwrite deploys automatically on push. Obtain the `installation_id` and `provider_repository_id` from your Appwrite GitHub (or other VCS) integration.\n\n```hcl\nresource \"appwrite_site\" \"app\" {\n name = \"app\"\n framework = \"nextjs\"\n build_runtime = \"node-22\"\n\n installation_id = \"your-github-installation-id\"\n provider_repository_id = \"your-repository-id\"\n provider_branch = \"main\"\n provider_root_directory = \"apps/web\"\n}\n```\n\n## Deploy from local code {% #deploy-from-code %}\n\nUse **`source_type = \"code\"`** to upload a `tar.gz` build artifact. `code_hash` lets Terraform detect changes to the artifact and trigger a fresh deployment on the next `apply`.\n\n```hcl\nresource \"appwrite_site_deployment\" \"dashboard\" {\n site_id = appwrite_site.dashboard.id\n source_type = \"code\"\n code_path = \"dist/dashboard.tar.gz\"\n code_hash = filesha256(\"dist/dashboard.tar.gz\")\n install_command = \"npm install\"\n build_command = \"npm run build\"\n output_directory = \".next\"\n activate = true\n}\n```\n\n## Deploy from a template repository {% #deploy-from-template %}\n\nUse **`source_type = \"template\"`** to deploy from a Git repository (for example one of Appwrite's site templates). Pin `type` to `branch`, `tag`, or `commit` and set `reference` accordingly.\n\n```hcl\nresource \"appwrite_site_deployment\" \"from_template\" {\n site_id = appwrite_site.dashboard.id\n source_type = \"template\"\n repository = \"templates-for-sites\"\n owner = \"appwrite\"\n root_directory = \"nextjs/starter\"\n type = \"branch\"\n reference = \"main\"\n activate = true\n}\n```\n\nBy default the resource waits for the build to finish before it returns; set `wait_for_ready = false` if you want `terraform apply` to return as soon as the deployment is queued.\n\n# Data sources {% #data-sources %}\n\nThe **`appwrite_site`** data source reads an existing site by ID. Use it when the site was created outside Terraform or in another state, and you need to reference attributes such as `framework` or the active `deployment_id`.\n\n```hcl\ndata \"appwrite_site\" \"existing\" {\n id = \"dashboard\"\n}\n\noutput \"site_framework\" {\n value = data.appwrite_site.existing.framework\n}\n```\n\nSee the [Terraform Registry](https://registry.terraform.io/providers/appwrite/appwrite/latest/docs/data-sources/site) for the full attribute list.\n\n# Related {% #related %}\n\n- [Sites product docs](/docs/products/sites)\n- [Configuration](/docs/tooling/terraform/provider)"}, {"path": "docs/tooling/terraform/resources/storage", "title": "Storage", "description": "Manage Appwrite Storage buckets and files with the Terraform provider, including file limits, extensions, compression, and security options.", "content": "The `appwrite_storage_bucket` resource manages [Storage](/docs/products/storage) buckets in your Appwrite project: file size limits, allowed extensions, compression, image transformations, encryption, and optional antivirus. The `appwrite_storage_file` resource uploads and manages **files** inside a bucket from a local path on the machine running Terraform.\n\nSee the Terraform Registry for generated schemas: [storage_bucket](https://registry.terraform.io/providers/appwrite/appwrite/latest/docs/resources/storage_bucket) and [storage_file](https://registry.terraform.io/providers/appwrite/appwrite/latest/docs/resources/storage_file). The [provider repository](https://github.com/appwrite/terraform-provider-appwrite) contains source and examples.\n\n# Resources {% #resources %}\n\n| Resource | Purpose |\n|----------|---------|\n| `appwrite_storage_bucket` | Create and update a storage bucket |\n| `appwrite_storage_file` | Upload and manage a file in a bucket (local `file_path`) |\n\n# Example {% #example %}\n\n```hcl\nresource \"appwrite_storage_bucket\" \"uploads\" {\n name = \"uploads\"\n}\n\nresource \"appwrite_storage_bucket\" \"images\" {\n name = \"images\"\n maximum_file_size = 10485760\n allowed_file_extensions = [\"jpg\", \"png\", \"webp\", \"gif\"]\n compression = \"gzip\"\n transformations = true\n}\n\nresource \"appwrite_storage_bucket\" \"documents\" {\n name = \"documents\"\n file_security = true\n encryption = true\n antivirus = true\n}\n\nresource \"appwrite_storage_file\" \"logo\" {\n bucket_id = appwrite_storage_bucket.images.id\n name = \"logo.png\"\n file_path = \"assets/logo.png\"\n}\n\nresource \"appwrite_storage_file\" \"public_config\" {\n bucket_id = appwrite_storage_bucket.documents.id\n name = \"config.json\"\n file_path = \"assets/config.json\"\n permissions = [\"read(\\\"any\\\")\"]\n}\n```\n\nSet `permissions` on a file to grant explicit read/write access (for example `read(\"any\")` for public downloads, or `read(\"user:USER_ID\")` for a single user). File-level permissions are only enforced on buckets with `file_security = true`; without it, bucket-level `permissions` govern every file.\n\nCommon optional arguments on buckets include `maximum_file_size`, `allowed_file_extensions`, `compression` (`none`, `gzip`, `zstd`), `transformations`, `file_security`, `encryption`, `antivirus`, `enabled`, and `permissions`. Read-only attributes expose `created_at` and `updated_at`.\n\nBuckets and files support [Terraform import](https://developer.hashicorp.com/terraform/cli/commands/import) where the upstream resource documents an ID format (see the Registry for each resource).\n\n# Data sources {% #data-sources %}\n\nThe **`appwrite_storage_bucket`** data source reads a bucket by ID instead of creating one. Use it when the bucket was created outside Terraform or lives in another state, and you need to reference attributes like `name`, `compression`, or `maximum_file_size`.\n\nSee the [Terraform Registry](https://registry.terraform.io/providers/appwrite/appwrite/latest/docs/data-sources/storage_bucket) for the full attribute list.\n\n```hcl\ndata \"appwrite_storage_bucket\" \"uploads\" {\n id = \"uploads\"\n}\n\nresource \"appwrite_storage_file\" \"logo\" {\n bucket_id = data.appwrite_storage_bucket.uploads.id\n name = \"logo.png\"\n file_path = \"assets/logo.png\"\n}\n```\n\n# Related {% #related %}\n\n- [Storage product docs](/docs/products/storage)\n- [Configuration](/docs/tooling/terraform/provider)"}, {"path": "docs/tooling/terraform/resources/webhooks", "title": "Webhooks", "description": "Register Appwrite webhooks with Terraform to deliver events to your HTTP endpoints.", "content": "The `appwrite_webhook` resource registers a **URL** and **event** subscriptions so Appwrite can notify your services when resources change. Configure **`tls`** for TLS verification on the webhook URL, **`auth_username`** and **`auth_password`** when your endpoint expects HTTP basic authentication, and read **`secret`** from Terraform state when you verify **incoming** webhook signatures on your server.\n\nSee the Terraform Registry: [webhook](https://registry.terraform.io/providers/appwrite/appwrite/latest/docs/resources/webhook). The [provider repository](https://github.com/appwrite/terraform-provider-appwrite) lists the full argument reference.\n\n# Resource {% #resource %}\n\n| Resource | Purpose |\n|----------|---------|\n| `appwrite_webhook` | Register a webhook URL and subscribe to Appwrite events |\n\n# Examples {% #examples %}\n\n## Basic {% #basic %}\n\n```hcl\nresource \"appwrite_webhook\" \"user_events\" {\n name = \"user-events\"\n url = \"https://api.example.com/webhooks/users\"\n events = [\"users.*.create\", \"users.*.update\"]\n tls = true\n}\n```\n\n## TLS and HTTP basic auth to your endpoint {% #authenticated %}\n\n```hcl\nresource \"appwrite_webhook\" \"authenticated\" {\n name = \"authenticated webhook\"\n url = \"https://api.example.com/webhooks/secure\"\n events = [\"databases.*.collections.*.documents.*.create\"]\n auth_username = \"webhook\"\n auth_password = var.webhook_password\n tls = true\n}\n```\n\nUse the read-only **`secret`** attribute when configuring **signature verification** for payloads Appwrite sends to your URL (see [Webhooks](/docs/apis/webhooks) in the platform docs).\n\n# Data sources {% #data-sources %}\n\nThe **`appwrite_webhook`** data source reads a webhook that already exists by ID. Use it to reference an existing webhook from other resources or outputs without recreating it in Terraform.\n\n```hcl\ndata \"appwrite_webhook\" \"slack\" {\n id = \"64f2cd7e27bda9f23ab6\"\n}\n\noutput \"webhook_url\" {\n value = data.appwrite_webhook.slack.url\n}\n```\n\nSee the [Terraform Registry](https://registry.terraform.io/providers/appwrite/appwrite/latest/docs/data-sources/webhook) for the full attribute list.\n\n# Related {% #related %}\n\n- [Configuration](/docs/tooling/terraform/provider): authentication and endpoints\n- [Webhooks](/docs/apis/webhooks): event delivery, headers, and signatures"}, {"path": "docs/tutorials/android/step-1", "title": "Build an ideas tracker with Android", "description": "Learn to build an Android app with no backend code using an Appwrite backend.", "content": "**Idea tracker**: an app to track all the side project ideas that you'll start, but probably never finish.\nIn this tutorial, you will build Idea tracker with Appwrite and Android.\n\n## Concepts {% #concepts %}\nThis tutorial will introduce the following concepts:\n\n1. Setting up your first project\n2. Authentication \n3. Databases and tables\n4. Queries and pagination\n\n\n## Prerequisites {% #prerequisites %}\n1. Basic knowledge of Kotlin and Android development.\n2. Have [Android Studio](https://developer.android.com/studio) installed on your computer."}, {"path": "docs/tutorials/android/step-2", "title": "Create app", "description": "Create a Android app project using Appwrite.", "content": "## Create Android project {% #create-android-project %}\n\nCreate a Android app with the Android Studio **New Project** wizard. Select **Empty Activity** as the template.\n\n## Add dependencies {% #add-dependencies %}\n\nInstall the Android Appwrite SDK.\n\nAdd the following to your dependencies in the `app/build.gradle` file:\n\n```groovy\nimplementation(\"io.appwrite:sdk-for-android:8.1.0\")\n```\n\nIn case you need to create OAuth 2 sessions in the future, the following activity needs to be added inside the `` tag, along side the existing `` tags in your [AndroidManifest.xml](https://developer.android.com/guide/topics/manifest/manifest-intro). \nBe sure to replace the **** string with your actual Appwrite project ID. \nYou can find your Appwrite project ID in you project settings screen in your Appwrite Console.\n\n```xml\n\n ...\n \n ...\n \n \n \n \n \n \n \" />\n \n \n \n\n```\n\n## Using the emulator {% #using-the-emulator %}\n\nRun your app on the [Android emulator](https://developer.android.com/studio/run/emulator).\n You can use the emulator to test your app on different versions of the Android platform and different screen sizes without the need to use physical devices.\nFor now, you should see a blank screen."}, {"path": "docs/tutorials/android/step-3", "title": "Set up Appwrite", "description": "Initialize Appwrite in your Android project.", "content": "## Create project {% #create-project %}\n\nHead to the [Appwrite Console](https://cloud.appwrite.io/console).\n\n{% only_dark %}\n![Create project screen](/images/docs/quick-starts/dark/create-project.avif)\n{% /only_dark %}\n{% only_light %}\n![Create project screen](/images/docs/quick-starts/create-project.avif)\n{% /only_light %}\n\nIf this is your first time using Appwrite, create an account and create your first project.\n\nThen, under **Add a platform**, add an **Android app**. The **Package Name** should be the same as the one you used when creating your app.\n\n{% only_dark %}\n![Add a platform](/images/docs/quick-starts/dark/add-platform.avif)\n{% /only_dark %}\n{% only_light %}\n![Add a platform](/images/docs/quick-starts/add-platform.avif)\n{% /only_light %}\n\nYou can skip optional steps.\n\n## Initialize Appwrite SDK {% #init-sdk %}\n\nTo use Appwrite in our Android app, we'll need to find our project ID. Find your project's ID in the **Settings** page. \n\n{% only_dark %}\n![Project settings screen](/images/docs/quick-starts/dark/project-id.avif)\n{% /only_dark %}\n{% only_light %}\n![Project settings screen](/images/docs/quick-starts/project-id.avif)\n{% /only_light %}\n\nCreate a new file `services/Appwrite.kt` to hold our Appwrite related code.\nOnly one instance of the `Client` class should be created per app.\nAdd the following code to it, replacing `` with your project ID.\n\n```kotlin\npackage .services\n\nimport android.content.Context\nimport io.appwrite.Client\n\nobject Appwrite {\n private const val ENDPOINT = \"https://.cloud.appwrite.io/v1\"\n private const val PROJECT_ID = \"\"\n\n private lateinit var client: Client\n\n fun init(context: Context) {\n client = Client(context)\n .setEndpoint(ENDPOINT)\n .setProject(PROJECT_ID)\n }\n}\n```"}, {"path": "docs/tutorials/android/step-4", "title": "Add authentication", "description": "Add Appwrite authentication to you Android app.", "content": "## Creating an account service {% #creating-an-account-service %}\n\nWe can use services to abstract business logic from our views.\nCreate a service to handle user authentication with a new file `services/AccountService.kt`.\n\nAdd the following code to it.\n\n```kotlin\npackage .services\n\nimport io.appwrite.Client\nimport io.appwrite.ID\nimport io.appwrite.models.User\nimport io.appwrite.exceptions.AppwriteException\nimport io.appwrite.services.Account\n\nclass AccountService(client: Client) {\n private val account = Account(client)\n\n suspend fun getLoggedIn(): User>? {\n return try {\n account.get()\n } catch (e: AppwriteException) {\n null\n }\n }\n\n suspend fun login(email: String, password: String): User>? {\n return try {\n account.createEmailPasswordSession(\n email = email,\n password = password\n )\n getLoggedIn()\n } catch (e: AppwriteException) {\n null\n }\n }\n\n suspend fun register(email: String, password: String): User>? {\n return try {\n account.create(\n userId = ID.unique(),\n email = email,\n password = password\n )\n login(email, password)\n } catch (e: AppwriteException) {\n null\n }\n }\n\n suspend fun logout() {\n account.deleteSession(\"current\")\n }\n}\n```\n\nWe can now use this service to login, register and logout a user. Integrate the service to the `/services/Appwrite.kt` file.\n\nLook for `// Add this line 👇` to find where the changes made here.\n\n```kotlin\nobject Appwrite {\n private const val ENDPOINT = \"https://.cloud.appwrite.io/v1\"\n private const val PROJECT_ID = \"\"\n\n private lateinit var client: Client\n\n // Add this line 👇\n internal lateinit var account: AccountService\n\n fun init(context: Context) {\n client = Client(context)\n .setEndpoint(ENDPOINT)\n .setProject(PROJECT_ID)\n\n // Add this line 👇\n account = AccountService(client)\n }\n}\n```\n\n## Login screen {% #login-screen %}\n\nUsing this service, we can now create a screen to login or register a user.\n\nCreate a new file `ui/screens/UserScreen.kt` and add the following code to it.\n\n```kotlin\npackage .ui.screens\n\nimport .services.AccountService\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.text.KeyboardOptions\nimport androidx.compose.material3.Button\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TextField\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.MutableState\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.rememberCoroutineScope\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.text.input.KeyboardType\nimport androidx.compose.ui.text.input.PasswordVisualTransformation\nimport androidx.compose.ui.unit.dp\nimport io.appwrite.models.User\nimport kotlinx.coroutines.launch\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun UserScreen(\n user: MutableState>?>,\n accountService: AccountService\n) {\n val coroutineScope = rememberCoroutineScope()\n var error by remember { mutableStateOf(null) }\n\n fun onLogin(email: String, password: String) {\n coroutineScope.launch {\n user.value = accountService.login(email, password)\n }\n error = if (user.value === null) {\n \"Unable to login\"\n } else {\n null\n }\n }\n\n fun onRegister(email: String, password: String) {\n coroutineScope.launch {\n user.value = accountService.register(email, password)\n }\n error = if (user.value === null) {\n \"Unable to register\"\n } else {\n null\n }\n }\n\n fun onLogout() {\n coroutineScope.launch {\n accountService.logout()\n user.value = null\n }\n }\n\n var username by remember { mutableStateOf(\"\") }\n var password by remember { mutableStateOf(\"\") }\n\n if (user.value !== null) {\n Column(\n modifier = Modifier.fillMaxSize(),\n horizontalAlignment = Alignment.CenterHorizontally,\n verticalArrangement = Arrangement.Center\n ) {\n Text(text = \"Logged in as ${user.value!!.email}\")\n Button(onClick = { onLogout() }) {\n Text(\"Logout\")\n }\n }\n }\n\n Column(\n modifier = Modifier.fillMaxSize(),\n horizontalAlignment = Alignment.CenterHorizontally,\n verticalArrangement = Arrangement.Center\n ) {\n TextField(\n value = username,\n onValueChange = { username = it },\n label = { Text(\"Email\") }\n )\n TextField(\n value = password,\n onValueChange = { password = it },\n label = { Text(\"Password\") },\n visualTransformation = PasswordVisualTransformation(),\n keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password)\n )\n Row(\n modifier = Modifier\n .fillMaxWidth()\n .padding(16.dp),\n horizontalArrangement = Arrangement.SpaceBetween\n ) {\n Button(onClick = { onLogin(username, password) }) {\n Text(\"Login\")\n }\n Button(onClick = { onRegister(username, password) }) {\n Text(\"Register\")\n }\n }\n if (error !== null) {\n Text(\n text = error!!,\n modifier = Modifier.padding(16.dp),\n color = androidx.compose.ui.graphics.Color.Red\n )\n }\n }\n}\n```"}, {"path": "docs/tutorials/android/step-5", "title": "Add MainActivity", "description": "Add navigation to your Android application.", "content": "## Creating the MainActivity {% #creating-the-mainactivity %}\n\nTo show the new screen, we need to set up our `MainActivity` class. Open `MainActivity.kt` and update it with following code.\nThis code sets up our `MainActivity` with a bottom navigation bar, including a **User** screen.\n\n```kotlin\npackage \n\nimport android.os.Bundle\nimport androidx.activity.ComponentActivity\nimport androidx.activity.compose.setContent\nimport androidx.compose.runtime.*\nimport androidx.compose.foundation.layout.*\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.filled.Person\nimport androidx.compose.material3.*\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport io.appwrite.models.User\nimport .services.Appwrite\nimport .ui.screens.UserScreen\nimport .services.AccountService\n\nenum class Screen {\n User\n}\n\nclass MainActivity : ComponentActivity() {\n override fun onCreate(savedInstanceState: Bundle?) {\n super.onCreate(savedInstanceState)\n\n Appwrite.init(applicationContext)\n\n setContent {\n AppContent(Appwrite.account)\n }\n }\n}\n\n@Composable\nprivate fun AppBottomBar(screen: MutableState) {\n BottomAppBar {\n Row(\n modifier = Modifier.fillMaxWidth(),\n horizontalArrangement = Arrangement.Center\n ) {\n IconButton(onClick = { screen.value = Screen.User }) {\n Column(horizontalAlignment = Alignment.CenterHorizontally) {\n Icon(Icons.Default.Person, contentDescription = \"User\")\n Text(\"User\")\n }\n }\n }\n }\n}\n\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nprivate fun AppContent(accountService: AccountService) {\n val user = remember { mutableStateOf>?>(null) }\n val screen = remember { mutableStateOf(Screen.User) }\n\n LaunchedEffect(screen) {\n user.value = accountService.getLoggedIn()\n }\n\n Scaffold(bottomBar = { AppBottomBar(screen) }) { padding ->\n Column(modifier = Modifier.padding(padding)) {\n when (screen.value) {\n Screen.User -> UserScreen(user, accountService)\n else -> Text(\"Ideas screen\")\n }\n }\n }\n}\n```\n\n## Test the MainActivity {% #test-the-mainactivity %}\n\nLaunch the app and you should be able to use the Login screen to register, login, and logout. Confirm your email address is displayed once you are logged in."}, {"path": "docs/tutorials/android/step-6", "title": "Add database", "description": "Add databases and queries to store user data in you Android application.", "content": "# Create table {% #create-table %}\n\nIn Appwrite, data is stored as a table of rows. Create a table in the [Appwrite Console](https://cloud.appwrite.io/) to store our ideas.\n\n{% only_dark %}\n![Create project screen](/images/docs/tutorials/dark/idea-tracker-table.avif)\n{% /only_dark %}\n\n{% only_light %}\n![Create project screen](/images/docs/tutorials/idea-tracker-table.avif)\n{% /only_light %}\n\nCreate a new table with the following columns:\n\n| Column | Type | Required | Size |\n|-------------|--------|----------|----------|\n| userId | Varchar | Yes | 200 |\n| title | Varchar | Yes | 200 |\n| description | Text | No | - |\n\n{% only_dark %}\n![Table permissions screen](/images/docs/tutorials/dark/idea-tracker-permissions.avif)\n{% /only_dark %}\n{% only_light %}\n![Table permissions screen](/images/docs/tutorials/idea-tracker-permissions.avif)\n{% /only_light %}\n\nNavigate to the **Settings** tab of your table, add the role **Any** and check the **Read** box.\nNext, add a **Users** role and give them access to **Create**, **Update** and **Delete** by checking those boxes.\n\n## Add and remove methods {% #add-add-remove-methods %}\n\nNow that you have a table to hold ideas, we can read and write to it from our app.\nCreate a new file `services/IdeasService.kt` and add the following code to it. Replace the values for `ideaDatabaseId` and `ideaTableId` with the IDs of the database and table you created in the previous step.\n\n```kotlin\npackage .services\n\nimport io.appwrite.Client\nimport io.appwrite.ID\nimport io.appwrite.Query\nimport io.appwrite.models.Row\nimport io.appwrite.services.TablesDB\n\nclass IdeaService(client: Client) {\n companion object {\n private const val ideaDatabaseId = \"\"\n private const val ideaTableId = \"\"\n }\n\n private val tablesDB = TablesDB(client)\n\n suspend fun fetch(): List>> {\n return tablesDB.listRows(\n ideaDatabaseId,\n ideaTableId,\n listOf(Query.orderDesc(\"\\$createdAt\"), Query.limit(10))\n ).rows\n }\n\n suspend fun add(\n userId: String,\n title: String,\n description: String\n ): Row> {\n return tablesDB.createRow(\n ideaDatabaseId,\n ideaTableId,\n ID.unique(),\n mapOf(\n \"userId\" to userId,\n \"title\" to title,\n \"description\" to description\n )\n )\n }\n\n suspend fun remove(id: String) {\n tablesDB.deleteRow(\n ideaDatabaseId,\n ideaTableId,\n id\n )\n }\n}\n```\n\nUpdate the `services/Appwrite.kt` file to add a new property for the `IdeaService` class.\n\nLook for `// Add this line 👇` to find where the changes made here.\n\n```kotlin\npackage io.appwrite.tutorialforandroid.services\n\nimport android.content.Context\nimport io.appwrite.Client\n\nobject Appwrite {\n private const val ENDPOINT = \"https://.cloud.appwrite.io/v1\"\n private const val PROJECT_ID = \"\"\n\n private lateinit var client: Client\n\n // Add this line 👇\n internal lateinit var ideas: IdeaService\n internal lateinit var account: AccountService\n\n fun init(context: Context) {\n client = Client(context)\n .setEndpoint(ENDPOINT)\n .setProject(PROJECT_ID)\n\n // Add this line 👇\n ideas = IdeaService(client)\n account = AccountService(client)\n }\n}\n```"}, {"path": "docs/tutorials/android/step-7", "title": "Create ideas page", "description": "Add pagination and ordering to you Android application powered by Appwrite Databases.", "content": "Using the `IdeasService`, we can build a screen to submit and view ideas.\nOverwrite the contents of `ui/screens/IdeasScreen.kt` with the following code.\n\n```kotlin\npackage .screens\n\n\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.lazy.LazyColumn\nimport androidx.compose.foundation.lazy.items\nimport androidx.compose.material3.Button\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TextField\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.LaunchedEffect\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.rememberCoroutineScope\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.unit.dp\nimport io.appwrite.models.Row\nimport io.appwrite.models.User\nimport kotlinx.coroutines.launch\nimport services.IdeaService\n\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun IdeasScreen(\n user: User>?,\n ideasService: IdeaService\n) {\n var ideas by remember { mutableStateOf>>>(listOf()) }\n val coroutineScope = rememberCoroutineScope()\n\n LaunchedEffect(ideasService) {\n coroutineScope.launch {\n ideas = ideasService.fetch()\n }\n }\n\n fun onSubmit(title: String, description: String) {\n if (user === null) return\n coroutineScope.launch {\n ideas = listOf(ideasService.add(user.id, title, description)).plus(ideas)\n }\n }\n\n fun onRemove(ideaId: String) {\n coroutineScope.launch {\n ideas = ideas.filter { idea -> idea.id !== ideaId }\n ideasService.remove(ideaId)\n }\n }\n\n var title by remember { mutableStateOf(\"\") }\n var description by remember { mutableStateOf(\"\") }\n\n Column(\n modifier = Modifier.fillMaxSize(),\n horizontalAlignment = Alignment.CenterHorizontally\n ) {\n if (user != null) {\n TextField(\n value = title,\n onValueChange = { title = it },\n label = { Text(\"Title\") },\n modifier = Modifier\n .fillMaxWidth()\n .padding(16.dp)\n )\n TextField(\n value = description,\n onValueChange = { description = it },\n label = { Text(\"Description\") },\n modifier = Modifier\n .fillMaxWidth()\n .padding(16.dp)\n )\n Button(onClick = {\n onSubmit(title, description)\n title = \"\"\n description = \"\"\n }) {\n Text(\"Submit\")\n }\n }\n\n LazyColumn(modifier = Modifier.fillMaxSize()) {\n items(ideas) { idea ->\n Column(modifier = Modifier.padding(16.dp)) {\n Text(text = idea.data[\"title\"]?.toString() ?: \"\", fontWeight = FontWeight(700))\n Text(text = idea.data[\"description\"]?.toString() ?: \"\")\n if (user?.id == idea.data[\"userId\"])\n Button(onClick = { onRemove(idea.id) }) {\n Text(\"Remove\")\n }\n }\n }\n }\n }\n}\n```\n\n## Update navigation {% #update-navigation %}\n\nUpdate `MainActivity.kt` to add the `IdeasScreen` to the navigation bar.\n\nLook for `// Add this line 👇` to find where the changes made here.\n\n```kotlin\npackage \n\nimport android.os.Bundle\nimport androidx.activity.ComponentActivity\nimport androidx.activity.compose.setContent\nimport androidx.compose.runtime.*\nimport androidx.compose.foundation.layout.*\nimport androidx.compose.material.icons.Icons\n// Add this line 👇\nimport androidx.compose.material.icons.filled.List\nimport androidx.compose.material.icons.filled.Person\nimport androidx.compose.material3.*\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport io.appwrite.models.User\nimport .services.Appwrite\nimport .services.AccountService\nimport .ui.screens.UserScreen\n\n// Add this line 👇\nimport .services.IdeaService\n\n// Add this line 👇\nimport .ui.screens.IdeasScreen\n\n\nenum class Screen {\n User,\n // Add this line 👇\n Ideas\n}\n\nclass MainActivity : ComponentActivity() {\n override fun onCreate(savedInstanceState: Bundle?) {\n super.onCreate(savedInstanceState)\n\n Appwrite.init(applicationContext)\n\n setContent {\n // Update this line 👇\n AppContent(Appwrite.account, Appwrite.ideas)\n }\n }\n}\n\n// Add navigation with this composable 👇\n@Composable\nprivate fun AppBottomBar(screen: MutableState) {\n BottomAppBar {\n Row(\n modifier = Modifier.fillMaxWidth(),\n horizontalArrangement = Arrangement.Center\n ) {\n IconButton(onClick = { screen.value = Screen.Ideas }) {\n Column(horizontalAlignment = Alignment.CenterHorizontally) {\n Icon(Icons.Default.List, contentDescription = \"Ideas\")\n Text(\"Ideas\")\n }\n }\n IconButton(onClick = { screen.value = Screen.User }) {\n Column(horizontalAlignment = Alignment.CenterHorizontally) {\n Icon(Icons.Default.Person, contentDescription = \"User\")\n Text(\"User\")\n }\n }\n }\n }\n}\n\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\n// Update this line 👇\nprivate fun AppContent(accountService: AccountService, ideasService: IdeaService) {\n val user = remember { mutableStateOf>?>(null) }\n // Update this line 👇\n val screen = remember { mutableStateOf(Screen.Ideas) }\n\n LaunchedEffect(screen) {\n user.value = accountService.getLoggedIn()\n }\n\n Scaffold(bottomBar = { AppBottomBar(screen) }) { padding ->\n Column(modifier = Modifier.padding(padding)) {\n when (screen.value) {\n Screen.User -> UserScreen(user, accountService)\n // Update this line 👇\n else -> IdeasScreen(user.value, ideasService)\n }\n }\n }\n}\n```"}, {"path": "docs/tutorials/android/step-8", "title": "Next steps", "description": "View your Android project powered by Appwrite authentication and databases.", "content": "## Test your project {% #test-project %}\n\nYou can now run your project and test it on your Android device or emulator."}, {"path": "docs/tutorials/apple/step-1", "title": "Coming soon", "description": "Learn to build an Apple app with no backend code using an Appwrite backend.", "content": "Improve the docs, add this guide.\n\nWe still don't have this guide in place, but we do have some great news. \nThe Appwrite docs, just like Appwrite, is completely open sourced.\nThis means, anyone can help improve them and add new guides and tutorials. \n\nIf you see this page, **we're actively looking for contributions to this page**.\nFollow 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."}, {"path": "docs/tutorials/astro-ssr-auth/step-1", "title": "Server-side authentication with Astro", "description": "Add SSR authentication to your Astro app with Appwrite", "content": "Appwrite takes away the stress of building and maintaining a backend. Appwrite helps implement authentication, databases, file storage, and respond to real-time events with **secure** APIs out of the box. \nIf you're a Astro developer, the examples in this guide show you how Appwrite can help you add authentication to Astro apps faster.\n\n## Before you start {% #before-you-start %}\n\nBefore following this tutorial, have the following prepared:\n\n- A recent version of [Node.js](https://nodejs.org/en/download/) installed on your system.\n- A basic knowledge of Astro.\n\nClone the [demos-for-astro](https://github.com/appwrite/demos-for-astro) examples and follow along with the source code."}, {"path": "docs/tutorials/astro-ssr-auth/step-2", "title": "Create project", "description": "Add authentication to a Astro project using Appwrite.", "content": "Create an [Astro project](https://docs.astro.build/en/install/auto/#1-run-the-setup-wizard) using:\n\n```sh\nnpm create astro@latest\n```\n\nThe command prompt will be something similar to this.\n\n```sh\nWhere should we create your new project? ./my-astro-project\n\nHow would you like to start your new project? Empty\n\nDo you plan to write TypeScript? No\n\nInstall dependencies? Yes\n\nInitialize a new git repository? Yes\n```\n\nAfter the prompt is finished, you can head over to the newly created project.\n\n```sh\ncd my-astro-project\n```\n\n## Install Appwrite {% #install-appwrite %}\n\nAppwrite provides a Node SDK that can be used in your Astro apps. You can use Appwrite by installing the Node SDK as an NPM package. The Node SDK is intended for server-side use. If you want to use Appwrite in a client-side application, you should use the Web SDK instead.\n\n```sh\nnpm install node-appwrite\n```\n\n## Add Node adapter to Astro {% #add-node-to-astro %}\n\nTo use Astro as an SSR framework, you need to add the Node adapter to your project. Run the following command:\n\n```sh\nnpx astro add node\n```"}, {"path": "docs/tutorials/astro-ssr-auth/step-3", "title": "Initialize SDK", "description": "Add authentication to a Astro project using Appwrite.", "content": "Before you can use Appwrite, you need to create the Appwrite `Client` and set the project ID and endpoint.\nThe client is then used to create services like `Databases` and `Account`, so they all point to the same Appwrite project.\n\nCreate a function to build services you need in a file like `src/server/appwrite.js` and **exporting the instances**.\n\nAs part of the function, set the current user's session if they are logged in. This is done by accessing the session cookie from the request and calling the `setSession(session)` with the cookie value.\n\n{% info title=\"Appwrite client security\" %}\nNotice that `createAdminClient` and `createSessionClient` returns **a new instance** of the Appwrite Client.\nWhen using Appwrite in server-integrations, it's important to **never share a `Client` instance** between two requests.\nDoing so could create security vulnerabilities.\n{% /info %}\n\n```js\n// src/server/appwrite.js\n\nimport { Client, Account } from \"node-appwrite\";\n\n// The name of your cookie that will store the session\nexport const SESSION_COOKIE = \"my-custom-session\";\n\n// Admin client, used to create new accounts\nexport function createAdminClient() {\n const client = new Client()\n .setEndpoint(import.meta.env.PUBLIC_APPWRITE_ENDPOINT)\n .setProject(import.meta.env.PUBLIC_APPWRITE_PROJECT)\n .setKey(import.meta.env.APPWRITE_KEY); // Set the API key here!\n\n // Return the services you need\n return {\n get account() {\n return new Account(client);\n },\n };\n}\n\n// Session client, used to make requests on behalf of the logged in user\nexport function createSessionClient(request) {\n const client = new Client()\n .setEndpoint(import.meta.env.PUBLIC_APPWRITE_ENDPOINT)\n .setProject(import.meta.env.PUBLIC_APPWRITE_PROJECT);\n\n // Get the session cookie from the request and set the session\n const cookies = parseCookies(request.headers.get(\"cookie\") ?? \"\");\n const session = cookies.get(SESSION_COOKIE);\n if (!session) {\n throw new Error(\"Session cookie not found\");\n }\n\n client.setSession(session);\n\n // Return the services you need\n return {\n get account() {\n return new Account(client);\n },\n };\n}\n\n// Helper function to parse cookies\nfunction parseCookies(cookies) {\n const map = new Map();\n for (const cookie of cookies.split(\";\")) {\n const [name, value] = cookie.split(\"=\");\n map.set(name.trim(), value ?? null);\n }\n return map;\n}\n```\n\n## Environment variables {% #environment-variables %}\n\n`import.meta.env.APPWRITE_KEY`, `import.meta.env.PUBLIC_APPWRITE_ENDPOINT` and `import.meta.env.PUBLIC_APPWRITE_PROJECT` are environment variables that are exported in your project's [.env file](https://kit.Astro.dev/docs/modules#$env-dynamic-public).\n\nFor example, your `.env` might look something similar to this.\n\n```env\nAPPWRITE_KEY=\nPUBLIC_APPWRITE_ENDPOINT=https://.cloud.appwrite.io/v1\nPUBLIC_APPWRITE_PROJECT=\n```\n\nThe `PUBLIC_APPWRITE_ENDPOINT` is the endpoint of your Appwrite project, and the `PUBLIC_APPWRITE_PROJECT` is the ID of the project you want to use.\nYou can get the values for these variables from the Appwrite console.\n\n{% only_dark %}\n![Create project screen](/images/docs/quick-starts/dark/create-project.avif)\n{% /only_dark %}\n\n{% only_light %}\n![Create project screen](/images/docs/quick-starts/create-project.avif)\n{% /only_light %}\n\nThe `APPWRITE_KEY` is an Appwrite API key with the necessary permissions to create new sessions.\n\nFor this tutorial you'll need an API key with the following scopes:\n\n| Category {% width=120 %} | Required scopes | Purpose |\n| ------------------------------------------------------ | ------------------------------------------------------ | ------------------------------------------------------ |\n| Sessions | `sessions.write` | Allows API key to create, update, and delete sessions. |\n\n{% only_dark %}\n![Server integrations](/images/docs/quick-starts/dark/integrate-server.avif)\n{% /only_dark %}\n\n{% only_light %}\n![Server integrations](/images/docs/quick-starts/integrate-server.avif)\n{% /only_light %}"}, {"path": "docs/tutorials/astro-ssr-auth/step-4", "title": "Add a server hook", "description": "Add authentication to a Astro project using Appwrite.", "content": "Astro middleware are functions that run on the server before a page is displayed to the user. Astro locals are a way to store data that is specific to the current request. We can use these features to store the user's account data, so that it is available to all pages.\n\nCreate a new file in the `src/` directory called `middleware.js`:\n\n```js\n// src/middleware.js\n\nimport { defineMiddleware } from \"astro:middleware\";\nimport { createSessionClient } from \"./server/appwrite\";\n\nexport const onRequest = defineMiddleware(async ({ request, locals }, next) => {\n try {\n const { account } = createSessionClient(request);\n locals.user = await account.get();\n } catch {}\n\n return next();\n});\n```\n\nTo ensure the `locals` object is typed correctly, we can add a type definition for it in a `env.d.ts` file at the root of the project:\n\n```ts\n/// \n\ntype Models = import(\"node-appwrite\").Models;\n\ndeclare namespace App {\n interface Locals {\n user?: Models.User;\n }\n}\n\ninterface ImportMetaEnv {\n readonly PUBLIC_APPWRITE_ENDPOINT: string;\n readonly PUBLIC_APPWRITE_PROJECT: string;\n readonly APPWRITE_KEY: string;\n}\n\ninterface ImportMeta {\n readonly env: ImportMetaEnv;\n}\n```\n\nNow, use the `locals` object in the home page to redirect based on the user's login status. \nOverwrite the file in the `src/pages` directory called `index.astro`:\n\n```js\n---\nconst { user } = Astro.locals;\nif (user) {\n return Astro.redirect(\"/account\");\n}\n\nreturn Astro.redirect(\"/signup\");\n---\n```\n\nIf the user is logged in, they will be redirected to the account page. If they are not logged in, they will be redirected to the sign up page."}, {"path": "docs/tutorials/astro-ssr-auth/step-5", "title": "Create sign up page", "description": "Add authentication to a Astro project using Appwrite.", "content": "We can now implement our sign up page. Create a `signin.astro` file in the `src/pages` directory:\n\n```js\n---\nconst { user } = Astro.locals;\nif (user) {\n return Astro.redirect(\"/account\");\n}\n---\n\n
\n \n \n \n \n
\n```\n\nThis is an HTML form with an email and password input.\nWhen the form is submitted, we want to send the email and password to Appwrite to authenticate the user.\nTo use Astro form actions, add an `if (Astro.request.method === \"POST\")` statement to the server-side javascript.\n\nIn the same file, implement the following.\n\n```js\n---\nimport { SESSION_COOKIE, createAdminClient } from \"../server/appwrite\";\nimport { ID } from \"node-appwrite\";\n\n// ... existing javascript\n\nif (Astro.request.method === \"POST\") {\n // Extract the form data\n const data = await Astro.request.formData();\n const email = data.get(\"email\");\n const password = data.get(\"password\");\n const name = data.get(\"name\");\n\n // Create the admin client\n const { account } = createAdminClient();\n\n // Create the email password session\n await account.create({\n userId: ID.unique(),\n email,\n password,\n name\n });\n const session = await account.createEmailPasswordSession({\n email,\n password\n });\n\n // Set the session cookie\n Astro.cookies.set(SESSION_COOKIE, session.secret, {\n path: \"/\",\n expires: new Date(session.expire),\n sameSite: \"strict\",\n secure: true,\n httpOnly: true,\n });\n\n // Redirect to the account page\n return Astro.redirect(\"/account\");\n}\n---\n\n```"}, {"path": "docs/tutorials/astro-ssr-auth/step-6", "title": "Create account page", "description": "Add authentication to a Astro project using Appwrite.", "content": "Now the end-user is able to sign up, we can create the account page. This page will display basic information about the user, and allow the user to log out. Create a new file in the `src/pages` directory called `account.astro` and add the following code:\n\n```js\n---\nimport { SESSION_COOKIE, createSessionClient } from \"../server/appwrite\";\n\n// Redirect the user if not signed in\nconst { user } = Astro.locals;\nif (!user) {\n return Astro.redirect(\"/signup\");\n}\n\n// Handle form action\nif (Astro.request.method === \"POST\") {\n // Create session client\n const { account } = createSessionClient(Astro.request);\n\n // Delete the Appwrite session\n await account.deleteSession({ sessionId: 'current' });\n\n // Delete the session cookie\n Astro.cookies.delete(SESSION_COOKIE);\n\n // Redirect the user to sign up page\n return Astro.redirect(\"/signup\");\n}\n---\n
    \n\t
  • \n\t\tEmail: {user.email}\n\t
  • \n\t
  • \n\t\tName: {user.name}\n\t
  • \n\t
  • \n\t\tID: {user.$id}\n\t
  • \n
\n\n
\n \n
\n```"}, {"path": "docs/tutorials/astro-ssr-auth/step-7", "title": "OAuth authentication with SSR", "description": "Add authentication to a Astro project using Appwrite.", "content": "To support the OAuth flow, we first redirect the user to the OAuth provider, and then handle the callback from the OAuth provider.\n\nTo redirect, add a button to our sign up page that redirects the user to the OAuth provider.\n\n```js\n\n\n\n\n
\n \n
\n```\n\nAdd a new `POST` route to handle the redirect.\n\n```js\n// src/pages/oauth.js\n\nimport { createAdminClient } from \"../server/appwrite\";\nimport { OAuthProvider } from \"node-appwrite\";\n\nexport const POST = async ({ redirect, url }) => {\n // Create the Appwrite client\n const { account } = createAdminClient();\n\n // Create an OAuth token\n const redirectUrl = await account.createOAuth2Token({\n provider: OAuthProvider.Github,\n success: `${url.origin}/oauth`,\n failure: `${url.origin}/signup`\n });\n\n // Redirect the end-user to the OAuth2 provider authentication\n return redirect(redirectUrl);\n};\n```\n\nThe `createOAuth2Token` method returns a URL to the OAuth provider. After authentication the OAuth provider redirects the user back to the `/oauth` route with the `userId` and `secret` URL query parameters.\n\nCreate a new `GET` route to handle the callback and create a session for the user.\n\n```js\n// src/pages/oauth.js\n\nimport { createAdminClient, SESSION_COOKIE } from \"../server/appwrite\";\nimport { OAuthProvider } from \"node-appwrite\";\n\n// ... existing POST handler\n\nexport const GET = async ({ redirect, cookies, url }) => {\n // Get the user ID and secret from the URL\n const userId = url.searchParams.get(\"userId\");\n const secret = url.searchParams.get(\"secret\");\n\n // Create the Appwrite client\n const { account } = createAdminClient();\n\n // Exchange the token for a session\n const session = await account.createSession({\n userId,\n secret\n });\n\n // Set the session cookie\n cookies.set(SESSION_COOKIE, session.secret, {\n sameSite: \"lax\",\n expires: new Date(session.expire),\n secure: true,\n httpOnly: true,\n path: \"/\",\n });\n\n // Redirect the logged in user to the account page\n return redirect(\"/account\");\n};\n```"}, {"path": "docs/tutorials/astro-ssr-auth/step-8", "title": "All set", "description": "Add authentication to a Astro project using Appwrite.", "content": "Start a preview of your app by running `npm run dev`.\n\nIf you want to see the complete source code with styling, see the [demos-for-astro](https://github.com/appwrite/demos-for-astro/tree/main/server-side-rendering) repository. \n\n## Other authentication methods {% #other-authentication-methods %}\nAppwrite also supports OAuth, passwordless login, anonymous login, and phone login. \nLearn more about them in the [authentication guide](https://appwrite.io/docs/products/auth)."}, {"path": "docs/tutorials/flutter/step-1", "title": "Coming soon", "description": "Learn to build an Flutter app with no backend code using an Appwrite backend.", "content": "Improve the docs, add this guide.\n\nWe still don't have this guide in place, but we do have some great news. \nThe Appwrite docs, just like Appwrite, is completely open sourced.\nThis means, anyone can help improve them and add new guides and tutorials. \n\nIf you see this page, **we're actively looking for contributions to this page**.\nFollow 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."}, {"path": "docs/tutorials/nextjs-ssr-auth/step-1", "title": "Server-side authentication with Next.js", "description": "Add SSR authentication to your Next.js app with Appwrite", "content": "Appwrite takes away the stress of building and maintaining a backend. Appwrite helps implement authentication, databases, file storage, and respond to real-time events with **secure** APIs out of the box. \nIf you're a Next.js developer, the examples in this guide show you how Appwrite can help you add authentication to Next.js apps faster.\n\n## Before you start {% #before-you-start %}\n\nBefore following this tutorial, have the following prepared:\n\n- A recent version of [Node.js](https://nodejs.org/en/download/) installed on your system.\n- A basic knowledge of Next.js and React.\n\n\nIf you're inspired and wish to follow along, make sure you've followed [Start with React](https://appwrite.io/docs/quick-starts/react) first.\nClone the [demos-for-react](https://github.com/appwrite/demos-for-react/tree/main/nextjs) examples and follow along with the source code."}, {"path": "docs/tutorials/nextjs-ssr-auth/step-2", "title": "Create project", "description": "Add authentication to a Next.js project using Appwrite.", "content": "Create a project using [Next.js](https://nextjs.org/docs/getting-started/installation#automatic-installation).\n\n```sh\nnpx create-next-app@latest\n```\n\nThe command will give you a prompt with several project types. We'll be starting with a skeleton project.\n\nThe prompt will be something similar to this.\n\n```sh\nWhat is your project named? my-app\nWould you like to use TypeScript? No\nWould you like to use ESLint? No\nWould you like to use Tailwind CSS? No\nWould you like to use `src/` directory? Yes\nWould you like to use App Router? (recommended) Yes\nWould you like to customize the default import alias (@/*)? No\nWhat import alias would you like configured? [Enter]\n```\n\nAfter the prompt is finished, you can head over to the newly created project.\n\n```sh\ncd my-app\nnpm install\n```\n\n## Install Appwrite {% #install-appwrite %}\n\nAppwrite provides a Node SDK that can be used in your Next.js apps. You can use Appwrite by installing the Node SDK as an NPM package. \nThe Node SDK is intended for server-side use. If you want to use Appwrite in a client-side application, you should use the [Web SDK](/docs/quick-starts/web) instead.\n\n```sh\nnpm install node-appwrite@20.0.0\n```"}, {"path": "docs/tutorials/nextjs-ssr-auth/step-3", "title": "Initialize SDK", "description": "Add authentication to a Next.js project using Appwrite.", "content": "Before you can use Appwrite, you need to create the Appwrite `Client` and set the project ID and endpoint. \nThe client is then used to create services like `Databases` and `Account`, so they all point to the same Appwrite project.\n\nCreate a function to build services you need in a file like `src/lib/server/appwrite.js` and **exporting the instances**.\n\n```js\n// src/lib/server/appwrite.js\n\"use server\";\nimport { Client, Account } from \"node-appwrite\";\nimport { cookies } from \"next/headers\";\n\nexport async function createSessionClient() {\n const client = new Client()\n .setEndpoint(process.env.NEXT_PUBLIC_APPWRITE_ENDPOINT)\n .setProject(process.env.NEXT_PUBLIC_APPWRITE_PROJECT);\n\n const session = await cookies().get(\"my-custom-session\");\n if (!session || !session.value) {\n throw new Error(\"No session\");\n }\n\n client.setSession(session.value);\n\n return {\n get account() {\n return new Account(client);\n },\n };\n}\n\nexport async function createAdminClient() {\n const client = new Client()\n .setEndpoint(process.env.NEXT_PUBLIC_APPWRITE_ENDPOINT)\n .setProject(process.env.NEXT_PUBLIC_APPWRITE_PROJECT)\n .setKey(process.env.NEXT_APPWRITE_KEY);\n\n return {\n get account() {\n return new Account(client);\n },\n };\n}\n```\n\nAs part of the function, set the current user's session if they are logged in. This is done by accessing the session cookie from the request and calling the `setSession(session)` with the cookie value.\n\n{% info title=\"Appwrite client security\" %}\nNotice that `createAdminClient` and `createSessionClient` returns **a new instance** of the Appwrite Client.\nWhen using Appwrite in server-integrations, it's important to **never share a `Client` instance** between two requests.\nDoing so could create security vulnerabilities.\n{% /info %}\n\n## Environment variables {% #environment-variables %}\n\n`NEXT_APPWRITE_KEY`, `NEXT_PUBLIC_APPWRITE_ENDPOINT` and `NEXT_PUBLIC_APPWRITE_PROJECT` are environment variables that are exported in your project's [.env file](https://kit.svelte.dev/docs/modules#$env-dynamic-public).\n\nFor example, your `.env` might look something similar to this.\n\n```env\nNEXT_APPWRITE_KEY=\nNEXT_PUBLIC_APPWRITE_ENDPOINT=https://.cloud.appwrite.io/v1\nNEXT_PUBLIC_APPWRITE_PROJECT=\n```\n\nThe `NEXT_PUBLIC_APPWRITE_ENDPOINT` is the endpoint of your appwrite instance , and the `NEXT_PUBLIC_APPWRITE_PROJECT` is the ID of the project you want to use. \nYou can get the values for these variables from the Appwrite console. \n{% only_dark %}\n![Create project screen](/images/docs/quick-starts/dark/create-project.avif)\n{% /only_dark %}\n{% only_light %}\n![Create project screen](/images/docs/quick-starts/create-project.avif)\n{% /only_light %}\n\nThe `NEXT_APPWRITE_KEY` is an Appwrite API key with the necessary permissions to create new sessions.\n\nFor this tutorial you'll need an API key with the following scopes:\n\n| Category {% width=120 %} | Required scopes | Purpose |\n|-----------|---------------------|---------|\n| Sessions | `sessions.write` | Allows API key to create, update, and delete sessions. |\n\n{% only_dark %}\n![Server integrations](/images/docs/quick-starts/dark/integrate-server.avif)\n{% /only_dark %}\n{% only_light %}\n![Server integrations](/images/docs/quick-starts/integrate-server.avif)\n{% /only_light %}"}, {"path": "docs/tutorials/nextjs-ssr-auth/step-4", "title": "Get the logged in user", "description": "Add authentication to a Next.js project using Appwrite.", "content": "Build a utility function to get the logged in user from Appwrite. This function will be used in our components and routes to check if a user is logged in, and access the user's details.\n\nEdit the `src/lib/server/appwrite.js` file to create a new function called `getLoggedInUser`.\n```js\n// ... your initilization functions\n\nexport async function getLoggedInUser() {\n try {\n const { account } = await createSessionClient();\n return await account.get();\n } catch (error) {\n return null;\n }\n}\n```\n\nNow, use the `getLoggedInUser` function in the home page to redirect based on the user's login status. Create a new file in the `app` directory called `page.jsx`.\n\n```js\n// src/app/page.jsx\n\nimport { getLoggedInUser } from \"@/lib/server/appwrite\";\nimport { redirect } from \"next/navigation\";\n\nexport default async function Home() {\n const user = await getLoggedInUser();\n\n if (!user) redirect(\"/signup\");\n\n redirect(\"/account\");\n}\n```\n\nThe user will be redirected to the sign up page if they are not logged in, or to the account page if they are logged in."}, {"path": "docs/tutorials/nextjs-ssr-auth/step-5", "title": "Create sign up page", "description": "Add authentication to a Next.js project using Appwrite.", "content": "We can now implement our sign up page. Create a `page.jsx` file in the `src/app/signup` directory:\n\n```jsx\n// src/app/signup/page.jsx\n\nimport {\n getLoggedInUser\n} from \"@/lib/server/appwrite\";\n\nexport default async function SignUpPage() {\n const user = await getLoggedInUser();\n if (user) redirect(\"/account\");\n\n return (\n <>\n
\n \n \n \n \n \n \n );\n}\n\n```\n\nThis is an HTML form with an email and password input. When the form is submitted, we want to send the email and password to Appwrite to authenticate the user. To use Next.js form actions we create the `signUpWithEmail` function in the same file:\n\n```jsx\n// src/app/signup/page.jsx\n\n// previous imports ...\n\nimport { ID } from \"node-appwrite\";\nimport { createAdminClient } from \"@/lib/server/appwrite\";\nimport { cookies } from \"next/headers\";\nimport { redirect } from \"next/navigation\";\n\nasync function signUpWithEmail(formData) {\n \"use server\";\n\n const email = formData.get(\"email\");\n const password = formData.get(\"password\");\n const name = formData.get(\"name\");\n\n const { account } = await createAdminClient();\n\n await account.create({\n userId: ID.unique(),\n email,\n password,\n name\n });\n const session = await account.createEmailPasswordSession({\n email,\n password\n });\n\n cookies().set(\"my-custom-session\", session.secret, {\n path: \"/\",\n httpOnly: true,\n sameSite: \"strict\",\n secure: true,\n });\n\n redirect(\"/account\");\n}\n\n// the SignUpPage component ...\n```\n\nThe `signUpWithEmail` function is an async function that takes the form data as an argument. It uses the `createAdminClient` function to create an admin Appwrite client and then calls the `createEmailPasswordSession` method on the `account` object. This method takes the email and password as arguments and returns a session object. We then set the session secret in a cookie and redirect the user to the account page."}, {"path": "docs/tutorials/nextjs-ssr-auth/step-6", "title": "Create account page", "description": "Add authentication to a Next.js project using Appwrite.", "content": "Now the end-user is able to sign up, we can create the account page. This page will display basic information about the user, and allow the user to log out. Create a new file in the `src/app/account` directory called `page.jsx` and add the following code:\n\n```jsx\n// src/app/account/page.jsx\n\nimport {\n createSessionClient,\n getLoggedInUser,\n} from \"@/lib/server/appwrite\";\nimport { redirect } from \"next/navigation\";\nimport { cookies } from \"next/headers\";\n\nasync function signOut() {\n \"use server\";\n\n const { account } = await createSessionClient();\n\n cookies().delete(\"my-custom-session\");\n await account.deleteSession({ sessionId: \"current\" });\n\n redirect(\"/signup\");\n}\n\nexport default async function HomePage() {\n const user = await getLoggedInUser();\n if (!user) redirect(\"/signup\");\n\n return (\n <>\n
    \n
  • \n Email: {user.email}\n
  • \n
  • \n Name: {user.name}\n
  • \n
  • \n ID: {user.$id}\n
  • \n
\n\n
\n \n
\n \n );\n}\n```\n\nThis code is similar to the `signup` page, but it uses the `getLoggedInUser` function to get the user's information. If the user is not logged in, the page will redirect to the sign-in page. Again, we use Next.js form actions to execute Appwrite code on the server. This time, the `signOut` function deletes the session cookie and redirect the user to the sign-in page."}, {"path": "docs/tutorials/nextjs-ssr-auth/step-7", "title": "OAuth authentication with SSR", "description": "Add authentication to a Next.js project using Appwrite.", "content": "## Enable OAuth provider {% #enable-oauth-provider %}\n\nTo enable the GitHub OAuth provider, navigate to your Appwrite Console > Auth > Settings > OAuth2 Providers > GitHub\n\nTo support the OAuth flow, we first redirect the user to the OAuth provider, and then handle the callback from the OAuth provider.\n\n## OAuth server action {% #oauth-server-action %}\n\nAdd a new server action. Navigate to `src/lib/server` and create a new file `oauth.js`:\n\n```js\n// src/lib/server/oauth.js\n\"use server\";\n\nimport { createAdminClient } from \"@/lib/server/appwrite\";\nimport { redirect } from \"next/navigation\";\nimport { headers } from \"next/headers\";\nimport { OAuthProvider } from \"node-appwrite\";\n\nexport async function signUpWithGithub() {\n\tconst { account } = await createAdminClient();\n\n const origin = headers().get(\"origin\");\n\n\tconst redirectUrl = await account.createOAuth2Token({\n\t\tprovider: OAuthProvider.Github,\n\t\tsuccess: `${origin}/oauth`,\n\t\tfailure: `${origin}/signup`,\n\t});\n\n\treturn redirect(redirectUrl);\n};\n```\n\nThe `createOAuth2Token` method redirects the user to the OAuth provider, and then the OAuth provider redirects the user back to the `/OAuth` route with the `userId` and `secret` URL query parameters.\n\n## OAuth form {% #oauth-form %}\n\nTo redirect, add a button to our sign up page that redirects the user to the OAuth provider.\n\n```jsx\n// src/app/signup/page.jsx\n\n// ... existing imports\nimport { signUpWithGithub } from \"@/lib/server/oauth\";\n\nexport default async function SignUpPage() {\n const user = await getLoggedInUser();\n if (user) redirect(\"/account\");\n\n return (\n <>\n {/* ... existing form */}\n
\n \n
\n \n );\n}\n\n```\n\n## OAuth callback {% #oauth-callback %}\n\nHandle the callback and create a session for the user. Create a new Next.js server route at `src/app/oauth/route.js`:\n\n```js\n// src/app/oauth/route.js\n\nimport { createAdminClient } from \"@/lib/server/appwrite\";\nimport { cookies } from \"next/headers\";\nimport { NextResponse } from \"next/server\";\n\nexport async function GET(request) {\n const userId = request.nextUrl.searchParams.get(\"userId\");\n const secret = request.nextUrl.searchParams.get(\"secret\");\n\n const { account } = await createAdminClient();\n const session = await account.createSession({\n userId,\n secret\n });\n\n cookies().set(\"my-custom-session\", session.secret, {\n path: \"/\",\n httpOnly: true,\n sameSite: \"strict\",\n secure: true,\n });\n\n return NextResponse.redirect(`${request.nextUrl.origin}/account`);\n}\n```"}, {"path": "docs/tutorials/nextjs-ssr-auth/step-8", "title": "All set", "description": "Add authentication to a Next.js project using Appwrite.", "content": "If you want to see the complete source code with styling, see the [demos-for-react](https://github.com/appwrite/demos-for-react/tree/master/nextjs/server-side-rendering) repository.\n\n## Other authentication methods {% #other-authentication-methods %}\nAppwrite also supports OAuth, passwordless login, anonymous login, and phone login. \nLearn more about them in the [authentication guide](https://appwrite.io/docs/products/auth)."}, {"path": "docs/tutorials/nextjs/step-1", "title": "Build an idea tracker with Next.js", "description": "Learn to build an idea tracker app with Appwrite and Next.js with authentication, databases and tables, queries, pagination, and file storage.", "content": "**Idea Tracker**: an app to track all the side project ideas that you'll start, but probably never finish.\nIn this tutorial, you will build an Idea Tracker with Appwrite and Next.js.\n\n{% only_dark %}\n![Create project screen](/images/docs/tutorials/dark/idea-tracker.avif)\n{% /only_dark %}\n{% only_light %}\n![Create project screen](/images/docs/tutorials/idea-tracker.avif)\n{% /only_light %}\n\n## Concepts {% #concepts %}\n\nThis tutorial will introduce the following concepts:\n\n1. Setting up your first project\n2. Authentication\n3. Navigation\n4. Databases and tables\n5. Queries\n\n\n## Prerequisites {% #prerequisites %}\n\n1. Basic knowledge of JavaScript and React.\n2. Have [Node.js](https://nodejs.org/en) and [NPM](https://www.npmjs.com/) installed on your computer."}, {"path": "docs/tutorials/nextjs/step-2", "title": "Create app", "description": "Create a Next.js app project and integrate with Appwrite", "content": "## Create Next.js project {% #create-nextjs-project %}\n\nCreate a Next.js app with the `npx create-next-app` command.\nThe command will install all the necessary dependencies for you.\n\n```sh\nnpx create-next-app@latest ideas-tracker --typescript --eslint --app --src-dir --import-alias \"@/*\"\n```\n\n## Add dependencies {% #add-dependencies %}\n\nOnce the project is created, change your current working directory and install the JavaScript Appwrite SDK.\n\n```sh\ncd ideas-tracker\nnpm install appwrite\nnpm install \"@appwrite.io/pink\"\n```\n\nOpen `src/app/globals.css` and replace the content with the following to import the relevant style files.\n\n```css\n/* src/app/globals.css */\n@import \"@appwrite.io/pink\";\n@import \"@appwrite.io/pink-icons\";\n\n* {\n box-sizing: border-box;\n}\n\nhtml,\nbody {\n overflow-x: hidden;\n}\n\nbody {\n background: hsl(var(--color-neutral-50));\n}\n\n.dark body {\n background: hsl(var(--color-neutral-900));\n}\n```\n\nYou can start your development server to see your app in the browser.\n\n```sh\nnpm run dev\n```\n\nThis will start a server at `http://localhost:3000/`."}, {"path": "docs/tutorials/nextjs/step-3", "title": "Set up Appwrite", "description": "Import and configure a project with Appwrite Cloud.", "content": "## Create project {% #create-project %}\n\nHead to the [Appwrite Console](https://cloud.appwrite.io/console).\n\n{% only_dark %}\n![Create project screen](/images/docs/quick-starts/dark/create-project.avif)\n{% /only_dark %}\n{% only_light %}\n![Create project screen](/images/docs/quick-starts/create-project.avif)\n{% /only_light %}\n\nIf this is your first time using Appwrite, create an account and create your first project.\n\nThen, under **Add a platform**, add a **Web app**.\nThe **Hostname** should be `localhost`.\n\n{% only_dark %}\n![Add a platform](/images/docs/quick-starts/dark/add-platform.avif)\n{% /only_dark %}\n{% only_light %}\n![Add a platform](/images/docs/quick-starts/add-platform.avif)\n{% /only_light %}\n\nYou can skip the optional steps.\n\n## Environment variables {% #environment-variables %}\n\nTo connect to Appwrite in our app, we'll need to configure our project endpoint and project ID.\nWe keep the secrets by using environment variables for the endpoint and project ID.\nYour project ID is located in the **Settings** page in the Appwrite console.\n\n{% only_dark %}\n![Project settings screen](/images/docs/quick-starts/dark/project-id.avif)\n{% /only_dark %}\n{% only_light %}\n![Project settings screen](/images/docs/quick-starts/project-id.avif)\n{% /only_light %}\n\nAdd a `.env.local` file to the root directory and add the following code to it, replacing `PROJECT_ID` with your project id.\n\n```\nNEXT_PUBLIC_APPWRITE_ENDPOINT=https://.cloud.appwrite.io/v1\nNEXT_PUBLIC_APPWRITE_PROJECT=PROJECT_ID\n```\n\n## Initialize Appwrite SDK {% #init-sdk %}\n\nCreate a new file `lib/appwrite.ts` for the Appwrite related code.\nOnly one instance of the `Client()` class should be created per app.\nAdd the following code to it.\n\n```ts\n// lib/appwrite.ts\n\nimport { Client, Account, TablesDB } from \"appwrite\";\n\nconst client = new Client();\n\nclient\n .setEndpoint(process.env.NEXT_PUBLIC_APPWRITE_ENDPOINT!)\n .setProject(process.env.NEXT_PUBLIC_APPWRITE_PROJECT!);\n\nexport const account = new Account(client);\nexport const tablesDB = new TablesDB(client);\nexport { ID } from \"appwrite\";\n```"}, {"path": "docs/tutorials/nextjs/step-4", "title": "Add authentication", "description": "Add authentication to your Next.js application using Appwrite Web SDK", "content": "For our ideas tracker app, we want any visitor to be able to read the ideas that are stored.\nOn the other hand, we don't want the page spammed with just about anything from anyone just stopping by.\nTo prevent that, or at least making it a bit more difficult, editing ideas will be available for logged in users only.\nWith authentication, we can differentiate between users and decide which users have access to which content.\n\nWe will build a page with a simple login form and store its related logic in a custom hook so it can be reused.\n\n## User session hook {% #user-session-hook %}\n\nThere are a few standard functions involved in handling a user session that are added to the hook.\nThe user needs to be able to register to an account, login to the account and logout from it.\n\nWe are using Appwrite as a backend to handle the user details, so we need to connect to Appwrite by importing the configurations from [step 3](/docs/tutorials/nextjs/step-3).\nThe response from these interactions will be stored in state to get more information about the user in our app.\n\nCreate a new file `hooks/useAuth.ts` and add the following code.\n\n```ts\n// hooks/useAuth.ts\n\nimport { useState, useEffect } from 'react';\nimport { account } from '../lib/appwrite';\nimport { ID } from 'appwrite';\nimport type { Models } from 'appwrite';\nimport { useRouter } from 'next/navigation';\n\nexport function useAuth() {\n const [current, setCurrent] = useState(null);\n const [loading, setLoading] = useState(true);\n const router = useRouter();\n\n const register = async (email: string, password: string): Promise => {\n await account.create({\n userId: ID.unique(),\n email,\n password\n });\n await login(email, password);\n };\n\n const login = async (email: string, password: string): Promise => {\n const session = await account.createEmailPasswordSession({\n email,\n password\n });\n setCurrent(session);\n router.push('/');\n };\n\n const logout = async (): Promise => {\n await account.deleteSession('current');\n setCurrent(null);\n router.push('/');\n };\n\n const getCurrentUser = async () => {\n try {\n const user = await account.get();\n setCurrent(user);\n } catch (error) {\n setCurrent(null);\n } finally {\n setLoading(false);\n }\n };\n\n useEffect(() => {\n getCurrentUser();\n }, []);\n\n return {\n current,\n loading,\n login,\n logout,\n register,\n };\n}\n```\n\n## Login page {% #login-page %}\n\nCreate a new file `src/app/login/page.tsx`.\nThis will create a new page accessible at `/login`.\n\nWe will define functions to handle form submissions and show either a signup or a login form.\n\n```tsx\n// src/app/login/page.tsx\n'use client';\n\nimport { useState } from 'react';\nimport { useAuth } from '../../hooks/useAuth';\nimport AuthForm from '../../components/AuthForm';\n\nexport default function LoginPage() {\n const { login, register } = useAuth();\n const [isSignUp, setIsSignUp] = useState(false);\n\n const handleLogin = async (event: React.FormEvent) => {\n event.preventDefault();\n const form = event.target as HTMLFormElement;\n const formData = new FormData(form);\n \n await login(\n formData.get('email') as string,\n formData.get('password') as string\n );\n \n form.reset();\n };\n\n const handleRegistration = async (event: React.FormEvent) => {\n event.preventDefault();\n const form = event.target as HTMLFormElement;\n const formData = new FormData(form);\n \n await register(\n formData.get('email') as string,\n formData.get('password') as string\n );\n \n form.reset();\n };\n\n return (\n
\n
\n

Login/Register

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

Submit Idea

\n
\n
    \n
  • \n \n \n
  • \n
  • \n \n \n
  • \n
\n
    \n
  • \n \n Submit\n \n
  • \n
\n
\n
\n
\n );\n}\n```\n\n## Ideas list {% #ideas-list %}\n\nNow we need to display the ideas from the database. Create a new file `src/components/IdeasList.tsx`.\n\n```tsx\n// src/components/IdeasList.tsx\n'use client';\n\nimport { useIdeas } from '../hooks/useIdeas';\nimport { useAuth } from '../hooks/useAuth';\n\nexport default function IdeasList() {\n const { current: ideas, loading, remove } = useIdeas();\n const { current: user } = useAuth();\n\n if (loading) {\n return
Loading ideas...
;\n }\n\n if (ideas.length === 0) {\n return (\n
\n

No ideas yet. Be the first to share one!

\n
\n );\n }\n\n return (\n
\n

Latest Ideas

\n
    \n {ideas.map((idea) => (\n
  • \n
    \n
    \n
    {idea.title}
    \n {idea.description && (\n

    \n {idea.description}\n

    \n )}\n
    \n {user && user.userId === idea.userId && (\n remove(idea.$id)}\n className=\"button is-text u-padding-inline-8\"\n aria-label=\"Delete idea\"\n >\n 🗑️\n \n )}\n
    \n
  • \n ))}\n
\n
\n );\n}\n```\n\n## Update home page {% #update-home-page %}\n\nNow let's update the home page to use our new components. Update `src/app/page.tsx`:\n\n```tsx\n// src/app/page.tsx\nimport IdeasForm from '../components/IdeasForm';\nimport IdeasList from '../components/IdeasList';\n\nexport default function Home() {\n return (\n
\n
\n

Ideas Tracker

\n

\n Track all your side project ideas in one place.\n

\n
\n \n \n
\n );\n}\n```\n\nYou can now test the full functionality of your ideas tracker app!"}, {"path": "docs/tutorials/nextjs/step-8", "title": "Next steps", "description": "View your Next.js app built on Appwrite Cloud.", "content": "## Test your project {% #test-project %}\nRun your project with `npm run dev` and open the URL shown by the NPM command in your browser.\n\nHead to the [Appwrite Console](https://cloud.appwrite.io/console) to see the new users and follow their interactions."}, {"path": "docs/tutorials/nuxt-ssr-auth/step-1", "title": "Server-side authentication with Nuxt", "description": "Add SSR authentication to your Nuxt app with Appwrite", "content": "Appwrite takes away the stress of building and maintaining a backend. Appwrite helps implement authentication, databases, file storage, and respond to real-time events with **secure** APIs out of the box. \nThis tutorials shows how Appwrite can help you add authentication to your Nuxt app using server-side rendering (SSR).\n\n## Before you start {% #before-you-start %}\n\nBefore following this tutorial, have the following prepared:\n\n- A recent version of [Node.js](https://nodejs.org/en/download/) installed on your system.\n- A basic knowledge of Vue and Nuxt.\n\nIf you're inspired and wish to follow along, make sure you've followed [Start with Nuxt](https://appwrite.io/docs/quick-starts/nuxt) first.\nClone the [nuxt-ssr-auth](https://github.com/appwrite-community/nuxt-ssr-auth) repository and follow along with the source code."}, {"path": "docs/tutorials/nuxt-ssr-auth/step-2", "title": "Create project", "description": "Add authentication to a Nuxt project using Appwrite.", "content": "Create a Vue project using [Nuxt](https://nuxt.com/docs/getting-started/installation#new-project). \n\n```sh\nnpx nuxi@latest init my-nuxt-project\n```\n\nThe command will give you a prompt with several options,\nthe prompt will be something similar to this.\n\n```sh\n❯ Which package manager would you like to use?\n● npm\n❯ Initialize git repository?\n● Yes\n```\n\nAfter the prompt is finished, you can head over to the newly created project.\n\n```sh\ncd my-nuxt-project\n```\n\n## Install Appwrite {% #install-appwrite %}\n\nAppwrite provides a Node SDK that can be used in your Nuxt apps. You can use Appwrite by installing the Node SDK as an NPM package. \nThe Node SDK is intended for server-side use. If you want to use Appwrite in a client-side application, you should [use the Web SDK instead](/docs/tutorials/nuxt)\n\n```sh\nnpm install node-appwrite\n```"}, {"path": "docs/tutorials/nuxt-ssr-auth/step-3", "title": "Initialize SDK", "description": "Add authentication to a Nuxt project using Appwrite.", "content": "Before you can use Appwrite, you need to create the Appwrite `Client` and set the project ID and endpoint.\nThe client is then used to create services like `Databases` and `Account`, so they all point to the same Appwrite project.\n\nCreate a function to the build services you need in a file like `server/lib/appwrite.js` and **exporting the instances**.\n\nAs part of the function, set the current user's session if they are logged in. This is done by accessing the session cookie from the request and calling the `setSession(session)` with the cookie value.\n\n{% info title=\"Appwrite client security\" %}\nWe recommend creating a new `Client` instance for each request.\nWhen using Appwrite in server-integrations, it's important to **never share a `Client` instance** between two requests.\nDoing so could create security vulnerabilities.\n{% /info %}\n\n```js\n// server/lib/appwrite.js\nimport { Client, Account } from \"node-appwrite\";\n\nexport const SESSION_COOKIE = \"my-custom-session\";\n\nexport function createAdminClient() {\n const client = new Client()\n .setEndpoint(process.env.PUBLIC_APPWRITE_ENDPOINT)\n .setProject(process.env.PUBLIC_APPWRITE_PROJECT)\n .setKey(process.env.APPWRITE_KEY);\n\n return {\n get account() {\n return new Account(client);\n },\n };\n}\n\nexport function createSessionClient(event) {\n const config = useRuntimeConfig(event);\n\n const client = new Client()\n .setEndpoint(config.public.appwriteEndpoint)\n .setProject(config.public.appwriteProjectId);\n\n const session = getCookie(event, SESSION_COOKIE);\n if (session) {\n client.setSession(session);\n }\n\n return {\n get account() {\n return new Account(client);\n },\n };\n}\n```\n\n`config.appwriteApiKey`, `config.public.appwriteEndpoint` and `config.public.appwriteProjectId` are Nuxt runtime configuration variables. Create a [nuxt.config.js](https://nuxt.com/docs/api/configuration/nuxt-config) file in the root of your project and add the following code to set the runtime configuration.\n\n```js\nexport default defineNuxtConfig({\n runtimeConfig: {\n appwriteKey: process.env.APPWRITE_KEY,\n public: {\n appwriteEndpoint: process.env.PUBLIC_APPWRITE_ENDPOINT,\n appwriteProjectId: process.env.PUBLIC_APPWRITE_PROJECT,\n },\n },\n});\n```\n\nNow you can use `.env` files to set the environment variables for your project. Retrieve the values for these variables from the Appwrite console.\n\nThe `PUBLIC_APPWRITE_ENDPOINT` is the endpoint of your Appwrite project, and the `PUBLIC_APPWRITE_PROJECT` is the ID of the project you want to use.\nYou can get the values for these variables from the Appwrite console.\n\n{% only_dark %}\n![Create project screen](/images/docs/quick-starts/dark/create-project.avif)\n{% /only_dark %}\n\n{% only_light %}\n![Create project screen](/images/docs/quick-starts/create-project.avif)\n{% /only_light %}\n\nThe `APPWRITE_KEY` is an Appwrite API key with the necessary permissions to create new sessions.\n\nFor this tutorial you'll need an API key with the following scopes:\n\n| Category {% width=120 %} | Required scopes | Purpose |\n| ------------------------------------------------------ | ------------------------------------------------------ | ------------------------------------------------------ |\n| Sessions | `sessions.write` | Allows API key to create, update, and delete sessions. |\n\n{% only_dark %}\n![Server integrations](/images/docs/quick-starts/dark/integrate-server.avif)\n{% /only_dark %}\n\n{% only_light %}\n![Server integrations](/images/docs/quick-starts/integrate-server.avif)\n{% /only_light %}\n\nFor example, your `.env` might look something similar to this.\n\n```env\nAPPWRITE_KEY=\nPUBLIC_APPWRITE_ENDPOINT=https://.cloud.appwrite.io/v1\nPUBLIC_APPWRITE_PROJECT=\n```"}, {"path": "docs/tutorials/nuxt-ssr-auth/step-4", "title": "Add server middleware", "description": "Add authentication to a Nuxt project using Appwrite.", "content": "Nuxt server middle are functions that run on the server before a route is displayed to the user. Nuxt context allows you to store data for the lifecycle of the current request. We can use this to store the user's account data, so that it is available to all pages.\n\nCreate a new file in the `server/middleware` directory called `auth.js`.\n```js\n// server/middleware/auth.js\nimport { createSessionClient } from \"../lib/appwrite\";\n\nexport default defineEventHandler(async (event) => {\n const { account } = createSessionClient(event);\n\n try {\n event.context.user = await account.get();\n } catch (error) {}\n});\n```\n\nTo ensure the `context` object is typed correctly, we can add a type definition for it in the `env.d.ts` file:\n\n```ts\nimport type { Models } from \"node-appwrite\";\n\ndeclare module \"h3\" {\n interface H3EventContext {\n user?: Models.User>;\n }\n}\n```\n\nNow, use the `context` object in the home page to redirect based on the user's login status. Create a new file in the `server/routes` directory called `index.js`:\n\n```js\nexport default defineEventHandler(async (event) => {\n if (event.context.user) {\n await sendRedirect(event, \"/account\");\n }\n\n await sendRedirect(event, \"/signup\");\n});\n```\n\nWhen a user visits the home page, they will be redirected to the sign up page if they are not logged in, or to the account page if they are logged in."}, {"path": "docs/tutorials/nuxt-ssr-auth/step-5", "title": "Create sign up page", "description": "Add authentication to a Nuxt project using Appwrite.", "content": "We can now implement our sign up page. Create a `signup.vue` file in the `pages` directory.\n\n```vue\n\n\n```\n\nThis is an HTML form with an email and password input. When the form is submitted, we want to send the email and password to Appwrite to authenticate the user. To use Nuxt form actions we create a `signup.post.js` file in the `server/api` directory:\n\n```javascript\n// server/api/signup.post.js\nimport { ID } from \"node-appwrite\";\nimport { SESSION_COOKIE, createAdminClient } from \"~/server/lib/appwrite\";\n\nexport default defineEventHandler(async (event) => {\n // Extract the form data\n const formData = await readFormData(event);\n const email = formData.get(\"email\");\n const password = formData.get(\"password\");\n\n // Create the Appwrite client.\n const { account } = createAdminClient(event);\n\n // Create a new user with email and password\n await account.create({\n userId: ID.unique(),\n email,\n password\n });\n\n // Create the session using the client\n const session = await account.createEmailPasswordSession({\n email,\n password\n });\n\n // Set the session cookie with the secret\n setCookie(event, SESSION_COOKIE, session.secret, {\n expires: new Date(session.expire),\n path: \"/\",\n httpOnly: true,\n secure: true,\n sameSite: \"strict\",\n });\n\n // Redirect to the account page.\n await sendRedirect(event, \"/account\");\n});\n```"}, {"path": "docs/tutorials/nuxt-ssr-auth/step-6", "title": "Create account page", "description": "Add authentication to a Nuxt project using Appwrite.", "content": "Now the end-user is able to sign up, we can create the account page. This page will display basic information about the user, and allow the user to log out.\n\nBefore creating the account page, the route should fetch the user data. Create a new `user.get.js` file in the `server/routes/api` directory and add the following code:\n\n```js\n// server/routes/api/user.get.js\nexport default defineEventHandler(async (event) => {\n const user = event.context.user;\n\n if (!user) {\n return false;\n }\n\n return user;\n})\n```\n\nCreate a new file in the `pages` directory called `account.vue` and add the following code:\n\n```vue\n\n\n\n\n```\n\nThis page will display the user's email, name, and ID. It also contains a form that will log the user out when submitted. The form will send a `POST` request to the `/api/signout` endpoint. We need to create this endpoint in the server. Create a new file in the `server/routes/api` directory called `signout.post.js` and add the following code:\n\n```javascript\n// server/routes/api/signout.post.js\nimport { SESSION_COOKIE, createSessionClient } from \"~/server/lib/appwrite\";\n\nexport default defineEventHandler(async (event) => {\n const { account } = createSessionClient(event);\n\n await account.deleteSession({ sessionId: \"current\" });\n deleteCookie(event, SESSION_COOKIE);\n\n await sendRedirect(event, \"/signup\");\n});\n```"}, {"path": "docs/tutorials/nuxt-ssr-auth/step-7", "title": "OAuth authentication with SSR", "description": "Add authentication to a Nuxt project using Appwrite.", "content": "To support the OAuth flow, we first redirect the user to the OAuth provider, and then handle the callback from the OAuth provider.\n\nTo redirect, add a button to our sign up page that redirects the user to the OAuth provider.\n\n```vue\n\n\n\n\n
\n \n \n
\n```\n\nAdd a new server route to handle the redirect.\n\n```js\n// server/routes/api/oauth.post.js\nimport { createAdminClient } from \"~/server/lib/appwrite\";\nimport { OAuthProvider } from \"node-appwrite\";\n\nexport default defineEventHandler(async (event) => {\n const config = useRuntimeConfig(event);\n const { account } = createAdminClient();\n const endpoint = config.public.appwriteEndpoint;\n\n const redirectUrl = await account.createOAuth2Token({\n provider: OAuthProvider.Github,\n success: `${endpoint}/api/oauth`,\n failure: `${endpoint}/signup`\n });\n\n // Redirect the user to the OAuth provider authorization page\n await sendRedirect(event, redirectUrl);\n});\n```\n\nThe `createOAuth2Token` method redirects the user to the OAuth provider, and then the OAuth provider redirects the user back to the `/oauth` route with the `userId` and `secret` URL query parameters.\n\nHandle the callback and create a session for the user. Create a new server route at `server/routes/api/oauth.get.js`.\n\n```js\n// server/routes/api/oauth.get.js\nimport { SESSION_COOKIE, createAdminClient } from \"~/server/lib/appwrite\";\n\nexport default defineEventHandler(async (event) => {\n // Extract the userId and secret from the URL query parameters\n const { userId, secret } = getQuery(event);\n if (!userId || !secret) {\n return sendRedirect(event, \"/signup\");\n }\n\n // Create the Appwrite client\n const { account } = createAdminClient();\n\n // Exchange the token userId and secret for a session\n const session = await account.createSession({\n userId,\n secret\n });\n\n // Set the session cookie\n setCookie(event, SESSION_COOKIE, session.secret, {\n expires: new Date(session.expire),\n path: \"/\",\n httpOnly: true,\n secure: true,\n sameSite: \"strict\",\n });\n\n // Redirect the user to the account page\n await sendRedirect(event, \"/account\");\n});\n```"}, {"path": "docs/tutorials/nuxt-ssr-auth/step-8", "title": "Enable the sign up and account pages", "description": "Add authentication to a Nuxt project using Appwrite.", "content": "For the last step, we must remove the welcome page and enable the pages we have created so far. For that, head to the `app.vue` file, and replace `` with `` so that code looks as follows:\n\n```vue\n\n\n```\n\nReplace `` with `` to allow the user to navigate to the pages created so far, such as the sign-up and account pages, instead of the default Nuxt welcome page."}, {"path": "docs/tutorials/nuxt-ssr-auth/step-9", "title": "All set", "description": "Add authentication to a Nuxt project using Appwrite.", "content": "If you want to see the complete source code with styling, see the [nuxt-ssr-auth](https://github.com/appwrite-community/nuxt-ssr-auth) repository. \n\n## Other authentication methods {% #other-authentication-methods %}\nAppwrite also supports OAuth, passwordless login, anonymous login, and phone login. \nLearn more about them in the [authentication guide](https://appwrite.io/docs/products/auth)."}, {"path": "docs/tutorials/nuxt/step-1", "title": "Build an ideas tracker with Nuxt", "description": "Learn to build an idea tracker app with Appwrite and Nuxt with authentication, databases and tables, queries, pagination, and file storage.", "content": "**Idea tracker**: an app to track all the side project ideas that you'll start, but probably never finish.\nIn this tutorial, you will build Idea tracker with Appwrite and Nuxt.\n\n{% only_dark %}\n![Create project screen](/images/docs/tutorials/dark/idea-tracker.avif)\n{% /only_dark %}\n{% only_light %}\n![Create project screen](/images/docs/tutorials/idea-tracker.avif)\n{% /only_light %}\n\n## Concepts {% #concepts %}\n\nThis tutorial will introduce the following concepts:\n\n1. Setting up your first project\n2. Authentication\n3. Navigation\n4. Databases and tables\n5. Queries\n\n\n## Prerequisites {% #prerequisites %}\n\n1. Basic knowledge of JavaScript.\n2. Have [Node.js](https://nodejs.org/en) and [NPM](https://www.npmjs.com/) installed on your computer."}, {"path": "docs/tutorials/nuxt/step-2", "title": "Create app", "description": "Create a Nuxt app project and integrate with Appwrite", "content": "## Create Nuxt project {% #create-nuxt-project %}\n\nCreate a Nuxt app with the `npx init` command.\nThe command will install all the necessary dependencies for you.\n\n```sh\nnpx nuxi@latest init ideas-tracker\n```\n\n## Add dependencies {% #add-dependencies %}\n\nOnce the project is created, change your current working directory and install the JavaScript Appwrite SDK.\n\n```sh\ncd ideas-tracker\nnpm install appwrite\nnpm install \"@appwrite.io/pink\"\n```\n\nOpen `App.vue` and import the relevant style files.\n\n```html\n\n\n\n```\n\nThen update `nuxt.config.ts` to disable SSR for now. SSR support is coming soon to Appwrite, for now, disable SSR.\n\n```ts\n// nuxt.config.ts\n\n// https://nuxt.com/docs/api/configuration/nuxt-config\nexport default defineNuxtConfig({\n ssr: false,\n devtools: { enabled: true }\n})\n```\n\nYou can start the development server to watch your app update in the browser as you make your changes.\n\n```sh\nnpm run dev\n```\n\n## File structure {% #file-structure %}\n\nNuxt relies on an opiniated directory structure to automate tasks and help organize the codebase.\nTo take advantage of this we need to add the following directories:\n- `/components/` to keep our UI components in one place.\n We will get back to it in [step 5](/docs/tutorials/nuxt/step-5)\n- `/composables/`for storing files handling global states and data fetching.\n We will use it in [step 4](/docs/tutorials/nuxt/step-4)\n- `/layouts/` to store the page layouts\n- `/pages/` for the content pages.\n\nRun the following command to create these folders\n\n```sh\nmkdir components composables layouts pages\n```\n\n## Add layout {% #add-layout %}\n\nGo to the `layouts/` directory and add the file `default.vue`.\nAdd the following code for the default layout.\nAs you see it's nearly empty but it is needed for the automatic routing to work properly.\n\n```html\n\n\n\n\n\n```\n\n## Add home page {% #add-home-page %}\n\nNext, head over to the `pages` directory.\nThis is where we will keep the content that will render on our pages in the web application.\nNuxt reads all the `.vue` files inside this directory and [automatically renders them as routes](https://v2.nuxt.com/docs/directory-structure/pages/).\nAdd the file `index.vue` with the following code.\n\n```vue\n\n\n\n```\n\nThis is what your directory should look like after adding the new directories and files:\n\n```\n[repository tree]\n├── .nuxt/\n├── components/\n├── composables/\n├── layouts/\n│ └── default.vue\n├── pages/\n│ ├── index.vue\n├── public/\n│ └── /favicon.ico\n├── server/\n│ └── /tsconfig.json\n├── .gitignore\n├── app.vue\n├── nuxt.config.ts\n├── package-lock.json\n├── package.json\n├── README.md\n└── tsconfig.json\n```\n\n## Render page {% #render-page %}\n\nIf you run the development server now, it will still render the Nuxt Welcome page.\nWe need to tell our app to use the files we just created instead.\nOpen `app.vue` in the root directory and replace the content with the following code.\nYour page will now be up and running.\n\n```vue\n\n\n\n\n\n```"}, {"path": "docs/tutorials/nuxt/step-3", "title": "Set up Appwrite", "description": "Import and configure a project with Appwrite Cloud.", "content": "## Create project {% #create-project %}\n\nHead to the [Appwrite Console](https://cloud.appwrite.io/console).\n\n{% only_dark %}\n![Create project screen](/images/docs/quick-starts/dark/create-project.avif)\n{% /only_dark %}\n{% only_light %}\n![Create project screen](/images/docs/quick-starts/create-project.avif)\n{% /only_light %}\n\nIf this is your first time using Appwrite, create an account and create your first project.\n\nThen, under **Add a platform**, add a **Web app**.\nThe **Hostname** should be `localhost`.\n\n{% only_dark %}\n![Add a platform](/images/docs/quick-starts/dark/add-platform.avif)\n{% /only_dark %}\n{% only_light %}\n![Add a platform](/images/docs/quick-starts/add-platform.avif)\n{% /only_light %}\n\nYou can skip the optional steps.\n\n## Environment variables {% #environment-variables %}\n\nTo connect to Appwrite in our app, we'll need to use sensitive information.\nWe keep the secrets by using environment variables for the endpoint and project id.\nYour project id is located in the **Settings** page in the Appwrite console.\n\n{% only_dark %}\n![Project settings screen](/images/docs/quick-starts/dark/project-id.avif)\n{% /only_dark %}\n{% only_light %}\n![Project settings screen](/images/docs/quick-starts/project-id.avif)\n{% /only_light %}\n\nAdd a `.env` file to the root directory and add the following code to it, replacing `PROJECT_ID` with your project id.\n\n```\nVITE_APPWRITE_ENDPOINT=https://.cloud.appwrite.io/v1\nVITE_APPWRITE_PROJECT=PROJECT_ID\n```\n\n## Initialize Appwrite SDK {% #init-sdk %}\n\nCreate a new file `appwrite.js` for the Appwrite related code.\nOnly one instance of the `Client()` class should be created per app.\nAdd the following code to it.\n\n```ts\n// appwrite.ts\n\nimport { Client, TablesDB, Account } from \"appwrite\";\n\nconst url: string = import.meta.env.VITE_APPWRITE_ENDPOINT;\nconst project: string = import.meta.env.VITE_APPWRITE_PROJECT;\n\nconst client: Client = new Client();\n\nclient.setEndpoint(url).setProject(project);\n\nexport const account: Account = new Account(client);\nexport const tablesDB: TablesDB = new TablesDB(client);\n```"}, {"path": "docs/tutorials/nuxt/step-4", "title": "Add authentication", "description": "Add authentication to your Nuxt application using Appwrite Web SDK", "content": "For our ideas tracker app, we want any visitor to be able to read the ideas that are stored.\nOn the other hand, we don't want the page spammed with just about anything from anyone just stopping by.\nTo prevent that, or at least making it a bit more difficult, editing ideas will be available for logged in users only.\nWith a login function, we can differentiate between users and decide which users have access to which content.\n\nWe will build a page with a simple login form and store its related logic in a `useUserSession` composable so it can be reused, starting with the composable.\n\n## User session composable {% #user-session-composable %}\n\nThere are a few standard functions involved in handling a user session that are added to the composable.\nThe user needs to be able to register to an account, login to the account and logout from it.\n\nWe are using Appwrite as a backend to handle the user details, so we need to connect to Appwrite by importing the configurations from step 3.\nThe response from these interactions will be stored as references to get more information about the user in our app.\n\nIn your `composable` directory, create the file `useUserSession.js` and add the following code.\n\n```ts\n// composable/useUserSession.ts\n\nimport { ID } from \"appwrite\";\nimport { ref } from \"vue\";\nimport { account } from \"../appwrite\";\nimport { type Models } from 'appwrite';\n\nconst current = ref(null); // Reference to current user object\n\nexport const useUserSession = () => {\n const register = async (email: string, password: string): Promise => {\n await account.create({\n userId: ID.unique(),\n email,\n password\n }); // Register new user in Appwrite\n await login(email, password); // Login registered user\n };\n\n const login = async (email: string, password: string): Promise => {\n const authUser = await account.createEmailPasswordSession({\n email,\n password\n }); // Open user session in Appwrite\n current.value = authUser; // Pass user data to current ref\n navigateTo(\"/\");\n };\n\n const logout = async (): Promise => {\n await account.deleteSession({\n sessionId: \"current\"\n }); // Delete Appwrite user session\n current.value = null; // Clear current ref\n navigateTo(\"/\");\n };\n\n // Check if already logged in to initialize the store.\n account.getSession({\n sessionId: 'current'\n }).then((user: Models.Session) => {\n current.value = user;\n });\n\n return {\n current,\n login,\n logout,\n register,\n };\n};\n```\n\nThen you can call the `useUserSession()` function in the pages and components to use the functionality.\n\n## Login page {% #login-page %}\n\nCreate a new file in the `pages` directory called `login.vue`.\nThis will not only create a new page, it will also add the route `/login` to the url.\nIn step 5 we will add a login button that will redirect us to this page.\n\nWe will define functions to handle form submissions and show either a signup\nor a login form.\n\n```vue\n\n\n\n\n```\n\nThis page renders a login or sign up form depending on `isSignUp`'s state.\nWe will also show buttons to toggle between the two different types of forms.\n\n## Authentication forms {% #auth-forms %}\n\nIn the previous step, we defined a `AuthForm` to handle signup and login.\nLet's build this form now.\n\nCreate a new file `components/authForm.vue` and add the following code.\nThe handle submit and submit type are props passed from `pages/login.vue`\n\n```html\n\n\n\n\n\n```\n\nGo to the browser and add `/login` to the url to check out the new page."}, {"path": "docs/tutorials/nuxt/step-5", "title": "Add navigation", "description": "Add navigation to your app.", "content": "To help our users navigate the app we want it to have a navigation bar that's visible on all pages.\nWe will once again use the `useUserSession()` composable for information about the current user.\n\nWith this piece of information we will show a login button when no user is logged in and a logout button when one is.\nWe will also put the user's e-mail address next to the logout button.\n\nFrom the `components` directory, create the file `navbar.vue` and add the code below.\n\n```vue\n\n\n\n\n```\n\nOpen `app.vue` from the root directory and add the navigation bar.\n\n```vue\n\n\n\n\n\n```\n\nHave a look in the browser at both the main page and the login page to test the new functionality."}, {"path": "docs/tutorials/nuxt/step-6", "title": "Add database", "description": "Add databases and queries for ideas in your Nuxt project.", "content": "In Appwrite, data is stored as a table of rows. \nCreate a new database and table in the [Appwrite Console](https://cloud.appwrite.io/) to store the ideas.\n\n{% only_dark %}\n![Create table screen](/images/docs/tutorials/dark/idea-tracker-table.avif)\n{% /only_dark %}\n{% only_light %}\n![Create table screen](/images/docs/tutorials/idea-tracker-table.avif)\n{% /only_light %}\n\nCreate a new table with the following columns:\n| Field | Type | Required | Size |\n|-------------|--------|----------|----------|\n| userId | Varchar | Yes | 200 |\n| title | Varchar | Yes | 200 |\n| description | Text | No | - |\n\nChange the table's permissions in the settings to give access.\n\n{% only_dark %}\n![Table permissions screen](/images/docs/tutorials/dark/idea-tracker-permissions.avif)\n{% /only_dark %}\n{% only_light %}\n![Table permissions screen](/images/docs/tutorials/idea-tracker-permissions.avif)\n{% /only_light %}\n\nNavigate to the **Settings** tab of your table, add the role **Any** and check the **Read** box.\nNext, add a **Users** role and give them access to **Create**, **Update** and **Delete** by checking those boxes.\n\n## Environment variables {% #environment-variables %}\n\nJust like when we set up the connection to Appwrite in [step 3](/docs/tutorials/nuxt/step-3), we need to keep the variables with the table id secret.\nOpen the `.env` file and add your database id and your table id to it.\n\n```\nVITE_DATABASE_ID=\"YOUR_DATABASE_ID\"\nVITE_TABLE_ID=\"YOUR_TABLE_ID\"\n```\n\n## Query methods {% #query-methods %}\n\nNow that we have a table in the database to hold ideas, we can connect to it from our app.\nOur users should be able to read, add and remove ideas.\nWe will add a new composable, `useIdeas`, to handle this functionality.\n\nCreate a new file in the composables directory, `useIdeas.js` and add the following code.\n\n```ts\n// composables/useIdeas.ts\n\nimport { ID, Query, Models} from \"appwrite\";\nimport { tablesDB } from \"~/appwrite\";\nimport { ref } from \"vue\";\n\nconst ideasDatabaseId: string = import.meta.env.VITE_DATABASE_ID;\nconst ideasTableId: string = import.meta.env.VITE_TABLE_ID;\nconst queryLimit: number = 10;\n\ninterface Idea extends Models.Row{\n title: string;\n description: string;\n userId: string;\n}\n\nconst current = ref(null); // Reference for the fetched data\n\nexport const useIdeas = () => {\n\n // Fetch the 10 most recent ideas from the database\n // Add the list to the current reference object\n const fetch = async (): Promise => {\n const response = await tablesDB.listRows({\n databaseId: ideasDatabaseId,\n tableId: ideasTableId,\n queries: [Query.orderDesc(\"$createdAt\"), Query.limit(queryLimit)]\n });\n current.value = response.rows as Idea[];\n };\n\n // Add new idea to the database,\n // Change the value of the current object\n const add = async (idea: Idea): Promise => {\n const response = await tablesDB.createRow({\n databaseId: ideasDatabaseId,\n tableId: ideasTableId,\n rowId: ID.unique(),\n data: idea\n });\n current.value = [response, ...(current.value as Idea[])].slice(0, 10) as Idea[];\n };\n\n const remove = async (id: string): Promise => {\n await tablesDB.deleteRow({\n databaseId: ideasDatabaseId,\n tableId: ideasTableId,\n rowId: id\n });\n await fetch(); // Refetch ideas to ensure we have 10 items\n };\n\n fetch();\n\n return {\n add,\n current,\n fetch,\n remove,\n };\n};\n```\n\nNow we can call the `useIdeas` composable from the home page."}, {"path": "docs/tutorials/nuxt/step-7", "title": "Ideas page", "description": "Add ideas from Appwrite database in your app.", "content": "With the methods in the `useIdeas` composable we can get some ideas to the home page for the users to interact with.\nWe will use it in a form component so the logged in users can add their ideas, and in a list component to render the ten most recent ideas.\nWe start with building the form.\n\n## Idea form {% #idea-form %}\n\nOn the home page, the logged in users should be able to add their ideas to the Appwrite database.\nThe form need a text field for filling in the title, a textarea for the description and a submit button.\n\nFrom the `components` directory, add the file `IdeasForm.vue` and add the following code.\n\n```vue\n\n\n\n\n```\n\nNext, add the component to the page `pages/index.vue` by auto-importing it in the `\n```"}, {"path": "docs/tutorials/vue/step-8", "title": "Next steps", "description": "View your Vue.js app build on Appwrite Cloud.", "content": "## Test your project {% #test-project %}\nRun your project with `npm run dev -- --open --port 3000` and open [http://localhost:3000](http://localhost:3000) in your browser."}]} \ No newline at end of file diff --git a/src/mcp_server_appwrite/docs_search.py b/src/mcp_server_appwrite/docs_search.py new file mode 100644 index 0000000..0128276 --- /dev/null +++ b/src/mcp_server_appwrite/docs_search.py @@ -0,0 +1,210 @@ +"""In-process semantic search over the Appwrite documentation. + +The heavy lifting (downloading docs, chunking, embedding) happens ahead of time +in ``scripts/build_docs_index.py``; the result is a small artifact committed +under ``data/`` and loaded here at startup. + +At query time we embed the user's query with the same OpenAI model used to build +the index (``text-embedding-3-small``) and rank the indexed chunks by cosine +similarity. Vectors are L2-normalized at build time, so cosine similarity is a +plain dot product. Matching chunks are deduped to their source page and the full +page content is returned. +""" + +from __future__ import annotations + +import json +import os +from pathlib import Path +from typing import Any, Callable + +import mcp.types as types + +ToolContent = types.TextContent | types.ImageContent | types.EmbeddedResource + +TOOL_NAME = "appwrite_search_docs" +EMBED_MODEL = "text-embedding-3-small" +DEFAULT_LIMIT = 5 +MAX_LIMIT = 10 +DEFAULT_MIN_SCORE = 0.25 +MIN_QUERY_LENGTH = 3 + +DATA_DIR = Path(__file__).parent / "data" +VECTORS_FILE = "docs_index.npz" +META_FILE = "docs_index_meta.json" + +# An embedder maps a query string to its embedding vector. +Embedder = Callable[[str], list[float]] + + +def _default_embedder() -> Embedder | None: + """Build an OpenAI-backed embedder, or ``None`` if no API key is configured.""" + api_key = os.getenv("OPENAI_API_KEY") + if not api_key: + return None + + from openai import OpenAI + + client = OpenAI(api_key=api_key) + + def embed(text: str) -> list[float]: + response = client.embeddings.create(model=EMBED_MODEL, input=text) + return response.data[0].embedding + + return embed + + +def _clamp_limit(value: Any, default: int) -> int: + if value is None: + return default + limit = int(value) + if limit < 1: + raise ValueError("limit must be at least 1.") + return min(limit, MAX_LIMIT) + + +class DocsSearch: + """Loads the committed docs index and answers semantic search queries. + + The instance is *available* only when both the index artifact and an embedder + (OpenAI API key) are present. ``server.build_operator`` omits the tool when the + instance is unavailable so the server still boots without docs search. + """ + + def __init__( + self, + *, + data_dir: Path | None = None, + embedder: Embedder | None = None, + min_score: float | None = None, + default_limit: int = DEFAULT_LIMIT, + ): + self._data_dir = data_dir or DATA_DIR + self._embedder = embedder if embedder is not None else _default_embedder() + self._min_score = ( + min_score + if min_score is not None + else float(os.getenv("DOCS_SEARCH_MIN_SCORE", DEFAULT_MIN_SCORE)) + ) + self._default_limit = int(os.getenv("DOCS_SEARCH_LIMIT", default_limit)) + self._vectors = None # np.ndarray [N, D], L2-normalized + self._chunk_page = None # np.ndarray [N] int, index into self._pages + self._pages: list[dict[str, Any]] = [] + self._index_loaded = self._load_index() + + @property + def available(self) -> bool: + return self._index_loaded and self._embedder is not None + + def _load_index(self) -> bool: + vectors_path = self._data_dir / VECTORS_FILE + meta_path = self._data_dir / META_FILE + if not vectors_path.exists() or not meta_path.exists(): + return False + + import numpy as np + + # allow_pickle stays False: the artifact holds only numeric arrays. + with np.load(vectors_path) as data: + self._vectors = data["vectors"] + self._chunk_page = data["chunk_page"] + + meta = json.loads(meta_path.read_text(encoding="utf-8")) + self._pages = meta.get("pages", []) + return self._vectors is not None and len(self._pages) > 0 + + def get_tool(self) -> types.Tool: + return types.Tool( + name=TOOL_NAME, + description=( + "Search the Appwrite documentation with a natural-language query and " + "return the most relevant documentation pages with their full content. " + "Use this for questions about Appwrite concepts, products, and guides " + "(databases, auth, storage, functions, messaging, sites, and more). " + "This does not require a project_id." + ), + inputSchema={ + "type": "object", + "properties": { + "query": { + "type": "string", + "description": "Natural-language documentation query, e.g. 'how do relationships work in databases'.", + }, + "limit": { + "type": "integer", + "minimum": 1, + "maximum": MAX_LIMIT, + "description": f"Maximum number of pages to return. Defaults to {self._default_limit}.", + }, + }, + "required": ["query"], + "additionalProperties": False, + }, + ) + + def search(self, arguments: dict[str, Any] | None) -> list[ToolContent]: + arguments = arguments or {} + query = str(arguments.get("query", "")).strip() + if len(query) < MIN_QUERY_LENGTH: + raise ValueError( + f"query must be at least {MIN_QUERY_LENGTH} characters long." + ) + if not self.available: + raise RuntimeError( + "Documentation search is unavailable: the docs index or OPENAI_API_KEY is not configured." + ) + + limit = _clamp_limit(arguments.get("limit"), self._default_limit) + results = self._rank(query, limit) + + if not results: + return [ + types.TextContent( + type="text", + text=f'No documentation matched "{query}". Try broader terms.', + ) + ] + + payload = {"query": query, "results": results} + return [ + types.TextContent( + type="text", + text=json.dumps(payload, indent=2, ensure_ascii=False), + ) + ] + + def _rank(self, query: str, limit: int) -> list[dict[str, Any]]: + import numpy as np + + embedding = np.asarray(self._embedder(query), dtype=np.float32) + norm = float(np.linalg.norm(embedding)) + if norm == 0.0: + return [] + embedding /= norm + + scores = self._vectors @ embedding # cosine similarity (both normalized) + + # Take the top `limit` chunks, then dedupe to pages. + top_indices = np.argsort(-scores)[:limit] + + results: list[dict[str, Any]] = [] + seen_pages: set[int] = set() + for index in top_indices: + score = float(scores[index]) + if score < self._min_score: + continue + page_index = int(self._chunk_page[index]) + if page_index in seen_pages: + continue + seen_pages.add(page_index) + page = self._pages[page_index] + results.append( + { + "path": page.get("path", ""), + "title": page.get("title", ""), + "description": page.get("description", ""), + "score": round(score, 3), + "content": page.get("content", ""), + } + ) + return results diff --git a/src/mcp_server_appwrite/http_app.py b/src/mcp_server_appwrite/http_app.py new file mode 100644 index 0000000..5768653 --- /dev/null +++ b/src/mcp_server_appwrite/http_app.py @@ -0,0 +1,172 @@ +"""Hosted Streamable-HTTP transport for the Appwrite MCP. + +Builds a single-tenant Starlette ASGI app for the served Appwrite project: + +* ``/mcp`` — the MCP Streamable-HTTP endpoint, gated by a bearer-token check that + returns an RFC 9728 ``WWW-Authenticate`` challenge when unauthenticated. +* ``/.well-known/oauth-protected-resource/mcp`` — RFC 9728 protected resource + metadata pointing at the project's Appwrite OAuth authorization server. +* ``/healthz`` — liveness probe. + +Auth uses the SDK primitives (``BearerAuthBackend`` + ``AuthContextMiddleware``) so the +validated token is reachable from tool handlers via ``get_access_token()``. +""" + +from __future__ import annotations + +import contextlib +import json +import sys + +from mcp.server.auth.middleware.auth_context import AuthContextMiddleware +from mcp.server.auth.middleware.bearer_auth import AuthenticatedUser, BearerAuthBackend +from mcp.server.streamable_http_manager import StreamableHTTPSessionManager +from starlette.applications import Starlette +from starlette.middleware import Middleware +from starlette.middleware.authentication import AuthenticationMiddleware +from starlette.requests import Request +from starlette.responses import JSONResponse, PlainTextResponse +from starlette.routing import Route +from starlette.types import Receive, Scope, Send + +from .auth import ( + AppwriteTokenVerifier, + protected_resource_metadata, + resource_metadata_url, +) +from .server import ( + SERVER_VERSION, + build_catalog_tools_manager, + build_mcp_server, + build_operator, +) + +_CORS_HEADERS = { + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Methods": "GET, POST, DELETE, OPTIONS", + "Access-Control-Allow-Headers": "Authorization, Content-Type, Mcp-Session-Id, Mcp-Protocol-Version", + "Access-Control-Expose-Headers": "Mcp-Session-Id, WWW-Authenticate", +} + + +def _log(message: str) -> None: + print(f"[appwrite-mcp][http] {message}", file=sys.stderr, flush=True) + + +async def _send_401(send: Send) -> None: + """RFC 9728 §5.1 — 401 with a WWW-Authenticate pointing to resource metadata.""" + metadata_url = resource_metadata_url() + parts = [ + 'error="invalid_token"', + 'error_description="Authentication required"', + f'resource_metadata="{metadata_url}"', + ] + www_authenticate = f"Bearer {', '.join(parts)}" + + body = json.dumps( + {"error": "invalid_token", "error_description": "Authentication required"} + ).encode() + headers = [ + (b"content-type", b"application/json"), + (b"content-length", str(len(body)).encode()), + (b"www-authenticate", www_authenticate.encode()), + ] + await send({"type": "http.response.start", "status": 401, "headers": headers}) + await send({"type": "http.response.body", "body": body}) + + +class RequireBearer: + """ASGI gate: require an authenticated user for ``/mcp`` requests. + + Scope enforcement is delegated to the Appwrite REST API (per-route scope checks + against the token's granted scopes), so the gate only requires a valid token. + """ + + def __init__(self, app: object) -> None: + self.app = app + + async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: + if scope["type"] != "http": # pragma: no cover + await self.app(scope, receive, send) + return + + if scope.get("method") == "OPTIONS": + await self._preflight(send) + return + + user = scope.get("user") + if not isinstance(user, AuthenticatedUser): + await _send_401(send) + return + + await self.app(scope, receive, send) + + async def _preflight(self, send: Send) -> None: + headers = [(k.lower().encode(), v.encode()) for k, v in _CORS_HEADERS.items()] + await send({"type": "http.response.start", "status": 204, "headers": headers}) + await send({"type": "http.response.body", "body": b""}) + + +async def protected_resource_metadata_endpoint(request: Request) -> JSONResponse: + metadata = await protected_resource_metadata() + return JSONResponse(metadata, headers=_CORS_HEADERS) + + +async def health_endpoint(request: Request) -> PlainTextResponse: + return PlainTextResponse(f"appwrite-mcp {SERVER_VERSION} ok") + + +def build_app() -> Starlette: + tools_manager = build_catalog_tools_manager() + operator = build_operator(tools_manager) + server = build_mcp_server(operator) + + # Streamable HTTP with SSE responses (the MCP SDK/ecosystem default). Stateless, + # so each request opens and closes its own short-lived stream — no session to pin. + session_manager = StreamableHTTPSessionManager( + app=server, + json_response=False, + stateless=True, + ) + + async def handle_mcp(scope: Scope, receive: Receive, send: Send) -> None: + await session_manager.handle_request(scope, receive, send) + + mcp_endpoint = RequireBearer(handle_mcp) + + @contextlib.asynccontextmanager + async def lifespan(app: Starlette): + async with session_manager.run(): + _log(f"Appwrite MCP (Streamable HTTP) ready — v{SERVER_VERSION}") + yield + + routes = [ + Route( + "/.well-known/oauth-protected-resource/mcp", + endpoint=protected_resource_metadata_endpoint, + methods=["GET", "OPTIONS"], + ), + Route("/healthz", endpoint=health_endpoint, methods=["GET"]), + Route( + "/mcp", + endpoint=mcp_endpoint, + methods=["GET", "POST", "DELETE", "OPTIONS"], + ), + ] + + middleware = [ + Middleware( + AuthenticationMiddleware, backend=BearerAuthBackend(AppwriteTokenVerifier()) + ), + Middleware(AuthContextMiddleware), + ] + + return Starlette(routes=routes, middleware=middleware, lifespan=lifespan) + + +def run_http(*, host: str = "0.0.0.0", port: int = 8000) -> None: + import uvicorn + + app = build_app() + _log(f"Serving on http://{host}:{port} (resource path: /mcp)") + uvicorn.run(app, host=host, port=port) diff --git a/src/mcp_server_appwrite/operator.py b/src/mcp_server_appwrite/operator.py index 72a293d..76a0402 100644 --- a/src/mcp_server_appwrite/operator.py +++ b/src/mcp_server_appwrite/operator.py @@ -1,9 +1,9 @@ from __future__ import annotations -from collections import OrderedDict -from dataclasses import dataclass import json import re +from collections import OrderedDict +from dataclasses import dataclass from typing import Any, Callable from urllib.parse import urlparse from uuid import uuid4 @@ -11,6 +11,7 @@ import mcp.types as types from mcp.server.lowlevel.helper_types import ReadResourceContents +from .docs_search import DocsSearch from .tool_manager import ToolManager SEARCH_LIMIT = 8 @@ -26,7 +27,10 @@ READ_HINTS = {"fetch", "find", "get", "list", "read", "search", "show", "view"} ToolContent = types.TextContent | types.ImageContent | types.EmbeddedResource -ToolExecutor = Callable[[str, dict[str, Any]], list[ToolContent]] +# (tool_name, arguments, project_id, organization_id) -> content +ToolExecutor = Callable[ + [str, dict[str, Any], str | None, str | None], list[ToolContent] +] @dataclass(frozen=True) @@ -94,11 +98,13 @@ def __init__( tools_manager: ToolManager, execute_tool: ToolExecutor, *, + docs_search: DocsSearch | None = None, preview_threshold: int = PREVIEW_THRESHOLD, search_limit: int = SEARCH_LIMIT, ): self._tools_manager = tools_manager self._execute_tool = execute_tool + self._docs_search = docs_search self._preview_threshold = preview_threshold self._search_limit = search_limit self._result_store = ResultStore() @@ -110,7 +116,7 @@ def get_catalog_resource_uri(self) -> str: return CATALOG_URI def get_public_tools(self) -> list[types.Tool]: - return [ + tools = [ types.Tool( name="appwrite_search_tools", description=( @@ -172,6 +178,25 @@ def get_public_tools(self) -> list[types.Tool]: "type": "boolean", "description": "Required for create, update, and delete tools.", }, + "project_id": { + "type": "string", + "description": ( + "Appwrite project ID to act on (sent as X-Appwrite-Project). " + "The connection authenticates against the Appwrite console, which " + "can list your projects/organizations but holds no data — so " + "project-scoped tools (databases, tables, users, storage, " + "functions, messaging, sites) require this. Discover a project " + "first, then pass its id. Omit for console/account-level tools." + ), + }, + "organization_id": { + "type": "string", + "description": ( + "Appwrite organization (team) ID to act on (sent as " + "X-Appwrite-Organization). Required for organization-scoped " + "console tools such as creating a project. Omit otherwise." + ), + }, }, "required": ["tool_name"], "additionalProperties": True, @@ -179,8 +204,16 @@ def get_public_tools(self) -> list[types.Tool]: ), ] + if self._docs_search is not None: + tools.append(self._docs_search.get_tool()) + + return tools + def has_public_tool(self, name: str) -> bool: - return name in {"appwrite_search_tools", "appwrite_call_tool"} + names = {"appwrite_search_tools", "appwrite_call_tool"} + if self._docs_search is not None: + names.add(self._docs_search.get_tool().name) + return name in names def execute_public_tool( self, name: str, arguments: dict[str, Any] | None @@ -189,6 +222,9 @@ def execute_public_tool( return self._search_tools(arguments or {}) if name == "appwrite_call_tool": return self._call_hidden_tool(arguments or {}) + if self._docs_search is not None and name == self._docs_search.get_tool().name: + content = self._docs_search.search(arguments or {}) + return self._preview_or_store_result(name, content) raise ValueError(f"Unknown public Appwrite tool {name}") def list_resources(self) -> list[types.Resource]: @@ -351,8 +387,14 @@ def _call_hidden_tool(self, raw_arguments: dict[str, Any]) -> list[ToolContent]: f"Tool {tool_name} is {entry.classification}. Re-run appwrite_call_tool with confirm_write=true if you intend to mutate Appwrite state." ) + project_id = raw_arguments.get("project_id", raw_arguments.get("projectId")) + organization_id = raw_arguments.get( + "organization_id", raw_arguments.get("organizationId") + ) arguments_object = _normalize_arguments(raw_arguments) - result_content = self._execute_tool(tool_name, arguments_object) + result_content = self._execute_tool( + tool_name, arguments_object, project_id, organization_id + ) return self._preview_or_store_result(tool_name, result_content) def _preview_or_store_result( @@ -625,6 +667,10 @@ def _normalize_arguments(raw_arguments: dict[str, Any]) -> dict[str, Any]: "args", "confirm_write", "confirmWrite", + "project_id", + "projectId", + "organization_id", + "organizationId", }: continue if value is not None: diff --git a/src/mcp_server_appwrite/server.py b/src/mcp_server_appwrite/server.py index 8347027..7a39569 100644 --- a/src/mcp_server_appwrite/server.py +++ b/src/mcp_server_appwrite/server.py @@ -1,13 +1,16 @@ from __future__ import annotations -import asyncio + import argparse import base64 -from dataclasses import dataclass +import importlib +import inspect import json import os +import pkgutil import re import sys from collections.abc import Mapping +from dataclasses import dataclass from datetime import date, datetime from decimal import Decimal from enum import Enum @@ -15,30 +18,58 @@ from types import UnionType from typing import Any, Union, get_args, get_origin -import mcp.server.stdio import mcp.types as types -from mcp.server import NotificationOptions, Server -from mcp.server.models import InitializationOptions -from dotenv import find_dotenv, load_dotenv from appwrite.client import Client -from appwrite.input_file import InputFile from appwrite.enums.browser import Browser -from appwrite.services.tables_db import TablesDB -from appwrite.services.users import Users -from appwrite.services.teams import Teams -from appwrite.services.storage import Storage -from appwrite.services.functions import Functions -from appwrite.services.locale import Locale -from appwrite.services.avatars import Avatars -from appwrite.services.messaging import Messaging -from appwrite.services.sites import Sites from appwrite.exception import AppwriteException +from appwrite.input_file import InputFile +from appwrite.service import Service as _SdkService +from dotenv import find_dotenv, load_dotenv +from mcp.server import Server +from mcp.server.auth.middleware.auth_context import get_access_token from mcp.server.lowlevel.helper_types import ReadResourceContents + +from .docs_search import DocsSearch from .operator import Operator from .service import Service from .tool_manager import ToolManager -SERVER_VERSION = "0.4.1" +SERVER_VERSION = "0.7.0" + +DEFAULT_ENDPOINT = "https://cloud.appwrite.io/v1" + +# Service modules in the Appwrite SDK to skip (none by default — every service the +# installed SDK ships is exposed). Add a module name here to hide a service. +EXCLUDED_SERVICES: frozenset[str] = frozenset() + + +def _discover_service_classes() -> dict[str, type]: + """Discover every Appwrite SDK service class, keyed by its module name + (e.g. ``"tables_db" -> TablesDB``). The module name is used as the tool-name + prefix. The catalog/schema is built once from these classes; at execution time + the matching class is re-instantiated on a per-request client (see + ``resolve_client``).""" + import appwrite.services as services_pkg + + discovered: dict[str, type] = {} + for module_info in pkgutil.iter_modules(services_pkg.__path__): + name = module_info.name + if name in EXCLUDED_SERVICES: + continue + module = importlib.import_module(f"appwrite.services.{name}") + for _, cls in inspect.getmembers(module, inspect.isclass): + if ( + issubclass(cls, _SdkService) + and cls is not _SdkService + and cls.__module__ == module.__name__ + ): + discovered[name] = cls + break + return discovered + + +# Maps the MCP service name (tool-name prefix) to its Appwrite SDK service class. +SERVICE_CLASSES: dict[str, type] = _discover_service_classes() @dataclass(frozen=True) @@ -54,6 +85,17 @@ def _log_startup(message: str) -> None: def parse_args(): parser = argparse.ArgumentParser(description="Appwrite MCP Server") + parser.add_argument( + "--host", + default=os.getenv("HOST", "0.0.0.0"), + help="Bind host for the HTTP server (default $HOST or 0.0.0.0).", + ) + parser.add_argument( + "--port", + type=int, + default=int(os.getenv("PORT", "8000")), + help="Bind port for the HTTP server (default $PORT or 8000).", + ) return parser.parse_args() @@ -68,7 +110,7 @@ def load_appwrite_config() -> AppwriteConfig: project_id = os.getenv("APPWRITE_PROJECT_ID") api_key = os.getenv("APPWRITE_API_KEY") - endpoint = os.getenv("APPWRITE_ENDPOINT", "https://cloud.appwrite.io/v1") + endpoint = os.getenv("APPWRITE_ENDPOINT", DEFAULT_ENDPOINT) if not project_id or not api_key: raise ValueError( @@ -88,21 +130,83 @@ def build_client(config: AppwriteConfig | None = None) -> Client: return client +def build_introspection_client() -> Client: + """A credential-less client used only to introspect SDK methods for schema + generation. It never makes API calls, so no project/key is required.""" + client = Client() + client.set_endpoint(os.getenv("APPWRITE_ENDPOINT", DEFAULT_ENDPOINT)) + client.add_header("x-sdk-name", "mcp") + return client + + +def build_client_for_request( + project_id: str, + bearer_token: str, + endpoint: str | None = None, + target_project: str | None = None, + organization_id: str | None = None, +) -> Client: + """Build a per-request client authenticated with a user's OAuth2 access token. + The Appwrite REST API accepts the OAuth2 access token directly as a Bearer token + and resolves the user + granted scopes from it. + + The token authenticates against the Appwrite console project. To act on one of + the user's own projects, pass ``target_project``: it is sent as the + ``X-Appwrite-Project`` header so the same console token operates on that + project's data. ``organization_id`` sets ``X-Appwrite-Organization`` for + org-scoped console operations (e.g. creating a project). + + Targeting a real project also sends ``X-Appwrite-Mode: admin``. Admin mode is + what lets a console-issued token be recognized across projects (resolving the + user as the project owner); without it the token is not a valid identity on + another project and the request falls back to the guest role. Admin mode is + only valid against a real project — the API rejects it on the console project — + so it is not sent for ``organization_id``-only (console) operations.""" + client = Client() + client.set_endpoint(endpoint or os.getenv("APPWRITE_ENDPOINT", DEFAULT_ENDPOINT)) + client.set_project(target_project or project_id) + client.add_header("Authorization", f"Bearer {bearer_token}") + client.add_header("x-sdk-name", "mcp") + if target_project: + client.add_header("x-appwrite-project", target_project) + # Admin mode lets the console-issued token be recognized on another project + # (as the owner) instead of falling back to guest. It is only valid when + # targeting a real project — the API rejects admin mode on the console + # project itself — so it is gated on target_project, not organization_id. + client.add_header("x-appwrite-mode", "admin") + if organization_id: + client.add_header("x-appwrite-organization", organization_id) + return client + + +def resolve_client( + target_project: str | None = None, organization_id: str | None = None +) -> Client: + """Build the Appwrite client for the current request from its OAuth access + token. The token is read from the request context populated by the auth + middleware and carries the project it was issued for (the console). Pass + ``target_project``/``organization_id`` to scope the call to one of the user's + own projects/organizations.""" + access_token = get_access_token() + if access_token is None: + raise RuntimeError("No authenticated Appwrite access token in request context.") + + claims = access_token.claims or {} + project_id = claims.get("project_id") + if not project_id: + raise RuntimeError("Authenticated token is missing a project identifier.") + return build_client_for_request( + project_id, + access_token.token, + target_project=target_project, + organization_id=organization_id, + ) + + def register_services(client: Client) -> ToolManager: tools_manager = ToolManager() - for service in [ - Service(TablesDB(client), "tables_db"), - Service(Users(client), "users"), - Service(Teams(client), "teams"), - Service(Storage(client), "storage"), - Service(Functions(client), "functions"), - Service(Messaging(client), "messaging"), - Service(Locale(client), "locale"), - Service(Avatars(client), "avatars"), - Service(Sites(client), "sites"), - ]: - tools_manager.register_service(service) - + for name, service_cls in SERVICE_CLASSES.items(): + tools_manager.register_service(Service(service_cls(client), name)) return tools_manager @@ -366,13 +470,28 @@ def execute_registered_tool( tools_manager: ToolManager, name: str, arguments: dict[str, Any] | None, + client: Client | None = None, + target_project: str | None = None, + organization_id: str | None = None, ) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]: tool_info = tools_manager.get_tool(name) if not tool_info: raise ValueError(f"Tool {name} not found") prepared_arguments = _prepare_arguments(tool_info, arguments or {}) - bound_method = tool_info["function"] + + service_name = tool_info["service_name"] + method_name = tool_info["method_name"] + service_cls = SERVICE_CLASSES.get(service_name) + if service_cls is None: + raise ValueError(f"Unknown service '{service_name}' for tool {name}") + + # Re-bind the SDK method to a client authenticated for the current request. + # An explicit client takes precedence (used by tests); otherwise it is resolved + # from the request's OAuth access token. + if client is None: + client = resolve_client(target_project, organization_id) + bound_method = getattr(service_cls(client), method_name) try: result = bound_method(**prepared_arguments) @@ -481,10 +600,18 @@ def _format_appwrite_error(exc: AppwriteException) -> str: return f"Appwrite request failed{detail_text}: {exc}" -async def serve(operator: Operator) -> Server: +def build_mcp_server(operator: Operator) -> Server: instructions = ( "Appwrite workflow: use appwrite_search_tools first, then appwrite_call_tool. " + "You authenticate against the Appwrite console, which can list your " + "organizations and projects but stores no project data itself. Project-scoped " + "tools (databases, tables, users, storage, functions, messaging, sites) need a " + "target project: first discover one (search for and call a tool that lists " + "projects), then pass its id as project_id to appwrite_call_tool. " + "Organization-scoped console tools (e.g. creating a project) need organization_id. " "Mutating hidden tools require confirm_write=true. " + "For questions about Appwrite concepts, products, or guides, use " + "appwrite_search_docs to search the documentation (no project_id needed). " "Large results are stored as resources; read the URI returned by the tool." ) @@ -518,43 +645,59 @@ async def handle_read_resource(uri) -> list[ReadResourceContents]: return server -async def _run(): - parse_args() - _log_startup("Loading Appwrite configuration") - config = load_appwrite_config() - client = build_client(config) - _log_startup(f"Using Appwrite endpoint: {config.endpoint}") - _log_startup("Registering Appwrite services") - tools_manager = register_services(client) - _log_startup("Starting Appwrite service validation") - validate_services(tools_manager) - _log_startup("Building Appwrite operator surface") - operator = Operator( +def build_operator(tools_manager: ToolManager) -> Operator: + """Wire the operator surface to the per-request execution path. The execution + callback re-binds each call to a per-request client via `resolve_client`. + + The docs-search tool is wired in only when its committed index and an + OPENAI_API_KEY are both available; otherwise the server boots without it.""" + docs_search = DocsSearch() + if docs_search.available: + _log_startup("Documentation search enabled (appwrite_search_docs)") + else: + _log_startup( + "Documentation search disabled: docs index or OPENAI_API_KEY not configured" + ) + docs_search = None + + return Operator( tools_manager, - lambda tool_name, tool_arguments: execute_registered_tool( + lambda tool_name, tool_arguments, target_project=None, organization_id=None: execute_registered_tool( tools_manager, tool_name, tool_arguments, + target_project=target_project, + organization_id=organization_id, ), + docs_search=docs_search, ) - async with mcp.server.stdio.stdio_server() as (read_stream, write_stream): - server = await serve(operator) - _log_startup("MCP transport: stdio") - _log_startup("Appwrite MCP server ready") - await server.run( - read_stream, - write_stream, - InitializationOptions( - server_name="appwrite", - server_version=SERVER_VERSION, - capabilities=server.get_capabilities( - notification_options=NotificationOptions(), - experimental_capabilities={}, - ), - ), - ) + +def build_catalog_tools_manager() -> ToolManager: + """Build the tool catalog/schema once from SDK introspection. Credentials arrive + per request (OAuth) rather than at startup, so a credential-less client suffices.""" + return register_services(build_introspection_client()) + + +def main(): + """Entry point: serve the Streamable-HTTP + OAuth resource server.""" + # Load .env early so env-driven config (APPWRITE_ENDPOINT, MCP_PUBLIC_URL, + # OPENAI_API_KEY for docs search) is available before the app is built. In + # production these are injected directly into the environment. + cwd_dotenv = Path.cwd() / ".env" + if cwd_dotenv.exists(): + load_dotenv(dotenv_path=cwd_dotenv) + else: + discovered_dotenv = find_dotenv(usecwd=True) + if discovered_dotenv: + load_dotenv(dotenv_path=discovered_dotenv) + + args = parse_args() + from .http_app import run_http + + run_http(host=args.host, port=args.port) + return 0 if __name__ == "__main__": - asyncio.run(_run()) + main() diff --git a/src/mcp_server_appwrite/service.py b/src/mcp_server_appwrite/service.py index 609172b..7f2b84c 100644 --- a/src/mcp_server_appwrite/service.py +++ b/src/mcp_server_appwrite/service.py @@ -1,12 +1,12 @@ -from enum import Enum import inspect import re +from enum import Enum from types import UnionType from typing import Any, Dict, List, Union, get_args, get_origin, get_type_hints from appwrite.input_file import InputFile -from mcp.types import Tool from docstring_parser import parse +from mcp.types import Tool class Service: @@ -196,7 +196,11 @@ def list_tools(self) -> Dict[str, Dict]: tools[tool_name] = { "definition": tool_definition, - "function": func, + # Store the service + method identity rather than a bound method so + # execution can re-bind to a per-request Appwrite client (HTTP/OAuth + # mode) instead of the credential-less client used for introspection. + "service_name": self.service_name, + "method_name": name, "parameter_types": { param_name: type_hints[param_name] for param_name in signature.parameters diff --git a/src/mcp_server_appwrite/tool_manager.py b/src/mcp_server_appwrite/tool_manager.py index 2c9e82e..9b6cbfe 100644 --- a/src/mcp_server_appwrite/tool_manager.py +++ b/src/mcp_server_appwrite/tool_manager.py @@ -1,5 +1,7 @@ -from typing import List, Dict +from typing import Dict, List + from mcp.types import Tool + from .service import Service diff --git a/tests/integration/support.py b/tests/integration/support.py index fcb6e7a..9c5e20f 100644 --- a/tests/integration/support.py +++ b/tests/integration/support.py @@ -1,17 +1,17 @@ from __future__ import annotations import json -import os import struct import tempfile import time +import unittest import zlib from dataclasses import dataclass from pathlib import Path from typing import Any from uuid import uuid4 -import unittest +from mcp_server_appwrite.docs_search import DocsSearch from mcp_server_appwrite.operator import Operator from mcp_server_appwrite.server import ( build_client, @@ -50,22 +50,33 @@ class LiveSurfaceRunner: def __init__(self): self.client = build_client() self.manager = register_services(self.client) + docs_search = DocsSearch() + self.docs_available = docs_search.available self.runtime = Operator( self.manager, - lambda tool_name, arguments: execute_registered_tool( + lambda tool_name, arguments, *_: execute_registered_tool( self.manager, tool_name, arguments, + self.client, ), + docs_search=docs_search if self.docs_available else None, ) self.tool_outcomes: dict[str, ToolOutcome] = {} self.public_outcomes: list[ToolOutcome] = [] + @property + def expected_public_outcomes(self) -> int: + """Number of public-surface calls run_public_smoke records.""" + return 4 if self.docs_available else 3 + def _invoke(self, tool_name: str, arguments: dict[str, Any] | None = None) -> Any: last_exc: Exception | None = None for attempt in range(1, 4): try: - return execute_registered_tool(self.manager, tool_name, arguments or {}) + return execute_registered_tool( + self.manager, tool_name, arguments or {}, self.client + ) except Exception as exc: # pragma: no cover - exercised live message = str(exc).lower() transient = ( @@ -256,6 +267,21 @@ def run_public_smoke(self) -> None: "confirm_write guard unexpectedly allowed a mutating call" ) + if not self.docs_available: + return + + try: + docs = self.runtime.execute_public_tool( + "appwrite_search_docs", + {"query": "create a database collection"}, + ) + self._register_public( + "appwrite_search_docs", "success", str(docs[0].text[:200]) + ) + except Exception as exc: # pragma: no cover - exercised live + self._register_public("appwrite_search_docs", "unexpected_error", str(exc)) + raise + class LiveIntegrationTestCase(unittest.TestCase): def new_runner(self) -> LiveSurfaceRunner: diff --git a/tests/integration/test_functions.py b/tests/integration/test_functions.py index 57c8adf..0501eea 100644 --- a/tests/integration/test_functions.py +++ b/tests/integration/test_functions.py @@ -1,5 +1,4 @@ from appwrite.enums.runtime import Runtime - from support import LiveIntegrationTestCase, requires_live_integration diff --git a/tests/integration/test_oauth_discovery.py b/tests/integration/test_oauth_discovery.py new file mode 100644 index 0000000..194bc25 --- /dev/null +++ b/tests/integration/test_oauth_discovery.py @@ -0,0 +1,65 @@ +"""Live contract test for the OAuth authorization-server discovery the MCP relies on. + +The hosted MCP server is an OAuth 2.1 Resource Server: it points clients at the +project's Appwrite authorization server, and MCP-aware clients then self-register +via RFC 7591 Dynamic Client Registration. That registration step only works if the +authorization server advertises ``registration_endpoint`` in its discovery document +(added by the cloud ``feat/oauth2-dynamic-client-registration`` work). + +This test locks that contract end-to-end against the configured Appwrite endpoint: +the same discovery document the MCP fetches for ``scopes_supported`` must advertise +the registration endpoint the README promises. It is skipped without live config. + +Note: it asserts against the document's own ``issuer`` (not the request host) so it +is correct even when the endpoint regionally redirects (e.g. ``fra.cloud.appwrite.io``). +""" + +from __future__ import annotations + +import unittest + +import httpx +from support import requires_live_integration + +from mcp_server_appwrite import auth + + +@requires_live_integration +class OAuthDiscoveryContractTests(unittest.TestCase): + def _discovery(self) -> dict: + url = f"{auth.issuer_url()}/.well-known/openid-configuration" + resp = httpx.get(url, timeout=10.0, follow_redirects=True) + resp.raise_for_status() + return resp.json() + + def test_discovery_advertises_dynamic_client_registration(self): + doc = self._discovery() + + # The fields the OAuth 2.1 + DCR client flow depends on. + issuer = doc.get("issuer") + self.assertTrue(issuer, "discovery document missing issuer") + self.assertTrue(doc.get("token_endpoint"), "discovery missing token_endpoint") + + # RFC 7591: registration must be advertised for clients to self-register. + # Skip (rather than fail) when the endpoint predates the dynamic client + # registration rollout, so this stays green against not-yet-upgraded + # deployments and starts verifying the moment the endpoint exposes it. + if "registration_endpoint" not in doc: + self.skipTest( + "authorization server does not advertise registration_endpoint yet " + "(RFC 7591 dynamic client registration not deployed for this endpoint)" + ) + self.assertEqual(doc["registration_endpoint"], f"{issuer}/register") + + def test_discovery_exposes_supported_scopes(self): + # The MCP sources its protected-resource metadata scopes from here. + doc = self._discovery() + self.assertIsInstance( + doc.get("scopes_supported"), + list, + "discovery missing scopes_supported (MCP cannot advertise scopes)", + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/integration/test_operator.py b/tests/integration/test_operator.py index 3f17b8f..87d2acd 100644 --- a/tests/integration/test_operator.py +++ b/tests/integration/test_operator.py @@ -13,5 +13,5 @@ def test_public_surface(self): if outcome.outcome == "unexpected_error" ] - self.assertEqual(len(runner.public_outcomes), 3) + self.assertEqual(len(runner.public_outcomes), runner.expected_public_outcomes) self.assertFalse(unexpected_errors, unexpected_errors) diff --git a/tests/integration/test_sites.py b/tests/integration/test_sites.py index 71e544b..3cfd3cf 100644 --- a/tests/integration/test_sites.py +++ b/tests/integration/test_sites.py @@ -1,6 +1,5 @@ from appwrite.enums.build_runtime import BuildRuntime from appwrite.enums.framework import Framework - from support import LiveIntegrationTestCase, requires_live_integration diff --git a/tests/integration/test_storage.py b/tests/integration/test_storage.py index ba6ddb1..eae99d6 100644 --- a/tests/integration/test_storage.py +++ b/tests/integration/test_storage.py @@ -1,5 +1,4 @@ from appwrite.enums.compression import Compression - from support import LiveIntegrationTestCase, requires_live_integration diff --git a/tests/test_all.py b/tests/test_all.py index f496ba3..8bc7f9e 100644 --- a/tests/test_all.py +++ b/tests/test_all.py @@ -1,7 +1,7 @@ import importlib.util import sys -from pathlib import Path import unittest +from pathlib import Path def load_tests( diff --git a/tests/unit/test_auth.py b/tests/unit/test_auth.py new file mode 100644 index 0000000..df52d1c --- /dev/null +++ b/tests/unit/test_auth.py @@ -0,0 +1,220 @@ +import asyncio +import os +import unittest +from unittest import mock + +import jwt + +from mcp_server_appwrite import auth + +ENV = { + "APPWRITE_ENDPOINT": "https://cloud.appwrite.io/v1", + "MCP_PUBLIC_URL": "https://mcp.appwrite.io", + "APPWRITE_PROJECT_ID": "console", +} + + +class AuthHelperTests(unittest.TestCase): + def setUp(self): + patcher = mock.patch.dict(os.environ, ENV, clear=False) + patcher.start() + self.addCleanup(patcher.stop) + + def test_issuer_and_resource_urls(self): + self.assertEqual( + auth.issuer_url(), "https://cloud.appwrite.io/v1/oauth2/console" + ) + self.assertEqual(auth.canonical_resource(), "https://mcp.appwrite.io/mcp") + self.assertEqual( + auth.resource_metadata_url(), + "https://mcp.appwrite.io/.well-known/oauth-protected-resource/mcp", + ) + + def test_build_resource_metadata_shape(self): + meta = auth.build_resource_metadata(["users.read", "teams.read"]) + self.assertEqual(meta["resource"], "https://mcp.appwrite.io/mcp") + self.assertEqual( + meta["authorization_servers"], + ["https://cloud.appwrite.io/v1/oauth2/console"], + ) + self.assertEqual(meta["bearer_methods_supported"], ["header"]) + self.assertEqual(meta["scopes_supported"], ["users.read", "teams.read"]) + + def test_supported_scopes_uses_cache_without_network(self): + pid = auth.configured_project_id() + auth._discovery_cache[pid] = { + "issuer": "https://fra.cloud.appwrite.io/v1/oauth2/console", + "jwks_uri": "https://fra.cloud.appwrite.io/v1/oauth2/console/.well-known/jwks.json", + "scopes_supported": ["rows.read", "rows.write"], + } + try: + scopes = asyncio.run(auth.supported_scopes()) + finally: + auth._discovery_cache.pop(pid, None) + self.assertEqual(scopes, ["rows.read", "rows.write"]) + + def test_supported_scopes_reads_dcr_enabled_discovery(self): + # The authorization server's discovery document now also advertises + # `registration_endpoint` (RFC 7591). Sourcing scopes must keep working + # against that document — the MCP points clients at this same AS, and + # the clients self-register there. This guards the discovery contract + # without a network round-trip. + discovery = { + "issuer": "https://cloud.appwrite.io/v1/oauth2/console", + "jwks_uri": "https://cloud.appwrite.io/v1/oauth2/console/.well-known/jwks.json", + "registration_endpoint": "https://cloud.appwrite.io/v1/oauth2/console/register", + "scopes_supported": ["users.read", "rows.write"], + } + seen_urls: list[str] = [] + + class _FakeResponse: + def raise_for_status(self): + return None + + def json(self): + return discovery + + class _FakeAsyncClient: + def __init__(self, *args, **kwargs): + pass + + async def __aenter__(self): + return self + + async def __aexit__(self, *args): + return False + + async def get(self, url): + seen_urls.append(url) + return _FakeResponse() + + with mock.patch.object(auth.httpx, "AsyncClient", _FakeAsyncClient): + try: + scopes = asyncio.run(auth.supported_scopes()) + finally: + auth._discovery_cache.pop("console", None) + + self.assertEqual(scopes, ["users.read", "rows.write"]) + # Discovery is read from the OIDC well-known path under the served project's issuer. + self.assertEqual( + seen_urls, + [ + "https://cloud.appwrite.io/v1/oauth2/console/.well-known/openid-configuration" + ], + ) + # The registration endpoint the MCP points clients to follows `issuer/register`. + self.assertEqual( + discovery["registration_endpoint"], + f"{discovery['issuer']}/register", + ) + + def test_supported_scopes_raises_when_discovery_unreachable(self): + # Point discovery at an unroutable address so the fetch fails fast. + with mock.patch.dict( + os.environ, + { + "APPWRITE_ENDPOINT": "http://127.0.0.1:1/v1", + "APPWRITE_PROJECT_ID": "unreachableproj", + }, + ): + with self.assertRaises(Exception): + asyncio.run(auth.supported_scopes()) + self.assertNotIn("unreachableproj", auth._discovery_cache) + + def test_project_id_from_issuer_accepts_matching_issuer(self): + self.assertEqual( + auth.project_id_from_issuer("https://cloud.appwrite.io/v1/oauth2/abc123"), + "abc123", + ) + + def test_project_id_from_issuer_accepts_cloud_regional_issuer(self): + self.assertEqual( + auth.project_id_from_issuer( + "https://fra.cloud.appwrite.io/v1/oauth2/abc123" + ), + "abc123", + ) + + def test_project_id_from_issuer_rejects_foreign_issuer(self): + self.assertEqual( + auth.project_id_from_issuer("https://evil.example.com/v1/oauth2/abc123"), + "abc123", + ) + self.assertIsNone(auth.project_id_from_issuer(None)) + # Extra path segments are not a valid single project id. + self.assertIsNone( + auth.project_id_from_issuer("https://cloud.appwrite.io/v1/oauth2/abc/extra") + ) + + def test_protected_resource_metadata_uses_discovered_issuer(self): + pid = auth.configured_project_id() + auth._discovery_cache[pid] = { + "issuer": "https://fra.cloud.appwrite.io/v1/oauth2/console", + "jwks_uri": "https://fra.cloud.appwrite.io/v1/oauth2/console/.well-known/jwks.json", + "scopes_supported": ["users.read"], + } + try: + meta = asyncio.run(auth.protected_resource_metadata()) + finally: + auth._discovery_cache.pop(pid, None) + + self.assertEqual( + meta["authorization_servers"], + ["https://fra.cloud.appwrite.io/v1/oauth2/console"], + ) + + +class AudienceTests(unittest.TestCase): + def setUp(self): + patcher = mock.patch.dict(os.environ, ENV, clear=False) + patcher.start() + self.addCleanup(patcher.stop) + self.verifier = auth.AppwriteTokenVerifier() + self.resource = "https://mcp.appwrite.io/mcp" + + def test_audience_match_string(self): + self.assertTrue(self.verifier._audience_ok(self.resource, self.resource)) + + def test_audience_match_list(self): + self.assertTrue( + self.verifier._audience_ok(["other", self.resource], self.resource) + ) + + def test_audience_mismatch_rejected(self): + self.assertFalse(self.verifier._audience_ok("nope", self.resource)) + + def test_missing_audience_rejected(self): + self.assertFalse(self.verifier._audience_ok(None, self.resource)) + + def test_verify_rejects_token_for_other_project(self): + # A token issued by a different project's authorization server must be + # rejected even before signature checks: this MCP serves one project only. + # The mismatch is caught from the (unverified) issuer claim, so an unsigned + # token with a foreign issuer is enough to exercise the guard. + token = jwt.encode( + {"iss": "https://cloud.appwrite.io/v1/oauth2/some-other-project"}, + "x" * 32, + algorithm="HS256", + ) + self.assertIsNone(self.verifier._verify_sync(token)) + + def test_verify_rejects_token_with_undiscovered_issuer(self): + pid = auth.configured_project_id() + auth._discovery_cache[pid] = { + "issuer": "https://fra.cloud.appwrite.io/v1/oauth2/console", + "jwks_uri": "https://fra.cloud.appwrite.io/v1/oauth2/console/.well-known/jwks.json", + "scopes_supported": ["users.read"], + } + token = jwt.encode( + {"iss": "https://cloud.appwrite.io/v1/oauth2/console"}, + "x" * 32, + algorithm="HS256", + ) + try: + self.assertIsNone(self.verifier._verify_sync(token)) + finally: + auth._discovery_cache.pop(pid, None) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/unit/test_docs_search.py b/tests/unit/test_docs_search.py new file mode 100644 index 0000000..21497ae --- /dev/null +++ b/tests/unit/test_docs_search.py @@ -0,0 +1,126 @@ +import json +import tempfile +import unittest +from pathlib import Path + +import numpy as np + +from mcp_server_appwrite.docs_search import MAX_LIMIT, DocsSearch, _clamp_limit + + +def write_index(data_dir: Path) -> None: + # Three pages; page 0 has two chunks. Dimension 3 keeps the test tiny. + vectors = np.array( + [ + [1.0, 0.0, 0.0], # chunk 0 -> page 0 (databases) + [0.96, 0.28, 0.0], # chunk 1 -> page 0 (databases) + [0.0, 1.0, 0.0], # chunk 2 -> page 1 (storage) + [0.0, 0.0, 1.0], # chunk 3 -> page 2 (auth) + ], + dtype=np.float32, + ) + norms = np.linalg.norm(vectors, axis=1, keepdims=True) + vectors = vectors / norms + np.savez_compressed( + data_dir / "docs_index.npz", + vectors=vectors, + chunk_page=np.array([0, 0, 1, 2], dtype=np.int32), + ) + pages = [ + { + "path": "docs/products/databases", + "title": "Databases", + "description": "Work with databases.", + "content": "# Databases\nFull databases page content.", + }, + { + "path": "docs/products/storage", + "title": "Storage", + "description": "Store files.", + "content": "# Storage\nFull storage page content.", + }, + { + "path": "docs/products/auth", + "title": "Authentication", + "description": "Authenticate users.", + "content": "# Auth\nFull auth page content.", + }, + ] + (data_dir / "docs_index_meta.json").write_text( + json.dumps({"model": "test", "dimension": 3, "pages": pages}) + ) + + +QUERY_VECTORS = { + "databases query": [1.0, 0.0, 0.0], + "auth query": [0.0, 0.0, 1.0], + "mixed query": [1.0, 1.0, 1.0], +} + + +def fake_embedder(query: str) -> list[float]: + return QUERY_VECTORS[query] + + +class DocsSearchTests(unittest.TestCase): + def setUp(self): + self._tmp = tempfile.TemporaryDirectory() + self.data_dir = Path(self._tmp.name) + write_index(self.data_dir) + + def tearDown(self): + self._tmp.cleanup() + + def make_search(self, **kwargs) -> DocsSearch: + return DocsSearch( + data_dir=self.data_dir, + embedder=fake_embedder, + **kwargs, + ) + + def test_available_when_index_and_embedder_present(self): + self.assertTrue(self.make_search().available) + + def test_unavailable_without_embedder(self): + search = DocsSearch(data_dir=self.data_dir, embedder=None) + self.assertFalse(search.available) + with self.assertRaisesRegex(RuntimeError, "unavailable"): + search.search({"query": "databases query"}) + + def test_short_query_is_rejected(self): + with self.assertRaisesRegex(ValueError, "at least 3"): + self.make_search().search({"query": "ab"}) + + def test_ranks_and_dedupes_to_page(self): + result = self.make_search().search({"query": "databases query"}) + payload = json.loads(result[0].text) + # Both top chunks belong to page 0 -> a single deduped result. + self.assertEqual(len(payload["results"]), 1) + top = payload["results"][0] + self.assertEqual(top["path"], "docs/products/databases") + self.assertEqual(top["title"], "Databases") + self.assertIn("Full databases page content", top["content"]) + self.assertAlmostEqual(top["score"], 1.0, places=2) + + def test_min_score_filters_unrelated_pages(self): + # Query aligns only with the auth chunk; orthogonal chunks score 0. + result = self.make_search(min_score=0.25).search({"query": "auth query"}) + payload = json.loads(result[0].text) + self.assertEqual(len(payload["results"]), 1) + self.assertEqual(payload["results"][0]["path"], "docs/products/auth") + + def test_no_match_returns_message(self): + # The mixed query scores ~0.577 against every chunk; a high threshold + # rejects them all. + result = self.make_search(min_score=0.99).search({"query": "mixed query"}) + self.assertIn("No documentation matched", result[0].text) + + def test_clamp_limit(self): + self.assertEqual(_clamp_limit(None, 5), 5) + self.assertEqual(_clamp_limit(50, 5), MAX_LIMIT) + with self.assertRaises(ValueError): + _clamp_limit(0, 5) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/unit/test_operator.py b/tests/unit/test_operator.py index 22aa502..d540245 100644 --- a/tests/unit/test_operator.py +++ b/tests/unit/test_operator.py @@ -22,6 +22,28 @@ def make_tool( ) +class FakeDocsSearch: + """Minimal stand-in for DocsSearch used to test the operator wiring.""" + + def __init__(self, content): + self._content = content + + def get_tool(self) -> types.Tool: + return types.Tool( + name="appwrite_search_docs", + description="Search the Appwrite documentation.", + inputSchema={ + "type": "object", + "properties": {"query": {"type": "string"}}, + "required": ["query"], + "additionalProperties": False, + }, + ) + + def search(self, arguments): + return self._content + + class OperatorTests(unittest.TestCase): def make_runtime(self, executor): manager = ToolManager() @@ -78,8 +100,48 @@ def make_runtime(self, executor): } return Operator(manager, executor) + def make_runtime_with_docs(self, docs_search): + manager = ToolManager() + manager.tools_registry = { + "tables_db_list": { + "definition": make_tool("tables_db_list", "List all databases."), + "function": object(), + "parameter_types": {}, + }, + } + return Operator(manager, lambda *_: [], docs_search=docs_search) + + def test_docs_tool_absent_without_docs_search(self): + runtime = self.make_runtime(lambda name, arguments, *_: []) + names = {tool.name for tool in runtime.get_public_tools()} + self.assertEqual(names, {"appwrite_search_tools", "appwrite_call_tool"}) + self.assertFalse(runtime.has_public_tool("appwrite_search_docs")) + + def test_docs_tool_listed_and_dispatched(self): + docs = FakeDocsSearch([types.TextContent(type="text", text='{"results": []}')]) + runtime = self.make_runtime_with_docs(docs) + + tools = runtime.get_public_tools() + self.assertEqual(len(tools), 3) + self.assertIn("appwrite_search_docs", {tool.name for tool in tools}) + self.assertTrue(runtime.has_public_tool("appwrite_search_docs")) + + result = runtime.execute_public_tool( + "appwrite_search_docs", {"query": "databases"} + ) + self.assertEqual(result[0].text, '{"results": []}') + + def test_docs_tool_large_result_is_stored_as_resource(self): + docs = FakeDocsSearch([types.TextContent(type="text", text="x" * 1200)]) + runtime = self.make_runtime_with_docs(docs) + + result = runtime.execute_public_tool( + "appwrite_search_docs", {"query": "databases"} + ) + self.assertIn("appwrite://operator/results/", result[0].text) + def test_search_tools_returns_ranked_match(self): - runtime = self.make_runtime(lambda name, arguments: []) + runtime = self.make_runtime(lambda name, arguments, *_: []) result = runtime.execute_public_tool( "appwrite_search_tools", @@ -92,7 +154,7 @@ def test_search_tools_returns_ranked_match(self): self.assertIn(CATALOG_URI, result[0].text) def test_search_tools_infers_mutating_search_for_create_query(self): - runtime = self.make_runtime(lambda name, arguments: []) + runtime = self.make_runtime(lambda name, arguments, *_: []) result = runtime.execute_public_tool( "appwrite_search_tools", @@ -103,7 +165,7 @@ def test_search_tools_infers_mutating_search_for_create_query(self): self.assertIn("functions_create", result[0].text) def test_search_tools_surfaces_required_create_tool_without_argument_hints(self): - runtime = self.make_runtime(lambda name, arguments: []) + runtime = self.make_runtime(lambda name, arguments, *_: []) result = runtime.execute_public_tool( "appwrite_search_tools", @@ -114,7 +176,7 @@ def test_search_tools_surfaces_required_create_tool_without_argument_hints(self) self.assertIn("tables_db_create_string_column", result[0].text) def test_search_tools_scores_get_queries_against_get_tools(self): - runtime = self.make_runtime(lambda name, arguments: []) + runtime = self.make_runtime(lambda name, arguments, *_: []) result = runtime.execute_public_tool( "appwrite_search_tools", @@ -125,7 +187,7 @@ def test_search_tools_scores_get_queries_against_get_tools(self): self.assertIn("functions_get", result[0].text) def test_call_tool_requires_confirm_write(self): - runtime = self.make_runtime(lambda name, arguments: []) + runtime = self.make_runtime(lambda name, arguments, *_: []) with self.assertRaisesRegex(RuntimeError, "confirm_write=true"): runtime.execute_public_tool( @@ -136,7 +198,7 @@ def test_call_tool_requires_confirm_write(self): def test_call_tool_merges_top_level_arguments(self): captured = {} - def executor(name, arguments): + def executor(name, arguments, *_): captured["name"] = name captured["arguments"] = arguments return [types.TextContent(type="text", text="ok")] @@ -157,7 +219,9 @@ def executor(name, arguments): def test_large_result_is_stored_as_resource(self): runtime = self.make_runtime( - lambda name, arguments: [types.TextContent(type="text", text="x" * 1200)] + lambda name, arguments, *_: [ + types.TextContent(type="text", text="x" * 1200) + ] ) result = runtime.execute_public_tool( diff --git a/tests/unit/test_server.py b/tests/unit/test_server.py index a375995..f9cab8f 100644 --- a/tests/unit/test_server.py +++ b/tests/unit/test_server.py @@ -67,7 +67,7 @@ def test_build_client_loads_dotenv_from_current_working_directory(self): os.chdir(previous_cwd) self.assertEqual(client._endpoint, "https://example.test/v1") - self.assertEqual(client._global_headers["x-appwrite-project"], "test-project") + self.assertEqual(client.get_config("project"), "test-project") self.assertEqual(client._global_headers["x-appwrite-key"], "test-key") def test_coerce_enum_returns_raw_value_string(self): @@ -210,20 +210,14 @@ def test_register_services_returns_fresh_manager(self): self.assertIsNot(manager_a, manager_b) self.assertEqual(len(manager_a.get_all_tools()), len(manager_b.get_all_tools())) + from mcp_server_appwrite.server import SERVICE_CLASSES + self.assertEqual( {service.service_name for service in manager_a.services}, - { - "avatars", - "functions", - "locale", - "messaging", - "sites", - "storage", - "tables_db", - "teams", - "users", - }, + set(SERVICE_CLASSES), ) + # Every advertised service is registered (the SDK currently ships 14). + self.assertGreaterEqual(len(manager_a.services), 14) def test_validate_services_raises_with_service_name(self): class FailingSdkService: diff --git a/uv.lock b/uv.lock index 0ca2eaa..47e8404 100644 --- a/uv.lock +++ b/uv.lock @@ -6,6 +6,15 @@ resolution-markers = [ "python_full_version < '3.14'", ] +[[package]] +name = "annotated-doc" +version = "0.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/ba/046ceea27344560984e26a590f90bc7f4a75b06701f653222458922b558c/annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4", size = 7288, upload-time = "2025-11-10T22:07:42.062Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303, upload-time = "2025-11-10T22:07:40.673Z" }, +] + [[package]] name = "annotated-types" version = "0.7.0" @@ -31,14 +40,15 @@ wheels = [ [[package]] name = "appwrite" -version = "13.4.1" +version = "21.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ + { name = "pydantic" }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/30/bc/c8e9671e00fc2feda1acc5b457e8f0277e5d44dc636d0c0e990167b470d6/appwrite-13.4.1.tar.gz", hash = "sha256:f6725133b57837f962970c6f665713ca430e2c12b5cd231f355612251cf168fd", size = 62458, upload-time = "2025-10-09T11:07:05.131Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/6f/6f8c0072434fe07850bc6b1c688ff1c7f7402a1c97a339f7d924c7f8cef6/appwrite-21.0.0.tar.gz", hash = "sha256:ef206093dfdcdb3ad10b898a19ad122e6b4002f2c3d34aa455e4c5f1eca502de", size = 172997, upload-time = "2026-06-22T13:19:37.382Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ae/7a/990c43b44bc5c0abe6d4ccf2dd46232e17133043e77ebed99a43a0f0be4c/appwrite-13.4.1-py3-none-any.whl", hash = "sha256:0a6abca5120a9fa1ca0423a9e476c4e7edbb43c524970d5b8eff36aa66a92eec", size = 78976, upload-time = "2025-10-09T11:07:03.819Z" }, + { url = "https://files.pythonhosted.org/packages/35/d2/c823fa22772ee8db9e9a4d3ca9cb1e01bab8a62417e53f54da295178a46f/appwrite-21.0.0-py3-none-any.whl", hash = "sha256:582c5268f88794f378144c49521edbb414a727dfbf6a1ab647effa7cf7f2c0c8", size = 307388, upload-time = "2026-06-22T13:19:36.075Z" }, ] [[package]] @@ -84,6 +94,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/42/b9/f8d6fa329ab25128b7e98fd83a3cb34d9db5b059a9847eddb840a0af45dd/argon2_cffi_bindings-25.1.0-cp39-abi3-win_arm64.whl", hash = "sha256:b0fdbcf513833809c882823f98dc2f931cf659d9a1429616ac3adebb49f5db94", size = 27149, upload-time = "2025-07-30T10:01:59.329Z" }, ] +[[package]] +name = "attrs" +version = "26.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/8e/82a0fe20a541c03148528be8cac2408564a6c9a0cc7e9171802bc1d26985/attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32", size = 952055, upload-time = "2026-03-19T14:22:25.026Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" }, +] + [[package]] name = "bcrypt" version = "5.0.0" @@ -304,6 +323,65 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] +[[package]] +name = "cryptography" +version = "49.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1f/99/d1c90d6041656cc6ee229dc99cd67fd0cd5aec3c5f7d72fffc27cc750054/cryptography-49.0.0.tar.gz", hash = "sha256:f89660a348f4f78a92366240a61404e337586ef7f5909a2fef59ca88ef505493", size = 854345, upload-time = "2026-06-12T20:02:30.512Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/22/adf66990e63584a68dfb50c24f48a125c07b1699899381c8151e63ed458c/cryptography-49.0.0-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:966fe0e9c67490071f14c0d2b1cb2dfb3023c5ce39457343931415f08382f2db", size = 4032100, upload-time = "2026-06-12T20:02:32.143Z" }, + { url = "https://files.pythonhosted.org/packages/09/41/3797cfaf69cae04a13ee78ebd83f0678d9c02b4779d21ce24445326f1a69/cryptography-49.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:36d1709f992593689b45bda411498d62c6e365f2ca00b84657d4dadd24de16db", size = 4692978, upload-time = "2026-06-12T20:01:21.305Z" }, + { url = "https://files.pythonhosted.org/packages/e6/8b/43011f7ebe515a8aa20d61f290a326cd890c2e738e16e59eaff8d9c3a412/cryptography-49.0.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0e959b578856a3924bc0cbb710fc12c387b9412a951389f3ca61704a9e25f325", size = 4716422, upload-time = "2026-06-12T20:01:48.566Z" }, + { url = "https://files.pythonhosted.org/packages/4a/91/01ce7303a4579e6d3a6abef01bd322848e9ea7a219adcabc5048b9033571/cryptography-49.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:53ecee2e23f7169b6117e99fc8a944e5e50f79e69758a83b52a00cb98ab2b2d2", size = 4700503, upload-time = "2026-06-12T20:02:47.091Z" }, + { url = "https://files.pythonhosted.org/packages/62/99/a2c95cf8293f07491e9e27c20cc4dcd18176d944e674679adeb1d0173fd6/cryptography-49.0.0-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:2eda353d8a27bcbcaa4cbed18994a74ab4d19a2ca897db188ea269ab9b71419b", size = 5309779, upload-time = "2026-06-12T20:02:08.987Z" }, + { url = "https://files.pythonhosted.org/packages/20/2c/0622f20ff02b2ef32558733443805dc82fd4c275be01b2d19d14676f3a1b/cryptography-49.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2afe9051da7ae7bd5905da5a949280c7d2bb75682e188f650a9d0f2756b834c6", size = 4749683, upload-time = "2026-06-12T20:02:03.335Z" }, + { url = "https://files.pythonhosted.org/packages/a3/5b/c5246635d5fd3b64e0d45ae10e99fd32fe9676a79915ccfe5a61ba9af1a5/cryptography-49.0.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:0b82e28ee398a386f0807bba7884d30f25218855690f45115831bcce5d90822c", size = 4337874, upload-time = "2026-06-12T20:02:54.323Z" }, + { url = "https://files.pythonhosted.org/packages/6d/88/05563c7fe2e914e87d1a536d06fe83e66b4e1d95cb593e05aea375531da8/cryptography-49.0.0-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:ccac2bfebc306b862133e3bb71f3f6ee8bb525240089b2d952e4144b3a6d5da7", size = 4700283, upload-time = "2026-06-12T20:01:34.822Z" }, + { url = "https://files.pythonhosted.org/packages/c4/b6/d7696e4e890d6ae1469935164c9e5215c557671cb78d6e3f458ccceaa632/cryptography-49.0.0-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:d0527ce944105f257f605a827d6ebead966c752038b6e8656abb9c5edee6fc68", size = 5265844, upload-time = "2026-06-12T20:01:24.09Z" }, + { url = "https://files.pythonhosted.org/packages/a9/3c/f3ad17eecc1a57b0ba236dc01f90e783c51f4a2f35f64777cc4f47a184b2/cryptography-49.0.0-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:cbc77da8c523d5abd028635ba850a6966fcee2c82e2bf65a41d1d8afe0f98be9", size = 4749290, upload-time = "2026-06-12T20:01:30.848Z" }, + { url = "https://files.pythonhosted.org/packages/4f/01/339573cf1023163a400b0b5d16f6d507de413b9f60be6fd1b77feeaf6737/cryptography-49.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b87e65d263b3e5d3bb92a57e2a6638e2f31110fa7aa890c7b2dbba42248d0a3f", size = 4834612, upload-time = "2026-06-12T20:01:29.246Z" }, + { url = "https://files.pythonhosted.org/packages/71/fd/577302e213a1be9468f92d1afef66fcf1ef83d516819d9992ca547f592bd/cryptography-49.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:66ec79c3904820572d7e987abdf304281f141d37ad9a489b8e97066e7b9b6459", size = 4980804, upload-time = "2026-06-12T20:01:42.853Z" }, + { url = "https://files.pythonhosted.org/packages/1f/09/f42b1d190c5ba75f72062a387f8030d1d75f6ab035788f1d9c4b01de6525/cryptography-49.0.0-cp311-abi3-win_amd64.whl", hash = "sha256:e5dfc1e64de5677cec922ffa8da89c546d0415bf6efdf081842e5d44c84e1f0e", size = 3810026, upload-time = "2026-06-12T20:02:39.262Z" }, + { url = "https://files.pythonhosted.org/packages/ec/9e/db72b3ae7fc9cfad53e630e56c6ae83b9b6ff0bf3718ffb8012d20b3aabf/cryptography-49.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:73a205dce83953d131a4aa1e0fd917a2fd1c5b1eef251e9d7152efefcbf5caf7", size = 4013892, upload-time = "2026-06-12T20:02:10.735Z" }, + { url = "https://files.pythonhosted.org/packages/86/12/c48a424f38db03027be9f7ed5c7dc5de9933dbee992865f98b13727a009d/cryptography-49.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:196ecd6a36e4e9aa10270393bb98d8df88fccee0bf1e5128b91ae4eb4375896d", size = 4678835, upload-time = "2026-06-12T20:02:48.743Z" }, + { url = "https://files.pythonhosted.org/packages/68/28/8a3ad4653662c93fc44dc4e5d8fd374c25c42e07b34bbfbadf49cf57a5a8/cryptography-49.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7abcee80084cda3f7691f3eb1ce480d8df49cec637b429aa35986c1de71738aa", size = 4697239, upload-time = "2026-06-12T20:02:56.03Z" }, + { url = "https://files.pythonhosted.org/packages/a8/b2/2193fc74f81aee4f9b62733133b73b5176718932ed8f2e4b03fa040480a6/cryptography-49.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:4ae387c9cb68ea569ca17e490d66d8142b81c3cc814bf179974b7d146e490bbb", size = 4685593, upload-time = "2026-06-12T20:02:50.666Z" }, + { url = "https://files.pythonhosted.org/packages/47/f1/1d3eaa243bfc5de4a187b22aa8c048b3e4980bfbe830ac46e6bac2e66947/cryptography-49.0.0-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:f37d847238971164fdbc68ade6f6574aecc9c0af714190e2083429ff68f4ce9d", size = 5289961, upload-time = "2026-06-12T20:01:46.468Z" }, + { url = "https://files.pythonhosted.org/packages/58/39/2d51306721330c486495853eda1c567880ff036de15a14c4b74f399934af/cryptography-49.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:c2bc30226390d60ea19d9f82b19db005fe0452154a23c1c410c12ea801e43561", size = 4731145, upload-time = "2026-06-12T20:02:16.832Z" }, + { url = "https://files.pythonhosted.org/packages/17/50/983e838c7fd0d87fd8c969bcdd328edaf5f756e38df5281637424c155873/cryptography-49.0.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:07cab27cc7b7e0fd28e5e26bb9eeedde5c135c868b46de4a27845abe94af6122", size = 4321719, upload-time = "2026-06-12T20:02:52.611Z" }, + { url = "https://files.pythonhosted.org/packages/a7/f5/8f571d7e27c55bce9f76f026143bcb1e040a4233149ecca0bea5fa5dd5f7/cryptography-49.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:b20133d204d2bb56ba047642199603876c872026ca53e79c35b83772ab2cc505", size = 4685209, upload-time = "2026-06-12T20:02:07.282Z" }, + { url = "https://files.pythonhosted.org/packages/e7/84/0e27016a6fc5a0886f797018b26aa42f40c09a82332bff77822a451deaaa/cryptography-49.0.0-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:b970c6da94d5bb18629db453d14f2a1300f6bf59b61e9b82377931ef95504866", size = 5246285, upload-time = "2026-06-12T20:01:32.439Z" }, + { url = "https://files.pythonhosted.org/packages/11/2d/5e1fb307cb5931881516b464c98774b3f2c36b5d4bb9a2830253cf553cad/cryptography-49.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:d8ecde755e2e91bf773fc94e8c9d730cd7f2007004cb492263a794ec3899a1c8", size = 4730441, upload-time = "2026-06-12T20:02:01.469Z" }, + { url = "https://files.pythonhosted.org/packages/e4/c0/bff5a02ee731d207d6a1ed51732549d8c53d2bc8da1d10ec6f2844201d68/cryptography-49.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e3fb64c420688e5319ae25113a354015abbd8dffbfbc41781a1ea66fc7622ac3", size = 4815869, upload-time = "2026-06-12T20:01:36.574Z" }, + { url = "https://files.pythonhosted.org/packages/b9/26/814681d14248d95d73d5c3eea0c39a94eb8302df966f670a2c60de90974b/cryptography-49.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:32703d93296f5c1f4b53349ad3a250c2cae0fdecd3a3dd5d47e616d8d616af27", size = 4960948, upload-time = "2026-06-12T20:02:18.688Z" }, + { url = "https://files.pythonhosted.org/packages/4c/fe/93ecac273d3738939d023612ad12cca9a3740a5345d69fda04134c43fd96/cryptography-49.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:33cd0565932807baddb67b96dbee92f2c374b5c89dee09fd74079aeb8c8dba61", size = 3799153, upload-time = "2026-06-12T20:01:39.059Z" }, + { url = "https://files.pythonhosted.org/packages/19/2a/5bb823f5bedcf80718cea7fbc95ec5515cca3769633c4b01a32be7f30e7c/cryptography-49.0.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:ec5e529fb80935c94fe7b729f9972b50e351a0e6b50aa294fd5cabb109fcc29a", size = 4025947, upload-time = "2026-06-12T20:01:25.745Z" }, + { url = "https://files.pythonhosted.org/packages/3d/df/40577043ca124e17012f408ddddaeb213b856336ac82ddb3bc915f39e29f/cryptography-49.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f78ff2c9ed8dc2d036b0f4d640e22522213d047c1b14e61205a7e55c80a494d4", size = 4692429, upload-time = "2026-06-12T20:01:53.628Z" }, + { url = "https://files.pythonhosted.org/packages/2c/99/2d13299eb3dd27b02dcfaafcc91d6b5cb3329f7cbd6d8f51921acd566c1a/cryptography-49.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:35b151772baff2c74cba7fa290ceaff4c3b11c0c881eb93eb5dbc05a7cfbba18", size = 4700968, upload-time = "2026-06-12T20:02:45.383Z" }, + { url = "https://files.pythonhosted.org/packages/a5/4d/9c0cd02f95e2602dd5e563da149ee0830abef3537be8b34dc56281ebe27a/cryptography-49.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:0f21641cf4b30fca7aee061ced0ec7ad7b073518088b7c9969a297c0ae796c69", size = 4697758, upload-time = "2026-06-12T20:01:41.13Z" }, + { url = "https://files.pythonhosted.org/packages/24/01/186c825898477d77e2324d5360fefe622ff1d8d1963ec0554e2cada8ec77/cryptography-49.0.0-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:9e82dcc8e56052715fb18b2429e3bca4823b1629136a2084fc45a9a5cecb9b64", size = 5298863, upload-time = "2026-06-12T20:02:24.579Z" }, + { url = "https://files.pythonhosted.org/packages/b8/7b/62cbbab75d0659865bf0273790031544a0b16c8072d258f9428dcd8190dc/cryptography-49.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:6f2debedf9ca60cf1d5bd466475638af5130f89965605cd818484d19987d3a21", size = 4735983, upload-time = "2026-06-12T20:01:50.14Z" }, + { url = "https://files.pythonhosted.org/packages/6c/72/3e798c064bc39e471008075d0f9bc9daf77a80879c092e4a8e170c585ed4/cryptography-49.0.0-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:8c25ceb16df5b9435f3f6a9829204985b0e0cbee3b48aacd432c7d2c850b44d9", size = 4334173, upload-time = "2026-06-12T20:01:44.743Z" }, + { url = "https://files.pythonhosted.org/packages/f0/ee/6fca21d1ac73e06f8bef71940abfd4d2f6472b4bca284d770f32bd4086f6/cryptography-49.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:28d8b15e6275f12c8a207dc309dfa957903c927d08d0cc937ee3f63f200693cc", size = 4697298, upload-time = "2026-06-12T20:02:20.918Z" }, + { url = "https://files.pythonhosted.org/packages/67/d0/a5fcd3515f0bae49a7b6d0413cc1bdccdcc1fc0047037a0d480642cdc5d6/cryptography-49.0.0-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:6fc361c34fb6aac015ce19435876635e5c6d21db31998b0920f675f131e043b8", size = 5254338, upload-time = "2026-06-12T20:02:22.737Z" }, + { url = "https://files.pythonhosted.org/packages/a0/84/84fe36f19caf857d61cb7fc9c63035a47ffabd84ea12d1d393148efa3615/cryptography-49.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:2400ef9c9e2299a25614eb1dea3db54a69b1349efd043bfac9c67630d136df36", size = 4735650, upload-time = "2026-06-12T20:02:41.389Z" }, + { url = "https://files.pythonhosted.org/packages/6c/a0/db537264e234f7273a73ec020873d6d6b39dfd8a53db78b550ca8320440e/cryptography-49.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:67e1d20ad9ef3a563c59ef22e7a8a0b8210bd26604369ea4a30a7c66aefe504e", size = 4834820, upload-time = "2026-06-12T20:01:51.847Z" }, + { url = "https://files.pythonhosted.org/packages/93/77/8df9eb486495979bccecd1062e2eaf435250e84437040295b57d09048b0b/cryptography-49.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:42b0684e0e40cf26122427802486f6d93aea593612603a94fbf260c7eb1e9c1b", size = 4967968, upload-time = "2026-06-12T20:02:12.524Z" }, + { url = "https://files.pythonhosted.org/packages/c2/e6/f60198ea8d9dfa15fff9ed4ca02ce362f6eadd9ba757dcc50634c4257b63/cryptography-49.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:026ac7423e6fa66872d3bf889be5974507da3944f866f704fa200eadacd00001", size = 3785547, upload-time = "2026-06-12T20:02:26.847Z" }, +] + +[[package]] +name = "distro" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722, upload-time = "2023-12-24T09:54:32.31Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" }, +] + [[package]] name = "docstring-parser" version = "0.16" @@ -335,6 +413,42 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/87/f5/72347bc88306acb359581ac4d52f23c0ef445b57157adedb9aee0cd689d2/httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd", size = 78551, upload-time = "2024-11-15T12:30:45.782Z" }, ] +[[package]] +name = "httptools" +version = "0.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/e5/d471fcb0e14523fe1c3f4ba58ca52480e7bd70ad7109a3846bc75892f7fb/httptools-0.8.0.tar.gz", hash = "sha256:6b2a32f18d97e16e90827d7a819ffa8dbd8cc245fc4e1fa9d1095b54ef4bd999", size = 271342, upload-time = "2026-05-25T22:17:48.841Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/88/1d21a36da8f5cb0fa49eafd4b169eba5608d57e75bbcf61845cbc6243216/httptools-0.8.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:880490234c10f70a9830743097e8958d6e4b9f5a0ffc24515023afeef984054d", size = 208247, upload-time = "2026-05-25T22:17:07.843Z" }, + { url = "https://files.pythonhosted.org/packages/a5/42/cc4feea2945cb3051038f090c9b36bd5b8a9d7f5a894a506a8983e33fd1c/httptools-0.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5931891fb7b441b8a3853cf1b85c82c903defce084dd5f6771ca46e31bf862c5", size = 113064, upload-time = "2026-05-25T22:17:09.136Z" }, + { url = "https://files.pythonhosted.org/packages/e3/a6/febbb8b8db0f58b38e44ad6cb946e6a255ae49b55f2e8543408fb7501ccd/httptools-0.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b15fc622b0f869d19207c4089a501d9bcc63ca5e071ffdd2f03f922df882dcb2", size = 523851, upload-time = "2026-05-25T22:17:10.106Z" }, + { url = "https://files.pythonhosted.org/packages/b7/e4/f90a0df0b83beff265b7e3b65f2a4cefd95792d4be0ac3e16049f2acd3c2/httptools-0.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:425f83884fd6343828d8c565f046cb72b6d19063f6924093e11bcd8e1548cd09", size = 518842, upload-time = "2026-05-25T22:17:11.218Z" }, + { url = "https://files.pythonhosted.org/packages/9e/2d/0c9ac76dd2c893841fbf6498d6acec4f2442e1b7067f6e3e316a80e494e8/httptools-0.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ef7c3c97f4311c7be57e2986629df89d49cb434dbff78eafcd48c2bff986b15a", size = 501238, upload-time = "2026-05-25T22:17:12.728Z" }, + { url = "https://files.pythonhosted.org/packages/ca/42/906adc91ae3a5fa9c59c0a2f21c139725bd7e5b41ae6acd485cd14123ebf/httptools-0.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a1afd7c9fbff0d9f5d489c4ce2768bd09c84a46ddefc7161e6aa82ae35c85745", size = 509567, upload-time = "2026-05-25T22:17:13.842Z" }, + { url = "https://files.pythonhosted.org/packages/05/0b/4240efeb672751ee5b9b380cb0e3fdc050bc05f68adc7a8aefc4fcd9a69a/httptools-0.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:cd96f29b4bab1d42fa6e3d008711c75e0f79e94e06827330160e3a304227f150", size = 90918, upload-time = "2026-05-25T22:17:15.155Z" }, + { url = "https://files.pythonhosted.org/packages/5e/e5/8cfcabc5546e8022f168be28bcdaa128a240a0befdd03b59d558b4f18bd6/httptools-0.8.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:614ceea8ea606848bece2338ac03b3ce5324bcb4be8dc7d377ed708012fa4db8", size = 205148, upload-time = "2026-05-25T22:17:16.333Z" }, + { url = "https://files.pythonhosted.org/packages/2a/0e/0fb14848c19a686c8062ff9067c1a48793e3224b47bc5b201535b6036fce/httptools-0.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2d689918c15a013c65ef52d9fd495d766893ab831a2c8d89f2ac5940a5df847c", size = 111368, upload-time = "2026-05-25T22:17:17.586Z" }, + { url = "https://files.pythonhosted.org/packages/2e/1b/46f1cecf06b9bbde8e4b8c88034ac7908989e5ff7a3a388ef38392949c1f/httptools-0.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:eb3028cca2fc0a6d720e52ef61d8ebb62fcbfeb1de56874546d858d3f25a26b7", size = 486447, upload-time = "2026-05-25T22:17:18.564Z" }, + { url = "https://files.pythonhosted.org/packages/77/00/258bfc0837221f81d9725c45f9b948a6a6b2994a147a4fb66e85100c668f/httptools-0.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:88bdd940f2b5d487b4d032c6afa5489a7dc4694410d43de3c38c4fb3af0dc45d", size = 482448, upload-time = "2026-05-25T22:17:19.912Z" }, + { url = "https://files.pythonhosted.org/packages/04/ab/d1cef3b5523f4d272a70f42a776c3169a2dddfe3a54de4b2ce4a36341528/httptools-0.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6a43c9dd399758ccc0531acb0a3c4a6c299ee893ee9400e9c893b7bdcfae0681", size = 464460, upload-time = "2026-05-25T22:17:20.882Z" }, + { url = "https://files.pythonhosted.org/packages/ce/48/5d1d072442277bb2b3434e0e60690b8e8c23840ef7de8b6ea54040a536d3/httptools-0.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0770728beb05094c809b98e814edff5fef69d26ad7d21185f2f6d5884a0ba683", size = 471312, upload-time = "2026-05-25T22:17:22.085Z" }, + { url = "https://files.pythonhosted.org/packages/0d/66/b96623b27e51a68199ef4efdda0613cced9233fe3062ac74e50749c5ad37/httptools-0.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:7685df791fad561384bfb139e77fde27a1ffd93134e016f95a0db424ffbf77b1", size = 90117, upload-time = "2026-05-25T22:17:23.074Z" }, + { url = "https://files.pythonhosted.org/packages/1a/12/fa3fbf5f9517b273edea2dc982aa82a8c634091e67c590792b729017bc6f/httptools-0.8.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:de242a49b5d18e0a8776e654e9f6bf6d89f3875a5c35b425a0e7ce940feb3fd6", size = 206183, upload-time = "2026-05-25T22:17:24.004Z" }, + { url = "https://files.pythonhosted.org/packages/30/fc/5e7c4cb443370f2090a3aba0453a07384d29ff66b7435bb90e77e1037599/httptools-0.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:159e9ab5f701ccd42e555a12f1ad8ff69702910fc1c996cf2bb66e5fcb7a231b", size = 112079, upload-time = "2026-05-25T22:17:25.216Z" }, + { url = "https://files.pythonhosted.org/packages/ba/53/771bd891eb0f236f32145d6a1775777ec85745f3cc983a1f23d1a3b8ddfe/httptools-0.8.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c4a9f1707e4823d54dfec6c33fa3697d302aed536ed352a7ebb5a061ddb869d0", size = 481596, upload-time = "2026-05-25T22:17:26.186Z" }, + { url = "https://files.pythonhosted.org/packages/62/42/94e15bc68ce3d423243c45d7f1b0c7561f13844f97dc52ae23182fb65628/httptools-0.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d76ad7b951387e3632c8716a9bb03ac5b45c5f16119aa409db0459520887944e", size = 480865, upload-time = "2026-05-25T22:17:27.542Z" }, + { url = "https://files.pythonhosted.org/packages/1c/7c/fe2980fc03723272e30f135b62360b075f513dfe7cc73aef36c7f04012bd/httptools-0.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a3b7387147361c3fd47a0bde763c5c91b5b4cd4dc9989b8ece84ff436c99843b", size = 463189, upload-time = "2026-05-25T22:17:28.546Z" }, + { url = "https://files.pythonhosted.org/packages/15/1b/47fc5fff68acd1bfa20b4734059c9a06cadb88119dcd5258b5b0d21d91c8/httptools-0.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f256d6ce930c52ca1cb2a960b7da03548c454e7d28b06059ad41bfe789036ce0", size = 466610, upload-time = "2026-05-25T22:17:29.816Z" }, + { url = "https://files.pythonhosted.org/packages/60/bd/07b13c93ffd9bec9546e0d43f8e19378dd696dbd278511406bc07371ef1f/httptools-0.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:19d1ee275bb59ba2643ba9a3a1e51cc0c788caf2b8df506368e03f56fdd08527", size = 92705, upload-time = "2026-05-25T22:17:31.133Z" }, + { url = "https://files.pythonhosted.org/packages/fd/c4/121648f68ce066d7bd762d6b6d97e620847642d38d54f3d90ff11d947629/httptools-0.8.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:de1ed58a974e75d56560acc7e7fed01a454994429456f65209789992e41f2568", size = 215023, upload-time = "2026-05-25T22:17:32.401Z" }, + { url = "https://files.pythonhosted.org/packages/b9/b0/312a062ae741ae3e8baa8c8bf20be81b2e67337b259ab4349bebc7b6142e/httptools-0.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e93c227b595c6926c1acee96891dd9da4be338cfbe82e5cd3bb9d8dd7dc4ac0b", size = 117405, upload-time = "2026-05-25T22:17:33.742Z" }, + { url = "https://files.pythonhosted.org/packages/fc/37/fccd705f795386bb05bf413012fecff2a33e5aa8c2f069096de3e9fd8702/httptools-0.8.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2a021c3a8e65cc125390d72f59b968afca3bdcaff25bd67965e0a055a14946ca", size = 558497, upload-time = "2026-05-25T22:17:34.732Z" }, + { url = "https://files.pythonhosted.org/packages/bd/39/f172e8003576de35f5ba77ff417cf0e34429d35dc014deef15afa337a72c/httptools-0.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:48774d39cbb70e2b1f71f88852a3087ae1d3a1eb80482bb48c13067ab080c14f", size = 571585, upload-time = "2026-05-25T22:17:35.813Z" }, + { url = "https://files.pythonhosted.org/packages/3e/b9/f5564760af99f3dbbf3f9104dc00e5da27e96cf433c6bdcf77617f70bf3f/httptools-0.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:88eead8ec8680a9f146c655bc88445a325bd7921cfd8194c7337e9467282427d", size = 543297, upload-time = "2026-05-25T22:17:37.08Z" }, + { url = "https://files.pythonhosted.org/packages/99/67/8d9f2c313618e161b82f3873188e7196126da1d6e29688df40eb3997c77a/httptools-0.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2c032fa028f46871ec7e1fc59fc15e8023eab3e6bbe6ece786a1611719a5d081", size = 539535, upload-time = "2026-05-25T22:17:38.032Z" }, + { url = "https://files.pythonhosted.org/packages/48/63/b906c01e53f50d432c0defe43ce52764a111dc1bdd028bafbeb54dcfd008/httptools-0.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:384c17174464c8e873398b7af24f0b1f44d992c820328413951a625323155d77", size = 108209, upload-time = "2026-05-25T22:17:39.473Z" }, +] + [[package]] name = "httpx" version = "0.28.1" @@ -368,6 +482,105 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, ] +[[package]] +name = "jiter" +version = "0.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/66/b5/55f06bb281d92fb3cc86d14e1def2bd908bb77693183e7cb1f5a3c388b0c/jiter-0.15.0.tar.gz", hash = "sha256:4251acc80e2b7c9b7b8823456ea0fceeb0734dac2df7636d3c711b38476b5a76", size = 166640, upload-time = "2026-05-19T10:09:48.361Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/53/4f6bddbcde3c71e56d0aa1337ec95950f3d27dd4153e25aadf0feac71751/jiter-0.15.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:0e90a1c315a0226ec822d973817967f9223b7701546c8c2a7913e7ab0926294d", size = 308793, upload-time = "2026-05-19T10:07:35.25Z" }, + { url = "https://files.pythonhosted.org/packages/01/84/c01099b59a285a1ebba64ae93f62bfa036675340fd1b0045ae65890a0442/jiter-0.15.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8c9004af7c8d67cce7f1aae1026fb55607f4aa600710d08ede3a3ce4aeefe7e0", size = 309570, upload-time = "2026-05-19T10:07:36.919Z" }, + { url = "https://files.pythonhosted.org/packages/58/64/8fb7f9d45bb98190355454cd04dad8d8f27223d6bd52f83af07f637168a6/jiter-0.15.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c210f8b35dc6f30aafd4b4365ca89b9d1189f21ab49b8e68fa6322a847aef138", size = 336783, upload-time = "2026-05-19T10:07:38.694Z" }, + { url = "https://files.pythonhosted.org/packages/c3/b6/f5739011d009b3a30f6a53c5240979030ba29ae46a8c67e3a15759f7c37d/jiter-0.15.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f30bae8bc1c2d613e28e5af3e8cceb09b742f1c8a8a5f839fb67afaffc03b61", size = 363555, upload-time = "2026-05-19T10:07:40.832Z" }, + { url = "https://files.pythonhosted.org/packages/e5/12/98a9d9f766665e8a3b6252454e17cb0c464606a28cf2fa09399b003345fa/jiter-0.15.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c60e71b6d10cfc284c9bf36bd885e8d44c46f688ce50aa91b5edd90181dea687", size = 452255, upload-time = "2026-05-19T10:07:42.62Z" }, + { url = "https://files.pythonhosted.org/packages/e8/d5/60f972840f79c5e7544fce567c56f1e4e50468f996baba3e78d823dd62a6/jiter-0.15.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ab068bce62a45aa3e7367eceaffb5dde60b7eb853be8dece45132e3d0ff4879", size = 373559, upload-time = "2026-05-19T10:07:44.201Z" }, + { url = "https://files.pythonhosted.org/packages/ee/cf/d46ef1234ba335aabc2f013210db8e0821a22f5e644a2e9449df199ecc23/jiter-0.15.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa248c9eb220197d363f688818dac2fd4b2f0cd7d843ca7105d652034823427d", size = 346055, upload-time = "2026-05-19T10:07:46.005Z" }, + { url = "https://files.pythonhosted.org/packages/f0/63/4d2749d8d54d230bad9b3a6b0d00cc28c6ff6b2fdffc26a8ccf76cc5a974/jiter-0.15.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:2a77aadd57cac1682e4401a72724d2796d89a4ba129b1a5812aa94ee480826eb", size = 351406, upload-time = "2026-05-19T10:07:47.855Z" }, + { url = "https://files.pythonhosted.org/packages/d9/b9/9965b990035d8773328e0a8c8b457a87bf2b19f6c4126d9d99296be5d16a/jiter-0.15.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2ae901f3a55bfafdde31d289590fa25e3245735a2b1e8c7cc15871710a002871", size = 389357, upload-time = "2026-05-19T10:07:49.665Z" }, + { url = "https://files.pythonhosted.org/packages/2d/55/9ddf903deda1413e87fed792f416b7123daee5b8efbad6a202a7421c36a5/jiter-0.15.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:f0b271b462769543716f92d3a4f90527df6ef5ed05ee95ec4137f513e21e1b77", size = 517263, upload-time = "2026-05-19T10:07:51.537Z" }, + { url = "https://files.pythonhosted.org/packages/e8/76/a0c40ad064d3a20a4fde231e35d56e9a01ce82164278180e82d5daf85469/jiter-0.15.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2fb6a5d26af81fc0f00f9360a891e05cf755e149bba391c4d563adc54812973d", size = 548646, upload-time = "2026-05-19T10:07:53.196Z" }, + { url = "https://files.pythonhosted.org/packages/23/4f/eca9b954942916ba2f453891b8593ab444cd872396fe66a3936616f236f3/jiter-0.15.0-cp312-cp312-win32.whl", hash = "sha256:c2f6bb8b5216ab9e7873bc08b5d7bef2b8abbb578a3069bf1cd14a45d71d771d", size = 206427, upload-time = "2026-05-19T10:07:55.307Z" }, + { url = "https://files.pythonhosted.org/packages/95/bf/8ead82a87495149542748e828d153fd232a512a22c83b02c4815c1a9c7d8/jiter-0.15.0-cp312-cp312-win_amd64.whl", hash = "sha256:40b2c7e92c44a84d748d21706c68dc6ff8161d80b59c99d774721a0d2317d7c7", size = 197300, upload-time = "2026-05-19T10:07:56.651Z" }, + { url = "https://files.pythonhosted.org/packages/f4/e4/9b8a78fb2d894471bc344e37f1949bdd784bd914d031dba0ba3a40c71dd7/jiter-0.15.0-cp312-cp312-win_arm64.whl", hash = "sha256:cc0bc345cf2df9d1c00ac443f50d543c1ccfa8b0422cb85b1ab70d681c0b255b", size = 192702, upload-time = "2026-05-19T10:07:58.307Z" }, + { url = "https://files.pythonhosted.org/packages/e5/f4/f708c900ecee41b2025ef8413d5351e5649eb2125c506f6720cc69b06f5c/jiter-0.15.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1c11465f97e2abf45a014b83b730222f8f1c5335e802c7055a67d50de6f1f4e3", size = 307829, upload-time = "2026-05-19T10:07:59.704Z" }, + { url = "https://files.pythonhosted.org/packages/86/59/db537c0949e83668c38481d426b9f2fd5ab758c4ee53a811dd0a510626a0/jiter-0.15.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d1e7b1776f0797956c509e123d0952d10d293a9492dea9f288ab9570ec01d1a5", size = 308445, upload-time = "2026-05-19T10:08:01.184Z" }, + { url = "https://files.pythonhosted.org/packages/37/38/ea0e13b18c30ef951da0d47d39e7fa9edb82a93a62990ffbd7cea9b622d4/jiter-0.15.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:351a341c2105aa430b7047e30f1bf7975f6313b00165d3fc07be2edaf741f279", size = 336181, upload-time = "2026-05-19T10:08:02.688Z" }, + { url = "https://files.pythonhosted.org/packages/58/fc/2303901b16c4ba05865588990a420c0b4156270b44379c20931544a1d962/jiter-0.15.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4ab395feec8d249ec4044e228e98a7033f043426a265df439dc3698823f0a4e4", size = 362985, upload-time = "2026-05-19T10:08:04.394Z" }, + { url = "https://files.pythonhosted.org/packages/5b/6f/11bace093c52e7d4d26c8e606ccd7ae8c972189622469ec0d9e28161e28b/jiter-0.15.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a2a438005b6f22d0273413484d6094d7c2c5d10ec1b3a3bf128e0d1d3ba53258", size = 453292, upload-time = "2026-05-19T10:08:05.967Z" }, + { url = "https://files.pythonhosted.org/packages/22/db/987f2f086ca4d7a6582eb4ccd513f9b26b42d9e4243a087609a3137a8fc7/jiter-0.15.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f18f85e4218d1b40f000f42a92239a7a61a902cd42c65e6c360dbd17dcb20894", size = 373501, upload-time = "2026-05-19T10:08:07.857Z" }, + { url = "https://files.pythonhosted.org/packages/8f/7c/89fbcabb2739b7a5b8dc959a1b6c5761f6484f5fed3486854b3c789bb1de/jiter-0.15.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1aa62e277fc1cbd80e6deacae6f4d983b41b3d7728e0645c5d741a6149bba45", size = 344683, upload-time = "2026-05-19T10:08:09.431Z" }, + { url = "https://files.pythonhosted.org/packages/30/6f/6cca7692e7dddfec6d8d76c54dc97f2af2a41df4ac0674b999df1f09a5f3/jiter-0.15.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:6550fa135c7deb8ead6af49ed7ff648532ea8334a1447fe34a36315ef79c5c29", size = 350892, upload-time = "2026-05-19T10:08:11.352Z" }, + { url = "https://files.pythonhosted.org/packages/39/14/0338d6190cb8e6d22e677ab1d4eabd4117f67cca70c54cd04b82ff64e068/jiter-0.15.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:066f8f33f18b2419cd8213b2436fa7fbc9c499f315971cfa3ce1f9820c001b1b", size = 388723, upload-time = "2026-05-19T10:08:12.912Z" }, + { url = "https://files.pythonhosted.org/packages/90/31/cc19f4a1bdb6afb09ce6a2f2615aa8d44d994eba0d8e6105ed1af920e736/jiter-0.15.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:75e8a04e91432dde9f1838373cf93d23726c79d3e908d319acf0e796f85592e7", size = 516648, upload-time = "2026-05-19T10:08:14.808Z" }, + { url = "https://files.pythonhosted.org/packages/49/9f/833c541512cd091b63c10c0381973dfe11bc7a503a818c16384417e0c81e/jiter-0.15.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:a97261f1fccb8e50ecd2890a96e46efdc3f57c80a197324c6777827231eca712", size = 547382, upload-time = "2026-05-19T10:08:16.927Z" }, + { url = "https://files.pythonhosted.org/packages/d2/11/e7b70e91f90bc4477e8eee9e8a5f7cf3cb41b4525d6394dc98a714eb8f7f/jiter-0.15.0-cp313-cp313-win32.whl", hash = "sha256:c77496cb10bd7549690fbbab3e5ec05857b83e49276f4a9423a766ddd2afcd4c", size = 205845, upload-time = "2026-05-19T10:08:18.401Z" }, + { url = "https://files.pythonhosted.org/packages/4b/23/5c20d9ad6f02c493e4023e5d2d09e1c1f15fe2753c9102c544aff068a88e/jiter-0.15.0-cp313-cp313-win_amd64.whl", hash = "sha256:b15741f501469009ae0ae90b7147958a664a7dede40aa7ff174a8a4645f546d0", size = 196842, upload-time = "2026-05-19T10:08:20.131Z" }, + { url = "https://files.pythonhosted.org/packages/6b/11/1eb400ef248e8c925fd883fbe325daf5e42cd1b0d308539dd332bd4f7ffc/jiter-0.15.0-cp313-cp313-win_arm64.whl", hash = "sha256:5d6a60072b44c3c2b797a7ddcbcbbf2b34ea3cfd4721580fbfd2a09d9d9b84ba", size = 192212, upload-time = "2026-05-19T10:08:21.807Z" }, + { url = "https://files.pythonhosted.org/packages/8a/60/2fd8d7c79da8acf9b7b277c7616847773779356b92acfc9bb158452174da/jiter-0.15.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:ef1fd24d9413f6209e00d3d5a453e67acfe004a25cc6c8e8484faed4311ab9e8", size = 315065, upload-time = "2026-05-19T10:08:23.218Z" }, + { url = "https://files.pythonhosted.org/packages/46/f4/008fb7d65e8ac2abf00811651a661e025c4ba80bbc6f378450384ddd3aed/jiter-0.15.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:144f8e72cb53dab146347b91cceac01f5481237f2b93b4a339a1ee8f8878b67c", size = 339444, upload-time = "2026-05-19T10:08:24.701Z" }, + { url = "https://files.pythonhosted.org/packages/00/55/90b0c7b9c6896c0f2a591dd36d36b71d22e09674bfef178fa03ba3f81499/jiter-0.15.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:553fcac2ef2cb990877f9fc0833b8b629a3e6a5670b6b5fd58219b41a653ddc4", size = 347779, upload-time = "2026-05-19T10:08:26.408Z" }, + { url = "https://files.pythonhosted.org/packages/51/6b/69666cec5000fd57734c118437394516c749ae8dbeea9fb66d6fef9c4775/jiter-0.15.0-cp313-cp313t-win_amd64.whl", hash = "sha256:774f93f65031856bf14ad9f59bdcab8b8cad501e5ceabd51ba3525f76937a25b", size = 200395, upload-time = "2026-05-19T10:08:28.055Z" }, + { url = "https://files.pythonhosted.org/packages/39/04/a6aa62cd27e8149b0d28df5561f10f6cceaf7935a9ccf3f1c5a05f9a0cd8/jiter-0.15.0-cp313-cp313t-win_arm64.whl", hash = "sha256:f1e1754960f38ec40613a07e5e372df67acb3b890fb383b6fb3de3e49ddbf3c7", size = 190516, upload-time = "2026-05-19T10:08:29.35Z" }, + { url = "https://files.pythonhosted.org/packages/eb/d2/079f350ebf7859d081de30aa890f9e3be68516f754f3ba32366ffff4dcee/jiter-0.15.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:ac0d9ddea4350974be7a221fc25895f251a8fee748c889bdced2141c0fec1a49", size = 308884, upload-time = "2026-05-19T10:08:31.667Z" }, + { url = "https://files.pythonhosted.org/packages/04/4e/a2c30a7f69b48c03b20935d647479106fe932f6e63f75faf53937197e05d/jiter-0.15.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:01a8222cf05ab1128e239421156c207949808acaaea2bdfd33130ae666786e86", size = 310028, upload-time = "2026-05-19T10:08:33.304Z" }, + { url = "https://files.pythonhosted.org/packages/40/90/2e7cdfd3cf8ca967be38c48f5cf474d79f089efaf559a40f15984a77ae69/jiter-0.15.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:182226cbc930c9fab81bc2e41a4da672f89539906dadb05e75670ac07b94f71f", size = 337485, upload-time = "2026-05-19T10:08:35.259Z" }, + { url = "https://files.pythonhosted.org/packages/9b/11/15a1aa28b120b8ee5b4f1fb894c125046225f09847738bd64233d3b84883/jiter-0.15.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:71683c38c825452999b5717fcae07ea708e8c93003e808be4319c1b02e3d176e", size = 364223, upload-time = "2026-05-19T10:08:36.694Z" }, + { url = "https://files.pythonhosted.org/packages/b7/25/f442e8af5f3d0dcf47b39e83a0efd9ee45ea946aa6d04625dc3181eae3b6/jiter-0.15.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:30f2218e6a9e5c18bc10fe6d41ac189c442c88eacf11bad9f28ef95a9bef00e6", size = 456387, upload-time = "2026-05-19T10:08:38.143Z" }, + { url = "https://files.pythonhosted.org/packages/da/f4/37f2d2c9f64f49af7da652ed7532bb5a2372e588e6927c3fdd76f911db65/jiter-0.15.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5157de9f76eb4bc5ea74a1219366a25f945ad305641d74e04f59c54087091aa9", size = 374461, upload-time = "2026-05-19T10:08:39.869Z" }, + { url = "https://files.pythonhosted.org/packages/60/28/edcfbbbf0cb15436f36664a8908a0df47ab9006298d4cd937dc08ea932d6/jiter-0.15.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90c5db5527c221249a876160663ab891ace358c17f7b9c93ec1478b7f0550e5c", size = 345924, upload-time = "2026-05-19T10:08:41.668Z" }, + { url = "https://files.pythonhosted.org/packages/47/13/89fba6398dab7f202b7278c4b4aac122399d2c0183971c4a57a3b7088df5/jiter-0.15.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:3e4540b8e74e4268811ac05db226a6a128ff572e7e0ce3f1163b693cadb184cd", size = 352283, upload-time = "2026-05-19T10:08:43.091Z" }, + { url = "https://files.pythonhosted.org/packages/1b/da/0f6af8cef2c565a1ab44d970f268c43ccaa72707386ea6388e6fe2b6cd26/jiter-0.15.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:62ebd14e47e9aed9df4472afcb2663668ce4d74891cd54f86bf6e44029d6dc89", size = 389985, upload-time = "2026-05-19T10:08:44.915Z" }, + { url = "https://files.pythonhosted.org/packages/a1/ec/b9cb7d6d29e24ee14910266157d2a279d7a8f60ee0df7fa840882976ba64/jiter-0.15.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0be6f5ad41a809f303f416d17cec92a7a725902fb9b4f3de3d19362ac0ef8554", size = 517695, upload-time = "2026-05-19T10:08:46.486Z" }, + { url = "https://files.pythonhosted.org/packages/64/5e/6d1bda880723aae0ad86b4b763f044362448efe31e3e819635d41cb03451/jiter-0.15.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:813dfbb17d65328bf86e5f0905dd277ba2265d3ca20556e86c0c7035b7182e5a", size = 548868, upload-time = "2026-05-19T10:08:48.026Z" }, + { url = "https://files.pythonhosted.org/packages/0c/72/7de501cf38dcacaf35098796f3a50e0f2e338baba18a58946c618544b809/jiter-0.15.0-cp314-cp314-win32.whl", hash = "sha256:50e51156192722a9c58db112837d3f8ef96fb3c5ecc14e95f409134b08b158ec", size = 206380, upload-time = "2026-05-19T10:08:49.738Z" }, + { url = "https://files.pythonhosted.org/packages/1e/a9/e19addf4b0c1bdce52c6da12351e6bc42c340c45e7c09e2158e46d293ccc/jiter-0.15.0-cp314-cp314-win_amd64.whl", hash = "sha256:30ce1a5d16b5641dc935d50ef775af6a0871e3d14ab05d6fc54dff371b78e558", size = 197687, upload-time = "2026-05-19T10:08:51.088Z" }, + { url = "https://files.pythonhosted.org/packages/f2/c9/776b1db01db25fc6c1d58d1979a37b0a9fe787e5f5b1d062d2eaacb77923/jiter-0.15.0-cp314-cp314-win_arm64.whl", hash = "sha256:510c8b3c17a0ed9ac69850c0438dada3c9b82d9c4d589fcb62002a5a9cf3a866", size = 192571, upload-time = "2026-05-19T10:08:52.451Z" }, + { url = "https://files.pythonhosted.org/packages/a0/f6/45bb4670bacf300fd2c7abadbfb3af376e5f1b6ae75fd9bc069891d15870/jiter-0.15.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7553333dd0930c104a5a0db8df72bf7219fe663d731383b576bb6ed6351c984d", size = 317151, upload-time = "2026-05-19T10:08:53.867Z" }, + { url = "https://files.pythonhosted.org/packages/d7/68/ed635ad5acd7b73e454283083bbb7c8205ad10e88b0d9d7d793b09fe8226/jiter-0.15.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2143ab06181d2b029eedcb6af3cebe95f11bbac62441781860f98ee9330a6a6", size = 341243, upload-time = "2026-05-19T10:08:55.383Z" }, + { url = "https://files.pythonhosted.org/packages/5d/db/3ff4176b817b8ea33879e71e13d8bc2b0d481a7ed3fe9e080f333d415c16/jiter-0.15.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6eac374c5c975709b69c10f09afd199df74150172156ad10c8d4fd785b7da995", size = 363629, upload-time = "2026-05-19T10:08:56.928Z" }, + { url = "https://files.pythonhosted.org/packages/ab/24/5f8270e0ba9c883582f96f722f8a0b58015c7ce1f8c6d4571cf394e99b6b/jiter-0.15.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b3b3b775e33d3bfaec9899edc526ae97b0da0bf9d071a46124ba419149a414f8", size = 456198, upload-time = "2026-05-19T10:08:58.618Z" }, + { url = "https://files.pythonhosted.org/packages/45/5b/76fc02b0b5c54c3d18c60653156e2f76fde1816f9b4722db68d6ee2c897e/jiter-0.15.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eda3071db3346334beae1360b46da4606da57bf3528c167b3c38533afaf9f2c5", size = 373710, upload-time = "2026-05-19T10:09:00.151Z" }, + { url = "https://files.pythonhosted.org/packages/c4/52/4310821b0ea9277994d3e1f49fc6a4b34e4800caebacb2c0af81da59a454/jiter-0.15.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c6694a173ecabc12eb60efbc0b474464ead1951ff65cd8b1e72100715c64512b", size = 349901, upload-time = "2026-05-19T10:09:01.621Z" }, + { url = "https://files.pythonhosted.org/packages/93/fe/67648c35b3594fba8854ac64cc8a826d8bcd18324bbdb53d77697c60b6ef/jiter-0.15.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:a254e10b593624d230c365b6d616b22ca0ad65e63a16e6631c2b3466022e6ba8", size = 352438, upload-time = "2026-05-19T10:09:03.216Z" }, + { url = "https://files.pythonhosted.org/packages/cb/28/0a1879d07ad6b3e025a2750027363452ced93c2d16d1c9d4b153ffd51c91/jiter-0.15.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d8d2955167274e15d79a7a020afdd9b39c990eb80b2d89fca695d92dcfdd38ec", size = 388152, upload-time = "2026-05-19T10:09:04.741Z" }, + { url = "https://files.pythonhosted.org/packages/c1/78/46c6f6b56ba85c90021f4afd72ed42f691f8f84daacb5fe27277070e3858/jiter-0.15.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:acf4ee4d1fc55917239fe72972fb292dd773055d05eb040d36f4326e02cc2c0e", size = 517707, upload-time = "2026-05-19T10:09:06.231Z" }, + { url = "https://files.pythonhosted.org/packages/ca/cb/720662d4c88fcad606e826fef5424365527ba43ce4868a479aed8f8c507e/jiter-0.15.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:e7196e56f1cd69af1dbb07dff02dcfb260a50b45a82d409d92a06fedb32473b5", size = 548241, upload-time = "2026-05-19T10:09:08.093Z" }, + { url = "https://files.pythonhosted.org/packages/60/e3/935b8034fd143f21125c87d51404a9e0e1449186a494405721ff5d1d695e/jiter-0.15.0-cp314-cp314t-win32.whl", hash = "sha256:7f6163c0f10b055245f814dcc59f4818da60dfe72f3e72ab89fc24b6bd5e9c52", size = 207950, upload-time = "2026-05-19T10:09:09.616Z" }, + { url = "https://files.pythonhosted.org/packages/93/59/984fd9ece895953dad3e0880a650e766f5a2da2c5514f0eafdaaabbeb5f9/jiter-0.15.0-cp314-cp314t-win_amd64.whl", hash = "sha256:980c256edb05b78a111b99c4de3b1d32e31634b867fd1fc2cf726e7b7bba9854", size = 200055, upload-time = "2026-05-19T10:09:11.367Z" }, + { url = "https://files.pythonhosted.org/packages/0e/a4/cf8d779feb133a27a2e3bc833bccb9e13aa332cdf820497ebf72c10ce8c3/jiter-0.15.0-cp314-cp314t-win_arm64.whl", hash = "sha256:66b1880df2d01e206e8339769d1c7c1753bcb653efd6289e203f6f24ebada0c0", size = 191244, upload-time = "2026-05-19T10:09:12.74Z" }, + { url = "https://files.pythonhosted.org/packages/73/38/505941b2b092fd5bbbd60a52a880db1173f1690ae6751bed3af1c9ddcb4e/jiter-0.15.0-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:631f13a3d04e97d4e083993b10f4b99530e3a10d953e2eb5e196b7dc7f812ce0", size = 303769, upload-time = "2026-05-19T10:09:42.203Z" }, + { url = "https://files.pythonhosted.org/packages/e7/95/a06692b29e77473f286e1ec1f426d3ca44d7b5843be8ad21d7a5f3fcdcc0/jiter-0.15.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:b6c0ffae686c39bf3737be60793783267628783ea42545632c10b291105aee45", size = 305128, upload-time = "2026-05-19T10:09:43.657Z" }, + { url = "https://files.pythonhosted.org/packages/23/85/7270d7ad41d6061a25b950c6bf91d638bd9aacb113200a8c8d57a055fd67/jiter-0.15.0-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d54fb5b31dea401a41af3f8a7d2512e9b6a6a005491e6166c7e4ffab9639a9c", size = 340459, upload-time = "2026-05-19T10:09:45.452Z" }, + { url = "https://files.pythonhosted.org/packages/c8/8d/302cb2057b7513327b4d575cff6b1d066ee6431a5357fc3f8867cd684406/jiter-0.15.0-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54d5d6090cdc1b7c9e780dfb04949a990adb1e301a2fc0bbcee7de4638d33f9a", size = 344469, upload-time = "2026-05-19T10:09:46.864Z" }, +] + +[[package]] +name = "jsonschema" +version = "4.26.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "jsonschema-specifications" }, + { name = "referencing" }, + { name = "rpds-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b3/fc/e067678238fa451312d4c62bf6e6cf5ec56375422aee02f9cb5f909b3047/jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326", size = 366583, upload-time = "2026-01-07T13:41:07.246Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce", size = 90630, upload-time = "2026-01-07T13:41:05.306Z" }, +] + +[[package]] +name = "jsonschema-specifications" +version = "2025.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "referencing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" }, +] + [[package]] name = "markdown-it-py" version = "3.0.0" @@ -382,21 +595,28 @@ wheels = [ [[package]] name = "mcp" -version = "1.3.0" +version = "1.28.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, { name = "httpx" }, { name = "httpx-sse" }, + { name = "jsonschema" }, { name = "pydantic" }, { name = "pydantic-settings" }, + { name = "pyjwt", extra = ["crypto"] }, + { name = "python-multipart" }, + { name = "pywin32", marker = "sys_platform == 'win32'" }, { name = "sse-starlette" }, - { name = "starlette" }, - { name = "uvicorn" }, + { name = "starlette", version = "0.46.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.14'" }, + { name = "starlette", version = "1.3.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.14'" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, + { name = "uvicorn", marker = "sys_platform != 'emscripten'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6b/b6/81e5f2490290351fc97bf46c24ff935128cb7d34d68e3987b522f26f7ada/mcp-1.3.0.tar.gz", hash = "sha256:f409ae4482ce9d53e7ac03f3f7808bcab735bdfc0fba937453782efb43882d45", size = 150235, upload-time = "2025-02-20T21:45:42.597Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c1/ee/94c6c50ffc5b5cf4737052275d11b57367f32d1a8516e31dcd60591b3916/mcp-1.28.0.tar.gz", hash = "sha256:559d3f9943674cafbe5744c5d3794f3237e8b47f9bbc58e20c0fad680d8487c2", size = 636040, upload-time = "2026-06-16T21:37:17.996Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d0/d2/a9e87b506b2094f5aa9becc1af5178842701b27217fa43877353da2577e3/mcp-1.3.0-py3-none-any.whl", hash = "sha256:2829d67ce339a249f803f22eba5e90385eafcac45c94b00cab6cef7e8f217211", size = 70672, upload-time = "2025-02-20T21:45:40.102Z" }, + { url = "https://files.pythonhosted.org/packages/2e/e1/4c1dc1fbb688641a712d34650c3d58bbbdcb314ddb75bc5817bbf33515a4/mcp-1.28.0-py3-none-any.whl", hash = "sha256:9c1e7cf3a9125557e418ecd4fed8e9adddce81b0dfdae4d6601d700f5beb71a4", size = 221959, upload-time = "2026-06-16T21:37:16.579Z" }, ] [package.optional-dependencies] @@ -407,13 +627,20 @@ cli = [ [[package]] name = "mcp-server-appwrite" -version = "0.4.1" +version = "0.7.0" source = { editable = "." } dependencies = [ { name = "appwrite" }, { name = "docstring-parser" }, + { name = "httpx" }, { name = "mcp", extra = ["cli"] }, + { name = "numpy" }, + { name = "openai" }, + { name = "pyjwt", extra = ["crypto"] }, { name = "python-dotenv" }, + { name = "starlette", version = "0.46.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.14'" }, + { name = "starlette", version = "1.3.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.14'" }, + { name = "uvicorn", extra = ["standard"] }, ] [package.optional-dependencies] @@ -427,23 +654,35 @@ integration = [ [package.dev-dependencies] dev = [ { name = "black" }, + { name = "pyyaml" }, + { name = "ruff" }, ] [package.metadata] requires-dist = [ - { name = "appwrite", specifier = ">=13.4.1,<16" }, + { name = "appwrite", specifier = ">=21.0.0,<22" }, { name = "argon2-cffi", marker = "extra == 'integration'", specifier = ">=23.1.0" }, { name = "bcrypt", marker = "extra == 'integration'", specifier = ">=4.1.2" }, { name = "docstring-parser", specifier = ">=0.16" }, - { name = "mcp", extras = ["cli"], specifier = ">=1.3.0" }, + { name = "httpx", specifier = ">=0.27.0" }, + { name = "mcp", extras = ["cli"], specifier = ">=1.12.0" }, + { name = "numpy", specifier = ">=2.0" }, + { name = "openai", specifier = ">=1.40" }, { name = "passlib", marker = "extra == 'integration'", specifier = ">=1.7.4" }, { name = "pycryptodome", marker = "extra == 'integration'", specifier = ">=3.20.0" }, + { name = "pyjwt", extras = ["crypto"], specifier = ">=2.9.0" }, { name = "python-dotenv", specifier = ">=1.0.1" }, + { name = "starlette", specifier = ">=0.40.0" }, + { name = "uvicorn", extras = ["standard"], specifier = ">=0.30.0" }, ] provides-extras = ["integration"] [package.metadata.requires-dev] -dev = [{ name = "black", specifier = ">=25.1.0" }] +dev = [ + { name = "black", specifier = ">=25.1.0" }, + { name = "pyyaml", specifier = ">=6.0" }, + { name = "ruff", specifier = ">=0.10.0" }, +] [[package]] name = "mdurl" @@ -463,6 +702,76 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, ] +[[package]] +name = "numpy" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/05/3d27272d30698dc0ecb7fdfaa41ad70303b444f81722bb99bce1d818638a/numpy-2.5.0.tar.gz", hash = "sha256:5a129578019311b6e56bdd714250f19b518f7dceeeb8d1af5490f4942d3f891c", size = 20652461, upload-time = "2026-06-21T20:57:51.95Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fa/0a/11486d02add7b1384dff7374d124b1cfbb0ee864dcc9f6a2c0380638cf84/numpy-2.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:489780423903667933b4ed6197b6ec3b75ea5dd17d1d8f0f38d798feb6921561", size = 16789987, upload-time = "2026-06-21T20:56:16.657Z" }, + { url = "https://files.pythonhosted.org/packages/55/b2/285f48640a181947b4587a3766d21ec1eaa7fea833d4b49957e09da467a2/numpy-2.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ece55976ced6bca95a03ae2839e2e5ccffe8eb6a3e7022415645eb154a81e4e6", size = 11760322, upload-time = "2026-06-21T20:56:19.813Z" }, + { url = "https://files.pythonhosted.org/packages/dd/67/b032db1eb03ca30d16eda3b0c22aaa615338b9263c2fd559d0f29451aca4/numpy-2.5.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:c83b664b0e6eee9594fa920cf0639d8af796606d3fad6cc70180c87e4b97c7be", size = 5319605, upload-time = "2026-06-21T20:56:22.173Z" }, + { url = "https://files.pythonhosted.org/packages/b9/83/03fc7300c7c6b6c84c487b1dc80d322817b95fbd1f4dd57a85e23b7198de/numpy-2.5.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:bf80333980bf37f523341ddd72c783f39d6829ec7736b9eb99086388a2d52cc2", size = 6653628, upload-time = "2026-06-21T20:56:23.914Z" }, + { url = "https://files.pythonhosted.org/packages/82/49/2ec21730bc63ccfda829323f7040a8ed4715b3852ce658689cf74ee96a8c/numpy-2.5.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1a4874217b36d5ac8fc876f52e39df56f8182c88463e9e2dceabf7ca8b7efb8", size = 15153691, upload-time = "2026-06-21T20:56:25.631Z" }, + { url = "https://files.pythonhosted.org/packages/bb/6b/f4a3d0637692c49da8ef99d72d52526f92e0a8d6ac4f0ca9f31441b9d9ea/numpy-2.5.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aaa760137137e8d3c920d27927748215b56014f92667dc9b6c27dfc61249255a", size = 16660066, upload-time = "2026-06-21T20:56:28.009Z" }, + { url = "https://files.pythonhosted.org/packages/3a/2f/c354ec86d1f3f5c19649463b0d39652e160736e5b0a4cd18dff0576715c4/numpy-2.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7174ce8265fc7f7417d171c9ea8fe905220748893ea67a2a7abe726ec331c4b0", size = 16514638, upload-time = "2026-06-21T20:56:30.26Z" }, + { url = "https://files.pythonhosted.org/packages/06/34/43efdcb319988648580f93c11f1ae82cf7e2faa74925e98e454ae3aa95f8/numpy-2.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b8c3daaf99de52415d20b42f8e8155c78642cb04207d02f9d317a0dcf1b3fb54", size = 18419647, upload-time = "2026-06-21T20:56:32.41Z" }, + { url = "https://files.pythonhosted.org/packages/71/e2/f5d1676b1d7fb682eb5e9a1641e7ebd2414b3216c370661d1029778908b4/numpy-2.5.0-cp312-cp312-win32.whl", hash = "sha256:6206db0af545d73d068add6d992279145f158428d1da6cc49adc4b630c5d6ee5", size = 6056688, upload-time = "2026-06-21T20:56:34.657Z" }, + { url = "https://files.pythonhosted.org/packages/8f/7c/48f115d1c58a34032facebcd51fdf2d02df2c51d4a46a81dd1197bb2ea6b/numpy-2.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:6f2d6873e2940c860a309d21e25b1e69af6aaffdd80aa056b04c16380db1c4f2", size = 12419237, upload-time = "2026-06-21T20:56:36.24Z" }, + { url = "https://files.pythonhosted.org/packages/86/26/2e0882f4044d1b1a1b63e875151fb2393389032022a8b7f5657a7996d3b2/numpy-2.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:a55e1eb2bca2cfd17a16b213c99dfc8502d47b0d494224d2122277d0400935ca", size = 10339912, upload-time = "2026-06-21T20:56:38.733Z" }, + { url = "https://files.pythonhosted.org/packages/8a/33/07675aaad7f26ea013d5e884d9a0d784b79c6bd7566c333f5a52fa3c610b/numpy-2.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:520e6b8be0a4b65840ac8090d4f51cef4bed66e2b0894d5a520f099adc24a9b2", size = 16784890, upload-time = "2026-06-21T20:56:40.799Z" }, + { url = "https://files.pythonhosted.org/packages/85/4b/953118a730ee3b35e28645e0eb4cf9beec5bdbb954e1ac2f5fcefba6bbc3/numpy-2.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:146b81cdd3967fdb6beca8ba25f00c58741d8f3cbd797f55af0fbe0bfec3469c", size = 11754584, upload-time = "2026-06-21T20:56:43.094Z" }, + { url = "https://files.pythonhosted.org/packages/44/9b/56dd530c367c74ae17411027cea4135ca57e1e0583bf5594cee18bd83217/numpy-2.5.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:126b88d95e8ff9b00c9e717aa540469f21d6180162f84c0caec51b16215d49cd", size = 5313904, upload-time = "2026-06-21T20:56:45.503Z" }, + { url = "https://files.pythonhosted.org/packages/ce/b0/bcd672edad27ecca7da1f7bb0ce72cd1706a4f2d79ae94990afc97c13e1c/numpy-2.5.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:d4313cef1594c5ce46c31b6e54e918338f63f16ee9322304e8c9114d6d81c8bd", size = 6648504, upload-time = "2026-06-21T20:56:47.567Z" }, + { url = "https://files.pythonhosted.org/packages/80/9e/15cdfcbd30a1544a46c9e487a00df331c4672450216538705a9e51fa6710/numpy-2.5.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:750fb097caf26fa878746d9d119f6f9da12dedcbff1eea966c3e3447647c4a9e", size = 15150086, upload-time = "2026-06-21T20:56:49.352Z" }, + { url = "https://files.pythonhosted.org/packages/32/4e/8d7656ccaab3e81e97258b8a9bc5f0c8502513a92fb4ceb0a2cbfebc17bf/numpy-2.5.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3893adc2dc7c0412ba76777db55a049215d99c9aa3113003be8f49f4f1290ab9", size = 16647250, upload-time = "2026-06-21T20:56:51.542Z" }, + { url = "https://files.pythonhosted.org/packages/3c/81/97060281b602ed07f21b12f4ec409eac1f75a2f91fbc829ed8b2becf3ad4/numpy-2.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:835e454dd99b238cdc5a3f63bce2371296f5ebc53ca1e0f8e6ddbb6d92a29aab", size = 16512864, upload-time = "2026-06-21T20:56:55.401Z" }, + { url = "https://files.pythonhosted.org/packages/33/ab/4496208146911f8d8ddb54f68a972aafa6c8d44babcb2ea03b0e5cc87c9d/numpy-2.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6f9836778081a0a3c02a6a21493f3e9f5b311f8d2541934f31f05583dc999ea4", size = 18408407, upload-time = "2026-06-21T20:56:57.75Z" }, + { url = "https://files.pythonhosted.org/packages/d4/9f/a4df67c181e4ee8b467aa3332dc2db10fd5c515136831302f3ca48bc0a01/numpy-2.5.0-cp313-cp313-win32.whl", hash = "sha256:0b525be4744b60bb0557ac872d53ef07d085b5f39622bc579c98d3809d05b988", size = 6054431, upload-time = "2026-06-21T20:57:00.016Z" }, + { url = "https://files.pythonhosted.org/packages/30/53/491e1c47c55b62ccc6a63c1c5b8635c73fc2258dddeb9bda27cae4a0ae96/numpy-2.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:44353e2878930039db472b99dc353d749826e4010bd4d2a7f835e94a97a5c748", size = 12414420, upload-time = "2026-06-21T20:57:01.815Z" }, + { url = "https://files.pythonhosted.org/packages/eb/4a/25c2906f541e9d9f4c5769764db732e6627be91a13f4724fa10634d77db4/numpy-2.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:48f54b00711f83a5f796b70c518e8c2b3c5848dda03a54911f23eb68519b9b60", size = 10339533, upload-time = "2026-06-21T20:57:03.961Z" }, + { url = "https://files.pythonhosted.org/packages/86/ad/abc44aaceaf7b17ee1edde2bbb4458da591bc79574cffff50c4bb35f00d1/numpy-2.5.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f27582c55ba4c750b7c58c8faf021d2cd9324a662b466229db8a417b41368af9", size = 16783807, upload-time = "2026-06-21T20:57:06.253Z" }, + { url = "https://files.pythonhosted.org/packages/5d/39/b72e168daf9c00fb20c9fc996d00437ccecdef3102387775d29d7a62576d/numpy-2.5.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:28e7137057d551e4a83c4ae414e3451f50568409db7569aacc7f9811ee06a446", size = 11765215, upload-time = "2026-06-21T20:57:08.547Z" }, + { url = "https://files.pythonhosted.org/packages/f7/a0/8400a9c0e3625182347593f5e1f57da9a617a534794805c8df5518154ddc/numpy-2.5.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:e1da54b53e75cd9fcfc23efcc7edab2c6aecf97b6037566d8a0fe804af8ec57c", size = 5324493, upload-time = "2026-06-21T20:57:11.012Z" }, + { url = "https://files.pythonhosted.org/packages/f6/8c/0d104deaa0401c93395a629ec902891618a2eff76d19229139cb5a887bfc/numpy-2.5.0-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:694d8f74e156f7fd01179f1aa8faa2f648ab6ae0f70b6c3fe57a03249aea2303", size = 6645211, upload-time = "2026-06-21T20:57:12.919Z" }, + { url = "https://files.pythonhosted.org/packages/6a/d9/4a4a628c812750363786afc3d33492709a5cd64b215469c16b0f6c7bb811/numpy-2.5.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1a7569a7b53c77716f036bb28cb1c91f166a26ec7d9502cd1e4bdfe502fdec22", size = 15166004, upload-time = "2026-06-21T20:57:14.717Z" }, + { url = "https://files.pythonhosted.org/packages/a0/5e/2a902317d7fc4aa93236e80c932662dadfc459b323d758329e01775125e1/numpy-2.5.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:39a0433bd4086ebd462960cf375e19195bb07b53dc1d87dd5fcf47ad78576f03", size = 16650797, upload-time = "2026-06-21T20:57:16.906Z" }, + { url = "https://files.pythonhosted.org/packages/e9/a0/a0090e6329f4ca5992c07847bb579c5259a19953dc57255bb08793142ffb/numpy-2.5.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:929f0c79ac38bcbd7154fe631dc907abfeddbcc5027a896bd1f7767323271e7a", size = 16524647, upload-time = "2026-06-21T20:57:19.165Z" }, + { url = "https://files.pythonhosted.org/packages/5e/7d/6caf27734c42b65837e7461ed0dbbd6b6fc835060c9714ec59d673bb383a/numpy-2.5.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:cc4f247a47bbf070bfd70be53ccdcf47b800af563535e7bbe172322197c30e21", size = 18411841, upload-time = "2026-06-21T20:57:21.638Z" }, + { url = "https://files.pythonhosted.org/packages/13/dc/26edadbd812536769a82c2e9e002234e33feb5da43061d47a044f6d309b7/numpy-2.5.0-cp314-cp314-win32.whl", hash = "sha256:5dc71423499fab3f46f7a7201155ade1669ea101f2f429d332df9e72f8161731", size = 6106361, upload-time = "2026-06-21T20:57:23.844Z" }, + { url = "https://files.pythonhosted.org/packages/f2/9e/4dd1459282229a72d92dece2ae9138e5cac94a72263a7ceb48f37434c925/numpy-2.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:ebb81d9d5443e0309d6c54894c3fbed74ad7da0714352a67b6d773cd189eae73", size = 12551749, upload-time = "2026-06-21T20:57:25.945Z" }, + { url = "https://files.pythonhosted.org/packages/05/a7/6bc6384c080b86c7f6c85c5bc5b540b24f4f679cd144791d99574e90d462/numpy-2.5.0-cp314-cp314-win_arm64.whl", hash = "sha256:3b94d0d0deceebfad3e67ae5c0e5eb87371e8f7a0581cd04a779928c2450cf1e", size = 10617072, upload-time = "2026-06-21T20:57:28.175Z" }, + { url = "https://files.pythonhosted.org/packages/86/6b/4a2b71d66ada5608ae02b63f150dfad520f6940721cb7f029ad270befc0e/numpy-2.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:22f3d43e362d650bc39db1f17851302874a148ca95ba6981c1dfb5fa6862f35b", size = 11881067, upload-time = "2026-06-21T20:57:30.104Z" }, + { url = "https://files.pythonhosted.org/packages/dc/b2/d365eb40a20efb49d67e9feb90494ed8511282ee1f5fa16006675c65397d/numpy-2.5.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:243563efb4cd7528a264567e9fd206c87826457322521d06206a00bfa316c927", size = 5440290, upload-time = "2026-06-21T20:57:32.193Z" }, + { url = "https://files.pythonhosted.org/packages/fa/5e/e9c03188de5f9b767e46a8fe988bcfd3efad066a4a3fda8b9cb11a93f895/numpy-2.5.0-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:84881d825ca75249b189bbee875fcfe3238aa5c479e6100893cda566e8e86826", size = 6748371, upload-time = "2026-06-21T20:57:33.933Z" }, + { url = "https://files.pythonhosted.org/packages/fd/1d/68c186a38a5027bae2c4ddd5ea681fdaf8b4d30fb7301def6d8ad270390f/numpy-2.5.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cda12aa4779d42b8771180aba759c96f527d43446d8f380ab59e2b35e8489efd", size = 15214643, upload-time = "2026-06-21T20:57:35.677Z" }, + { url = "https://files.pythonhosted.org/packages/8c/67/73f67b7c7e20635baae9c4c3ead4ae7326a005900297a6110971abd62eb5/numpy-2.5.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c0121101093d2bd74981b10f8837d78e794a8ff57834eb27179f49e1ba11ac6", size = 16690128, upload-time = "2026-06-21T20:57:38.159Z" }, + { url = "https://files.pythonhosted.org/packages/eb/05/d4c1fb0c46d02a27d6b2b8b319a78c90937acec8631c1641874670b31e6f/numpy-2.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d371c92cfa09da00022f501ab67fafaea813d752eb30ac44336d45b1e5b0268a", size = 16577902, upload-time = "2026-06-21T20:57:40.447Z" }, + { url = "https://files.pythonhosted.org/packages/9e/1d/771c797d50fa26e4888989cccf1d50ee51f530d4e455ad2692dcb64fa711/numpy-2.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9990713e9c38154c6861e7547f1e3fc7a87e75ff09bab24ef1cc81d81c2835e9", size = 18452814, upload-time = "2026-06-21T20:57:42.875Z" }, + { url = "https://files.pythonhosted.org/packages/e8/46/52fc0d2a68d7643f0f149eeea5a5d8ea2a3507056ac8afa83c9212606e8b/numpy-2.5.0-cp314-cp314t-win32.whl", hash = "sha256:edadfbd4794b1086c0d822f81863e8a68fc129d132fd0bb9e31e955d7fbbbdb7", size = 6253168, upload-time = "2026-06-21T20:57:45.101Z" }, + { url = "https://files.pythonhosted.org/packages/2a/be/6c8d1118b5f13b2881dc095d5b345de19c6638b8959c17409b6eff84c8aa/numpy-2.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f7e5fa4382967ae6548bd2f174219afb908e294b0d5f625af01166edd5f7d9aa", size = 12736286, upload-time = "2026-06-21T20:57:46.935Z" }, + { url = "https://files.pythonhosted.org/packages/fd/6a/d3a169aaf8536cf228d56a09e04bcb713a2fe4410d4e2105b9419b5a9c89/numpy-2.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:016623417bb330d719d579daf2d6b9a01ddc52e41a9ed61a47f39fde46dcd865", size = 10686451, upload-time = "2026-06-21T20:57:49.313Z" }, +] + +[[package]] +name = "openai" +version = "2.43.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "distro" }, + { name = "httpx" }, + { name = "jiter" }, + { name = "pydantic" }, + { name = "sniffio" }, + { name = "tqdm" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f3/fa/88d0c58a0c58df7e6758e66b99c5d028d5e0bb49f8812d7203940cd9dbf1/openai-2.43.0.tar.gz", hash = "sha256:e74d238200a26868977002190fb6631613480a93dfe0c9c982e77021ed60a017", size = 785369, upload-time = "2026-06-17T17:06:56.06Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/d2/ba767f4bbb30776c03d40906a2d3afad716a165ffa1771fc23b8992f7920/openai-2.43.0-py3-none-any.whl", hash = "sha256:65a670b54fadf2268c9e1330133373c963eb779ee969e5cbad419ec2c21dce97", size = 1355077, upload-time = "2026-06-17T17:06:53.614Z" }, +] + [[package]] name = "packaging" version = "26.0" @@ -540,55 +849,92 @@ wheels = [ [[package]] name = "pydantic" -version = "2.10.6" +version = "2.13.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-types" }, { name = "pydantic-core" }, { name = "typing-extensions" }, + { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b7/ae/d5220c5c52b158b1de7ca89fc5edb72f304a70a4c540c84c8844bf4008de/pydantic-2.10.6.tar.gz", hash = "sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236", size = 761681, upload-time = "2025-01-24T01:42:12.693Z" } +sdist = { url = "https://files.pythonhosted.org/packages/18/a5/b60d21ac674192f8ab0ba4e9fd860690f9b4a6e51ca5df118733b487d8d6/pydantic-2.13.4.tar.gz", hash = "sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6", size = 844775, upload-time = "2026-05-06T13:43:05.343Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/3c/8cc1cc84deffa6e25d2d0c688ebb80635dfdbf1dbea3e30c541c8cf4d860/pydantic-2.10.6-py3-none-any.whl", hash = "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584", size = 431696, upload-time = "2025-01-24T01:42:10.371Z" }, + { url = "https://files.pythonhosted.org/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl", hash = "sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba", size = 472262, upload-time = "2026-05-06T13:43:02.641Z" }, ] [[package]] name = "pydantic-core" -version = "2.27.2" +version = "2.46.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fc/01/f3e5ac5e7c25833db5eb555f7b7ab24cd6f8c322d3a3ad2d67a952dc0abc/pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39", size = 413443, upload-time = "2024-12-18T11:31:54.917Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d6/74/51c8a5482ca447871c93e142d9d4a92ead74de6c8dc5e66733e22c9bba89/pydantic_core-2.27.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9e0c8cfefa0ef83b4da9588448b6d8d2a2bf1a53c3f1ae5fca39eb3061e2f0b0", size = 1893127, upload-time = "2024-12-18T11:28:30.346Z" }, - { url = "https://files.pythonhosted.org/packages/d3/f3/c97e80721735868313c58b89d2de85fa80fe8dfeeed84dc51598b92a135e/pydantic_core-2.27.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83097677b8e3bd7eaa6775720ec8e0405f1575015a463285a92bfdfe254529ef", size = 1811340, upload-time = "2024-12-18T11:28:32.521Z" }, - { url = "https://files.pythonhosted.org/packages/9e/91/840ec1375e686dbae1bd80a9e46c26a1e0083e1186abc610efa3d9a36180/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:172fce187655fece0c90d90a678424b013f8fbb0ca8b036ac266749c09438cb7", size = 1822900, upload-time = "2024-12-18T11:28:34.507Z" }, - { url = "https://files.pythonhosted.org/packages/f6/31/4240bc96025035500c18adc149aa6ffdf1a0062a4b525c932065ceb4d868/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:519f29f5213271eeeeb3093f662ba2fd512b91c5f188f3bb7b27bc5973816934", size = 1869177, upload-time = "2024-12-18T11:28:36.488Z" }, - { url = "https://files.pythonhosted.org/packages/fa/20/02fbaadb7808be578317015c462655c317a77a7c8f0ef274bc016a784c54/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05e3a55d124407fffba0dd6b0c0cd056d10e983ceb4e5dbd10dda135c31071d6", size = 2038046, upload-time = "2024-12-18T11:28:39.409Z" }, - { url = "https://files.pythonhosted.org/packages/06/86/7f306b904e6c9eccf0668248b3f272090e49c275bc488a7b88b0823444a4/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c3ed807c7b91de05e63930188f19e921d1fe90de6b4f5cd43ee7fcc3525cb8c", size = 2685386, upload-time = "2024-12-18T11:28:41.221Z" }, - { url = "https://files.pythonhosted.org/packages/8d/f0/49129b27c43396581a635d8710dae54a791b17dfc50c70164866bbf865e3/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fb4aadc0b9a0c063206846d603b92030eb6f03069151a625667f982887153e2", size = 1997060, upload-time = "2024-12-18T11:28:44.709Z" }, - { url = "https://files.pythonhosted.org/packages/0d/0f/943b4af7cd416c477fd40b187036c4f89b416a33d3cc0ab7b82708a667aa/pydantic_core-2.27.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28ccb213807e037460326424ceb8b5245acb88f32f3d2777427476e1b32c48c4", size = 2004870, upload-time = "2024-12-18T11:28:46.839Z" }, - { url = "https://files.pythonhosted.org/packages/35/40/aea70b5b1a63911c53a4c8117c0a828d6790483f858041f47bab0b779f44/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:de3cd1899e2c279b140adde9357c4495ed9d47131b4a4eaff9052f23398076b3", size = 1999822, upload-time = "2024-12-18T11:28:48.896Z" }, - { url = "https://files.pythonhosted.org/packages/f2/b3/807b94fd337d58effc5498fd1a7a4d9d59af4133e83e32ae39a96fddec9d/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:220f892729375e2d736b97d0e51466252ad84c51857d4d15f5e9692f9ef12be4", size = 2130364, upload-time = "2024-12-18T11:28:50.755Z" }, - { url = "https://files.pythonhosted.org/packages/fc/df/791c827cd4ee6efd59248dca9369fb35e80a9484462c33c6649a8d02b565/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a0fcd29cd6b4e74fe8ddd2c90330fd8edf2e30cb52acda47f06dd615ae72da57", size = 2158303, upload-time = "2024-12-18T11:28:54.122Z" }, - { url = "https://files.pythonhosted.org/packages/9b/67/4e197c300976af185b7cef4c02203e175fb127e414125916bf1128b639a9/pydantic_core-2.27.2-cp312-cp312-win32.whl", hash = "sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc", size = 1834064, upload-time = "2024-12-18T11:28:56.074Z" }, - { url = "https://files.pythonhosted.org/packages/1f/ea/cd7209a889163b8dcca139fe32b9687dd05249161a3edda62860430457a5/pydantic_core-2.27.2-cp312-cp312-win_amd64.whl", hash = "sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9", size = 1989046, upload-time = "2024-12-18T11:28:58.107Z" }, - { url = "https://files.pythonhosted.org/packages/bc/49/c54baab2f4658c26ac633d798dab66b4c3a9bbf47cff5284e9c182f4137a/pydantic_core-2.27.2-cp312-cp312-win_arm64.whl", hash = "sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b", size = 1885092, upload-time = "2024-12-18T11:29:01.335Z" }, - { url = "https://files.pythonhosted.org/packages/41/b1/9bc383f48f8002f99104e3acff6cba1231b29ef76cfa45d1506a5cad1f84/pydantic_core-2.27.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7d14bd329640e63852364c306f4d23eb744e0f8193148d4044dd3dacdaacbd8b", size = 1892709, upload-time = "2024-12-18T11:29:03.193Z" }, - { url = "https://files.pythonhosted.org/packages/10/6c/e62b8657b834f3eb2961b49ec8e301eb99946245e70bf42c8817350cbefc/pydantic_core-2.27.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82f91663004eb8ed30ff478d77c4d1179b3563df6cdb15c0817cd1cdaf34d154", size = 1811273, upload-time = "2024-12-18T11:29:05.306Z" }, - { url = "https://files.pythonhosted.org/packages/ba/15/52cfe49c8c986e081b863b102d6b859d9defc63446b642ccbbb3742bf371/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71b24c7d61131bb83df10cc7e687433609963a944ccf45190cfc21e0887b08c9", size = 1823027, upload-time = "2024-12-18T11:29:07.294Z" }, - { url = "https://files.pythonhosted.org/packages/b1/1c/b6f402cfc18ec0024120602bdbcebc7bdd5b856528c013bd4d13865ca473/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa8e459d4954f608fa26116118bb67f56b93b209c39b008277ace29937453dc9", size = 1868888, upload-time = "2024-12-18T11:29:09.249Z" }, - { url = "https://files.pythonhosted.org/packages/bd/7b/8cb75b66ac37bc2975a3b7de99f3c6f355fcc4d89820b61dffa8f1e81677/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce8918cbebc8da707ba805b7fd0b382816858728ae7fe19a942080c24e5b7cd1", size = 2037738, upload-time = "2024-12-18T11:29:11.23Z" }, - { url = "https://files.pythonhosted.org/packages/c8/f1/786d8fe78970a06f61df22cba58e365ce304bf9b9f46cc71c8c424e0c334/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eda3f5c2a021bbc5d976107bb302e0131351c2ba54343f8a496dc8783d3d3a6a", size = 2685138, upload-time = "2024-12-18T11:29:16.396Z" }, - { url = "https://files.pythonhosted.org/packages/a6/74/d12b2cd841d8724dc8ffb13fc5cef86566a53ed358103150209ecd5d1999/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8086fa684c4775c27f03f062cbb9eaa6e17f064307e86b21b9e0abc9c0f02e", size = 1997025, upload-time = "2024-12-18T11:29:20.25Z" }, - { url = "https://files.pythonhosted.org/packages/a0/6e/940bcd631bc4d9a06c9539b51f070b66e8f370ed0933f392db6ff350d873/pydantic_core-2.27.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8d9b3388db186ba0c099a6d20f0604a44eabdeef1777ddd94786cdae158729e4", size = 2004633, upload-time = "2024-12-18T11:29:23.877Z" }, - { url = "https://files.pythonhosted.org/packages/50/cc/a46b34f1708d82498c227d5d80ce615b2dd502ddcfd8376fc14a36655af1/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7a66efda2387de898c8f38c0cf7f14fca0b51a8ef0b24bfea5849f1b3c95af27", size = 1999404, upload-time = "2024-12-18T11:29:25.872Z" }, - { url = "https://files.pythonhosted.org/packages/ca/2d/c365cfa930ed23bc58c41463bae347d1005537dc8db79e998af8ba28d35e/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:18a101c168e4e092ab40dbc2503bdc0f62010e95d292b27827871dc85450d7ee", size = 2130130, upload-time = "2024-12-18T11:29:29.252Z" }, - { url = "https://files.pythonhosted.org/packages/f4/d7/eb64d015c350b7cdb371145b54d96c919d4db516817f31cd1c650cae3b21/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ba5dd002f88b78a4215ed2f8ddbdf85e8513382820ba15ad5ad8955ce0ca19a1", size = 2157946, upload-time = "2024-12-18T11:29:31.338Z" }, - { url = "https://files.pythonhosted.org/packages/a4/99/bddde3ddde76c03b65dfd5a66ab436c4e58ffc42927d4ff1198ffbf96f5f/pydantic_core-2.27.2-cp313-cp313-win32.whl", hash = "sha256:1ebaf1d0481914d004a573394f4be3a7616334be70261007e47c2a6fe7e50130", size = 1834387, upload-time = "2024-12-18T11:29:33.481Z" }, - { url = "https://files.pythonhosted.org/packages/71/47/82b5e846e01b26ac6f1893d3c5f9f3a2eb6ba79be26eef0b759b4fe72946/pydantic_core-2.27.2-cp313-cp313-win_amd64.whl", hash = "sha256:953101387ecf2f5652883208769a79e48db18c6df442568a0b5ccd8c2723abee", size = 1990453, upload-time = "2024-12-18T11:29:35.533Z" }, - { url = "https://files.pythonhosted.org/packages/51/b2/b2b50d5ecf21acf870190ae5d093602d95f66c9c31f9d5de6062eb329ad1/pydantic_core-2.27.2-cp313-cp313-win_arm64.whl", hash = "sha256:ac4dbfd1691affb8f48c2c13241a2e3b60ff23247cbcf981759c768b6633cf8b", size = 1885186, upload-time = "2024-12-18T11:29:37.649Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/9d/56/921726b776ace8d8f5db44c4ef961006580d91dc52b803c489fafd1aa249/pydantic_core-2.46.4.tar.gz", hash = "sha256:62f875393d7f270851f20523dd2e29f082bcc82292d66db2b64ea71f64b6e1c1", size = 471464, upload-time = "2026-05-06T13:37:06.98Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/8c/af022f0af448d7747c5154288d46b5f2bc5f17366eaa0e23e9aa04d59f3b/pydantic_core-2.46.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3245406455a5d98187ec35530fd772b1d799b26667980872c8d4614991e2c4a2", size = 2106158, upload-time = "2026-05-06T13:38:57.215Z" }, + { url = "https://files.pythonhosted.org/packages/19/95/6195171e385007300f0f5574592e467c568becce2d937a0b6804f218bc49/pydantic_core-2.46.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:962ccbab7b642487b1d8b7df90ef677e03134cf1fd8880bf698649b22a69371f", size = 1951724, upload-time = "2026-05-06T13:37:02.697Z" }, + { url = "https://files.pythonhosted.org/packages/8e/bc/f47d1ff9cbb1620e1b5b697eef06010035735f07820180e74178226b27b3/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8233f2947cf85404441fd7e0085f53b10c93e0ee78611099b5c7237e36aacbf7", size = 1975742, upload-time = "2026-05-06T13:37:09.448Z" }, + { url = "https://files.pythonhosted.org/packages/5b/11/9b9a5b0306345664a2da6410877af6e8082481b5884b3ddd78d47c6013ce/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a233125ac121aa3ffba9a2b59edfc4a985a76092dc8279586ab4b71390875e7", size = 2052418, upload-time = "2026-05-06T13:37:38.234Z" }, + { url = "https://files.pythonhosted.org/packages/f1/b7/a65fec226f5d78fc39f4a13c4cc0c768c22b113438f60c14adc9d2865038/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b712b53160b79a5850310b912a5ef8e57e56947c8ad690c227f5c9d7e561712", size = 2232274, upload-time = "2026-05-06T13:38:27.753Z" }, + { url = "https://files.pythonhosted.org/packages/68/f0/92039db98b907ef49269a8271f67db9cb78ae2fc68062ef7e4e77adb5f61/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9401557acd873c3a7f3eb9383edef8ac4968f9510e340f4808d427e75667e7b4", size = 2309940, upload-time = "2026-05-06T13:38:05.353Z" }, + { url = "https://files.pythonhosted.org/packages/5f/97/2aab507d3d00ca626e8e57c1eac6a79e4e5fbcc63eb99733ff55d1717f65/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:926c9541b14b12b1681dca8a0b75feb510b06c6341b70a8e500c2fdcff837cce", size = 2094516, upload-time = "2026-05-06T13:39:10.577Z" }, + { url = "https://files.pythonhosted.org/packages/22/37/a8aca44d40d737dde2bc05b3c6c07dff0de07ce6f82e9f3167aeaf4d5dea/pydantic_core-2.46.4-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:56cb4851bcaf3d117eddcef4fe66afd750a50274b0da8e22be256d10e5611987", size = 2136854, upload-time = "2026-05-06T13:40:22.59Z" }, + { url = "https://files.pythonhosted.org/packages/24/99/fcef1b79238c06a8cbec70819ac722ba76e02bc8ada9b0fd66eba40da01b/pydantic_core-2.46.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c68fcd102d71ea85c5b2dfac3f4f8476eff42a9e078fd5faefff6d145063536b", size = 2180306, upload-time = "2026-05-06T13:40:10.666Z" }, + { url = "https://files.pythonhosted.org/packages/ae/6c/fc44000918855b42779d007ae63b0532794739027b2f417321cddbc44f6a/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b2f69dec1725e79a012d920df1707de5caf7ed5e08f3be4435e25803efc47458", size = 2190044, upload-time = "2026-05-06T13:40:43.231Z" }, + { url = "https://files.pythonhosted.org/packages/6b/65/d9cadc9f1920d7a127ad2edba16c1db7916e59719285cd6c94600b0080ba/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:8d0820e8192167f80d88d64038e609c31452eeca865b4e1d9950a27a4609b00b", size = 2329133, upload-time = "2026-05-06T13:39:57.365Z" }, + { url = "https://files.pythonhosted.org/packages/d0/cf/c873d91679f3a30bcf5e7ac280ce5573483e72295307685120d0d5ad3416/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fbdb89b3e1c94a30cc5edfce477c6e6a5dc4d8f84665b455c27582f211a1c72c", size = 2374464, upload-time = "2026-05-06T13:38:06.976Z" }, + { url = "https://files.pythonhosted.org/packages/47/bd/6f2fc8188f31bf10590f1e98e7b306336161fac930a8c514cd7bd828c7dc/pydantic_core-2.46.4-cp312-cp312-win32.whl", hash = "sha256:9aa768456404a8bf48a4406685ac2bec8e72b62c69313734fa3b73cf33b3a894", size = 1974823, upload-time = "2026-05-06T13:40:47.985Z" }, + { url = "https://files.pythonhosted.org/packages/40/8c/985c1d41ea1107c2534abd9870e4ed5c8e7669b5c308297835c001e7a1c4/pydantic_core-2.46.4-cp312-cp312-win_amd64.whl", hash = "sha256:e9c26f834c65f5752f3f06cb08cb86a913ceb7274d0db6e267808a708b46bc89", size = 2072919, upload-time = "2026-05-06T13:39:21.153Z" }, + { url = "https://files.pythonhosted.org/packages/c4/ba/f463d006e0c47373ca7ec5e1a261c59dc01ef4d62b2657af925fb0deee3a/pydantic_core-2.46.4-cp312-cp312-win_arm64.whl", hash = "sha256:4fc73cb559bdb54b1134a706a2802a4cddd27a0633f5abb7e53056268751ac6a", size = 2027604, upload-time = "2026-05-06T13:39:03.753Z" }, + { url = "https://files.pythonhosted.org/packages/51/a2/5d30b469c5267a17b39dec53208222f76a8d351dfac4af661888c5aee77d/pydantic_core-2.46.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5d5902252db0d3cedf8d4a1bc68f70eeb430f7e4c7104c8c476753519b423008", size = 2106306, upload-time = "2026-05-06T13:37:48.029Z" }, + { url = "https://files.pythonhosted.org/packages/c1/81/4fa520eaffa8bd7d1525e644cd6d39e7d60b1592bc5b516693c7340b50f1/pydantic_core-2.46.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c94f0688e7b8d0a67abf40e57a7eaaecd17cc9586706a31b76c031f63df052b4", size = 1951906, upload-time = "2026-05-06T13:37:17.012Z" }, + { url = "https://files.pythonhosted.org/packages/03/d5/fd02da45b659668b05923b17ba3a0100a0a3d5541e3bd8fcc4ecb711309e/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f027324c56cd5406ca49c124b0db10e56c69064fec039acc571c29020cc87c76", size = 1976802, upload-time = "2026-05-06T13:37:35.113Z" }, + { url = "https://files.pythonhosted.org/packages/21/f2/95727e1368be3d3ed485eaab7adbd7dda408f33f7a36e8b48e0144002b91/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e739fee756ba1010f8bcccb534252e85a35fe45ae92c295a06059ce58b74ccd3", size = 2052446, upload-time = "2026-05-06T13:37:12.313Z" }, + { url = "https://files.pythonhosted.org/packages/9c/86/5d99feea3f77c7234b8718075b23db11532773c1a0dbd9b9490215dc2eeb/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d56801be94b86a9da183e5f3766e6310752b99ff647e38b09a9500d88e46e76", size = 2232757, upload-time = "2026-05-06T13:39:01.149Z" }, + { url = "https://files.pythonhosted.org/packages/d2/3a/508ac615935ef7588cf6d9e9b91309fdc2da751af865e02a9098de88258c/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2412e734dcb48da14d4e4006b82b46b74f2518b8a26ee7e58c6844a6cd6d03c4", size = 2309275, upload-time = "2026-05-06T13:37:41.406Z" }, + { url = "https://files.pythonhosted.org/packages/07/f8/41db9de19d7987d6b04715a02b3b40aea467000275d9d758ffaa31af7d50/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9551187363ffc0de2a00b2e47c25aeaeb1020b69b668762966df15fc5659dd5a", size = 2094467, upload-time = "2026-05-06T13:39:18.847Z" }, + { url = "https://files.pythonhosted.org/packages/2c/e2/f35033184cb11d0052daf4416e8e10a502ea2ac006fc4f459aee872727d1/pydantic_core-2.46.4-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:0186750b482eefa11d7f435892b09c5c606193ef3375bcf94aa00ae6bfb66262", size = 2134417, upload-time = "2026-05-06T13:40:17.944Z" }, + { url = "https://files.pythonhosted.org/packages/7e/7b/6ceeb1cc90e193862f444ebe373d8fdf613f0a82572dde03fb10734c6c71/pydantic_core-2.46.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5855698a4856556d86e8e6cd8434bc3ac0314ee8e12089ae0e143f64c6256e4e", size = 2179782, upload-time = "2026-05-06T13:40:32.618Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f2/c8d7773ede6af08036423a00ae0ceffce266c3c52a096c435d68c896083f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:cbaf13819775b7f769bf4a1f066cb6df7a28d4480081a589828ef190226881cd", size = 2188782, upload-time = "2026-05-06T13:36:51.018Z" }, + { url = "https://files.pythonhosted.org/packages/59/31/0c864784e31f09f05cdd87606f08923b9c9e7f6e51dd27f20f62f975ce9f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:633147d34cf4550417f12e2b1a0383973bdf5cdfde212cb09e9a581cf10820be", size = 2328334, upload-time = "2026-05-06T13:40:37.764Z" }, + { url = "https://files.pythonhosted.org/packages/c2/eb/4f6c8a41efa30baa755590f4141abf3a8c370fab610915733e74134a7270/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:82cf5301172168103724d49a1444d3378cb20cdee30b116a1bd6031236298a5d", size = 2372986, upload-time = "2026-05-06T13:39:34.152Z" }, + { url = "https://files.pythonhosted.org/packages/5b/24/b375a480d53113860c299764bfe9f349a3dc9108b3adc0d7f0d786492ebf/pydantic_core-2.46.4-cp313-cp313-win32.whl", hash = "sha256:9fa8ae11da9e2b3126c6426f147e0fba88d96d65921799bb30c6abd1cb2c97fb", size = 1973693, upload-time = "2026-05-06T13:37:55.072Z" }, + { url = "https://files.pythonhosted.org/packages/7e/e8/cff247591966f2d22ec8c003cd7587e27b7ba7b81ab2fb888e3ab75dc285/pydantic_core-2.46.4-cp313-cp313-win_amd64.whl", hash = "sha256:6b3ace8194b0e5204818c92802dcdca7fc6d88aabbb799d7c795540d9cd6d292", size = 2071819, upload-time = "2026-05-06T13:38:49.139Z" }, + { url = "https://files.pythonhosted.org/packages/c6/1a/f4aee670d5670e9e148e0c82c7db98d780be566c6e6a97ee8035528ca0b3/pydantic_core-2.46.4-cp313-cp313-win_arm64.whl", hash = "sha256:184c081504d17f1c1066e430e117142b2c77d9448a97f7b65c6ac9fd9aee238d", size = 2027411, upload-time = "2026-05-06T13:40:45.796Z" }, + { url = "https://files.pythonhosted.org/packages/8d/74/228a26ddad29c6672b805d9fd78e8d251cd04004fa7eed0e622096cd0250/pydantic_core-2.46.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:428e04521a40150c85216fc8b85e8d39fece235a9cf5e383761238c7fa9b96fb", size = 2102079, upload-time = "2026-05-06T13:38:41.019Z" }, + { url = "https://files.pythonhosted.org/packages/ad/1f/8970b150a4b4365623ae00fc88603491f763c627311ae8031e3111356d6e/pydantic_core-2.46.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23ace664830ee0bfe014a0c7bc248b1f7f25ed7ad103852c317624a1083af462", size = 1952179, upload-time = "2026-05-06T13:36:59.812Z" }, + { url = "https://files.pythonhosted.org/packages/95/30/5211a831ae054928054b2f79731661087a2bc5c01e825c672b3a4a8f1b3e/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce5c1d2a8b27468f433ca974829c44060b8097eedc39933e3c206a90ee49c4a9", size = 1978926, upload-time = "2026-05-06T13:37:39.933Z" }, + { url = "https://files.pythonhosted.org/packages/57/e9/689668733b1eb67adeef047db3c2e8788fcf65a7fd9c9e2b46b7744fe245/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7283d57845ecf5a163403eb0702dfc220cc4fbdd18919cb5ccea4f95ee1cdab4", size = 2046785, upload-time = "2026-05-06T13:38:01.995Z" }, + { url = "https://files.pythonhosted.org/packages/60/d9/6715260422ff50a2109878fd24d948a6c3446bb2664f34ee78cd972b3acd/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8daafc69c93ee8a0204506a3b6b30f586ef54028f52aeeeb5c4cfc5184fd5914", size = 2228733, upload-time = "2026-05-06T13:40:50.371Z" }, + { url = "https://files.pythonhosted.org/packages/18/ae/fdb2f64316afca925640f8e70bb1a564b0ec2721c1389e25b8eb4bf9a299/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd2213145bcc2ba85884d0ac63d222fece9209678f77b9b4d76f054c561adb28", size = 2307534, upload-time = "2026-05-06T13:37:21.531Z" }, + { url = "https://files.pythonhosted.org/packages/89/1d/8eff589b45bb8190a9d12c49cfad0f176a5cbd1534908a6b5125e2886239/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a5f930472650a82629163023e630d160863fce524c616f4e5186e5de9d9a49b", size = 2099732, upload-time = "2026-05-06T13:39:31.942Z" }, + { url = "https://files.pythonhosted.org/packages/06/d5/ee5a3366637fee41dee51a1fc91562dcf12ddbc68fda34e6b253da2324bb/pydantic_core-2.46.4-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:c1b3f518abeca3aa13c712fd202306e145abf59a18b094a6bafb2d2bbf59192c", size = 2129627, upload-time = "2026-05-06T13:37:25.033Z" }, + { url = "https://files.pythonhosted.org/packages/94/33/2414be571d2c6a6c4d08be21f9292b6d3fdb08949a97b6dfe985017821db/pydantic_core-2.46.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a7dd0b3ee80d90150e3495a3a13ac34dbcbfd4f012996a6a1d8900e91b5c0fb", size = 2179141, upload-time = "2026-05-06T13:37:14.046Z" }, + { url = "https://files.pythonhosted.org/packages/7b/79/7daa95be995be0eecc4cf75064cb33f9bbbfe3fe0158caf2f0d4a996a5c7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:3fb702cd90b0446a3a1c5e470bfa0dd23c0233b676a9099ddcc964fa6ca13898", size = 2184325, upload-time = "2026-05-06T13:36:53.615Z" }, + { url = "https://files.pythonhosted.org/packages/9f/cb/d0a382f5c0de8a222dc61c65348e0ce831b1f68e0a018450d31c2cace3a5/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b8458003118a712e66286df6a707db01c52c0f52f7db8e4a38f0da1d3b94fc4e", size = 2323990, upload-time = "2026-05-06T13:40:29.971Z" }, + { url = "https://files.pythonhosted.org/packages/05/db/d9ba624cc4a5aced1598e88c04fdbd8310c8a69b9d38b9a3d39ce3a61ed7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:372429a130e469c9cd698925ce5fc50940b7a1336b0d82038e63d5bbc4edc519", size = 2369978, upload-time = "2026-05-06T13:37:23.027Z" }, + { url = "https://files.pythonhosted.org/packages/f2/20/d15df15ba918c423461905802bfd2981c3af0bfa0e40d05e13edbfa48bc3/pydantic_core-2.46.4-cp314-cp314-win32.whl", hash = "sha256:85bb3611ff1802f3ee7fdd7dbff26b56f343fb432d57a4728fdd49b6ef35e2f4", size = 1966354, upload-time = "2026-05-06T13:38:03.499Z" }, + { url = "https://files.pythonhosted.org/packages/fc/b6/6b8de4c0a7d7ab3004c439c80c5c1e0a3e8d78bbae19379b01960383d9e5/pydantic_core-2.46.4-cp314-cp314-win_amd64.whl", hash = "sha256:811ff8e9c313ab425368bcbb36e5c4ebd7108c2bbf4e4089cfbb0b01eff63fac", size = 2072238, upload-time = "2026-05-06T13:39:40.807Z" }, + { url = "https://files.pythonhosted.org/packages/32/36/51eb763beec1f4cf59b1db243a7dcc39cbb41230f050a09b9d69faaf0a48/pydantic_core-2.46.4-cp314-cp314-win_arm64.whl", hash = "sha256:bfec22eab3c8cc2ceec0248aec886624116dc079afa027ecc8ad4a7e62010f8a", size = 2018251, upload-time = "2026-05-06T13:37:26.72Z" }, + { url = "https://files.pythonhosted.org/packages/e8/91/855af51d625b23aa987116a19e231d2aaef9c4a415273ddc189b79a45fee/pydantic_core-2.46.4-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:af8244b2bef6aaad6d92cda81372de7f8c8d36c9f0c3ea36e827c60e7d9467a0", size = 2099593, upload-time = "2026-05-06T13:39:47.682Z" }, + { url = "https://files.pythonhosted.org/packages/fb/1b/8784a54c65edb5f49f0a14d6977cf1b209bba85a4c77445b255c2de58ab3/pydantic_core-2.46.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a4330cdbc57162e4b3aa303f588ba752257694c9c9be3e7ebb11b4aca659b5d", size = 1935226, upload-time = "2026-05-06T13:40:40.428Z" }, + { url = "https://files.pythonhosted.org/packages/e8/e7/1955d28d1afc56dd4b3ad7cc0cf39df1b9852964cf16e5d13912756d6d6b/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c61fc04a3d840155ff08e475a04809278972fe6aef51e2720554e96367e34b", size = 1974605, upload-time = "2026-05-06T13:37:32.029Z" }, + { url = "https://files.pythonhosted.org/packages/93/e2/3fedbf0ba7a22850e6e9fd78117f1c0f10f950182344d8a6c535d468fdd8/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c50f2528cf200c5eed56faf3f4e22fcd5f38c157a8b78576e6ba3168ec35f000", size = 2030777, upload-time = "2026-05-06T13:38:55.239Z" }, + { url = "https://files.pythonhosted.org/packages/f8/61/46be275fcaaba0b4f5b9669dd852267ce1ff616592dccf7a7845588df091/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0cbe8b01f948de4286c74cdd6c667aceb38f5c1e26f0693b3983d9d74887c65e", size = 2236641, upload-time = "2026-05-06T13:37:08.096Z" }, + { url = "https://files.pythonhosted.org/packages/60/db/12e93e46a8bac9988be3c016860f83293daea8c716c029c9ace279036f2f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:617d7e2ca7dcb8c5cf6bcb8c59b8832c94b36196bbf1cbd1bfb56ed341905edd", size = 2286404, upload-time = "2026-05-06T13:40:20.221Z" }, + { url = "https://files.pythonhosted.org/packages/e2/4a/4d8b19008f38d31c53b8219cfedc2e3d5de5fe99d90076b7e767de29274f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7027560ee92211647d0d34e3f7cd6f50da56399d26a9c8ad0da286d3869a53f3", size = 2109219, upload-time = "2026-05-06T13:38:12.153Z" }, + { url = "https://files.pythonhosted.org/packages/88/70/3cbc40978fefb7bb09c6708d40d4ad1a5d70fd7213c3d17f971de868ec1f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:f99626688942fb746e545232e7726926f3be91b5975f8b55327665fafda991c7", size = 2110594, upload-time = "2026-05-06T13:40:02.971Z" }, + { url = "https://files.pythonhosted.org/packages/9d/20/b8d36736216e29491125531685b2f9e61aa5b4b2599893f8268551da3338/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc3e9034a63de20e15e8ade85358bc6efc614008cab72898b4b4952bea0509ff", size = 2159542, upload-time = "2026-05-06T13:39:27.506Z" }, + { url = "https://files.pythonhosted.org/packages/1d/a2/367df868eb584dacf6bf82a389272406d7178e301c4ac82545ab98bc2dd9/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:97e7cf2be5c77b7d1a9713a05605d49460d02c6078d38d8bef3cbe323c548424", size = 2168146, upload-time = "2026-05-06T13:38:31.93Z" }, + { url = "https://files.pythonhosted.org/packages/c1/b8/4460f77f7e201893f649a29ab355dddd3beee8a97bcb1a320db414f9a06e/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:3bf92c5d0e00fefaab325a4d27828fe6b6e2a21848686b5b60d2d9eeb09d76c6", size = 2306309, upload-time = "2026-05-06T13:37:44.717Z" }, + { url = "https://files.pythonhosted.org/packages/64/c4/be2639293acd87dc8ddbcec41a73cee9b2ebf996fe6d892a1a74e88ad3f7/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:3ecbc122d18468d06ca279dc26a8c2e2d5acb10943bb35e36ae92096dc3b5565", size = 2369736, upload-time = "2026-05-06T13:37:05.645Z" }, + { url = "https://files.pythonhosted.org/packages/30/a6/9f9f380dbb301f67023bf8f707aaa75daadf84f7152d95c410fd7e81d994/pydantic_core-2.46.4-cp314-cp314t-win32.whl", hash = "sha256:e846ae7835bf0703ae43f534ab79a867146dadd59dc9ca5c8b53d5c8f7c9ef02", size = 1955575, upload-time = "2026-05-06T13:38:51.116Z" }, + { url = "https://files.pythonhosted.org/packages/40/1f/f1eb9eb350e795d1af8586289746f5c5677d16043040d63710e22abc43c9/pydantic_core-2.46.4-cp314-cp314t-win_amd64.whl", hash = "sha256:2108ba5c1c1eca18030634489dc544844144ee36357f2f9f780b93e7ddbb44b5", size = 2051624, upload-time = "2026-05-06T13:38:21.672Z" }, + { url = "https://files.pythonhosted.org/packages/f6/d2/42dd53d0a85c27606f316d3aa5d2869c4e8470a5ed6dec30e4a1abe19192/pydantic_core-2.46.4-cp314-cp314t-win_arm64.whl", hash = "sha256:4fcbe087dbc2068af7eda3aa87634eba216dbda64d1ae73c8684b621d33f6596", size = 2017325, upload-time = "2026-05-06T13:40:52.723Z" }, + { url = "https://files.pythonhosted.org/packages/9d/1d/8987ad40f65ae1432753072f214fb5c74fe47ffbd0698bb9cbbb585664f8/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:1d8ba486450b14f3b1d63bc521d410ec7565e52f887b9fb671791886436a42f7", size = 2095527, upload-time = "2026-05-06T13:39:52.283Z" }, + { url = "https://files.pythonhosted.org/packages/64/d3/84c282a7eee1d3ac4c0377546ef5a1ea436ce26840d9ac3b7ed54a377507/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:3009f12e4e90b7f88b4f9adb1b0c4a3d58fe7820f3238c190047209d148026df", size = 1936024, upload-time = "2026-05-06T13:40:15.671Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ca/eac61596cdeb4d7e174d3dc0bd8a6238f14f75f97a24e7b7db4c7e7340a0/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad785e92e6dc634c21555edc8bd6b64957ab844541bcb96a1366c202951ae526", size = 1990696, upload-time = "2026-05-06T13:38:34.717Z" }, + { url = "https://files.pythonhosted.org/packages/fa/c3/7c8b240552251faf6b3a957db200fcfbbcec36763c050428b601e0c9b83b/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00c603d540afdd6b80eb39f078f33ebd46211f02f33e34a32d9f053bba711de0", size = 2147590, upload-time = "2026-05-06T13:39:29.883Z" }, ] [[package]] @@ -613,6 +959,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293, upload-time = "2025-01-06T17:26:25.553Z" }, ] +[[package]] +name = "pyjwt" +version = "2.13.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3b/81/58d0ac84e1ef3a3843791d6954d94c0b33d526c75eeb1efbce9d0a4c4077/pyjwt-2.13.0.tar.gz", hash = "sha256:41571c89ca91598c79e8ef18a2d07367d4810fbbd6f637794879baf1b7703423", size = 107515, upload-time = "2026-05-21T19:54:36.618Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/5e/ecf12fdb62546d64385c158514e9b2b671f7832108ef2ecd2020ce0af2d1/pyjwt-2.13.0-py3-none-any.whl", hash = "sha256:66adcc2aff09b3f1bbd95fc1e1577df8ac8723c978552fd43304c8a290ac5728", size = 31274, upload-time = "2026-05-21T19:54:35.362Z" }, +] + +[package.optional-dependencies] +crypto = [ + { name = "cryptography" }, +] + [[package]] name = "python-dotenv" version = "1.0.1" @@ -622,6 +982,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6a/3e/b68c118422ec867fa7ab88444e1274aa40681c606d59ac27de5a5588f082/python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a", size = 19863, upload-time = "2024-01-23T06:32:58.246Z" }, ] +[[package]] +name = "python-multipart" +version = "0.0.32" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5b/42/55c32bb9b12693c092ad250a0e82edb5b31ddeda6eb772de5f308b3804ad/python_multipart-0.0.32.tar.gz", hash = "sha256:be54b7f3fa167bb83e4fcd936b887b708f4e57fe75911c02aebf53efaf8d938e", size = 46881, upload-time = "2026-06-04T16:18:58.647Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/04/e8135ebd1ad02c56ec633277529b2602ff99ff634be76cdba5744cf554fd/python_multipart-0.0.32-py3-none-any.whl", hash = "sha256:ff6d3f776f16878c894e52e107296ffc890e913c611b1a4ec6c44e2821fe2e23", size = 30042, upload-time = "2026-06-04T16:18:57.319Z" }, +] + [[package]] name = "pytokens" version = "0.4.1" @@ -651,6 +1020,85 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c6/78/397db326746f0a342855b81216ae1f0a32965deccfd7c830a2dbc66d2483/pytokens-0.4.1-py3-none-any.whl", hash = "sha256:26cef14744a8385f35d0e095dc8b3a7583f6c953c2e3d269c7f82484bf5ad2de", size = 13729, upload-time = "2026-01-30T01:03:45.029Z" }, ] +[[package]] +name = "pywin32" +version = "312" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/ff/32aa7d2ed0ab12b323aaa64f9b75e6ad4f8fd09f9ccfc28c79414d46838d/pywin32-312-cp312-cp312-win32.whl", hash = "sha256:dab4f65ac9c4e48400a2a0530c46c3c579cd5905ecd11b80692373915269208b", size = 6371877, upload-time = "2026-06-04T07:49:28.836Z" }, + { url = "https://files.pythonhosted.org/packages/03/d9/77040d3b43df3f3be32ea289433d660d2727f5ba327bc73be835127d9d60/pywin32-312-cp312-cp312-win_amd64.whl", hash = "sha256:b457f6d628a47e8a7346ce22acb7e1a46a4a78b52e1d17e1af56871bd19a93bc", size = 6914841, upload-time = "2026-06-04T07:49:31.85Z" }, + { url = "https://files.pythonhosted.org/packages/e3/cc/7b1ec671775756020a0ee7f4feeaf3c568f0ab86bd3900088cf986937a92/pywin32-312-cp312-cp312-win_arm64.whl", hash = "sha256:6017c58e12f6809fbb0555b75df144c2922a9ffd18e4b9b5afa863b6c1a9d950", size = 6727901, upload-time = "2026-06-04T07:49:34.244Z" }, + { url = "https://files.pythonhosted.org/packages/2d/41/12fbfd7f36ed2146d8bc9de96c2741296bf0d490b98508496cff322e274c/pywin32-312-cp313-cp313-win32.whl", hash = "sha256:7a27df850933d16a8eabfbaeb73d52b273e2da667f80d70b01a89d1f6828d02c", size = 6370184, upload-time = "2026-06-04T07:49:36.253Z" }, + { url = "https://files.pythonhosted.org/packages/ba/db/36a78e3403099d31d9746d13fdcde5accc43c1155f375a34d15983a479a7/pywin32-312-cp313-cp313-win_amd64.whl", hash = "sha256:c53e878d15a1c44788082bfe712a905433473aa38f86375b7cf8b45e3acbaaf9", size = 6914298, upload-time = "2026-06-04T07:49:38.876Z" }, + { url = "https://files.pythonhosted.org/packages/84/37/c1697194092b76de9ed47ca124323f02c57ffc8a45c06f88a3d5acaf01eb/pywin32-312-cp313-cp313-win_arm64.whl", hash = "sha256:59aba5d5940842075343a5ddc6b11f1cdf0d1567fe745290359dfbcc7c2eb831", size = 6727640, upload-time = "2026-06-04T07:49:41.083Z" }, + { url = "https://files.pythonhosted.org/packages/fc/2b/1f3cded5822fd49c02f40544cbb5f58c7cfd6b1694869fd476cb6170ee97/pywin32-312-cp314-cp314-win32.whl", hash = "sha256:a77a90fbb6881238d2ca9c6fd797b25817f3768fe78d214a90137ff055a75f5b", size = 6468928, upload-time = "2026-06-04T07:49:43.188Z" }, + { url = "https://files.pythonhosted.org/packages/21/82/3bf86d2e2808902013132e1ce905a7da0da53790f3836c64bf44d55e24f3/pywin32-312-cp314-cp314-win_amd64.whl", hash = "sha256:a4dd3a848290ef724347b19f301045831d8e802fa4464f491b98b1e0a081432e", size = 7024157, upload-time = "2026-06-04T07:49:45.34Z" }, + { url = "https://files.pythonhosted.org/packages/a4/0e/73f6d6800b4f27655abd9e9f6aaeaefcddb2b946e4674efa2bab184a7f7b/pywin32-312-cp314-cp314-win_arm64.whl", hash = "sha256:9fce94568364e0155e6dfb781ac5d95903be8baf28670632beab1b523f300daa", size = 6839598, upload-time = "2026-06-04T07:49:47.613Z" }, + { url = "https://files.pythonhosted.org/packages/eb/61/caa39686032d2ebdd04ff0ab5cbe163126c0066d98e00c9018646e42393b/pywin32-312-cp315-cp315-win32.whl", hash = "sha256:5c1fbe4a937a73ae9297384a3da38518cbc694c68ad8a809b2e19acd350f03ed", size = 6471159, upload-time = "2026-06-04T07:49:50.035Z" }, + { url = "https://files.pythonhosted.org/packages/0f/cd/7e1de64a4a6f69c04214169657ccab0d93a670ea50e35eb8f489d7378249/pywin32-312-cp315-cp315-win_amd64.whl", hash = "sha256:c2f03a0f73f804a13c2735b99392b0cd426bb4f2c4d0178e5ac966a0f21618d5", size = 7025293, upload-time = "2026-06-04T07:49:54.857Z" }, + { url = "https://files.pythonhosted.org/packages/23/ed/4532e9388e65fa16b46776ef47ad631a64eda1631884488af707666350ed/pywin32-312-cp315-cp315-win_arm64.whl", hash = "sha256:a8597d28f267b39074aef51fa593530082b39cbe5a074226096857b1fed2dfb9", size = 6840337, upload-time = "2026-06-04T07:49:57.531Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, +] + +[[package]] +name = "referencing" +version = "0.37.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "rpds-py" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" }, +] + [[package]] name = "requests" version = "2.32.3" @@ -679,6 +1127,141 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/19/71/39c7c0d87f8d4e6c020a393182060eaefeeae6c01dab6a84ec346f2567df/rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90", size = 242424, upload-time = "2024-11-01T16:43:55.817Z" }, ] +[[package]] +name = "rpds-py" +version = "2026.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/43/25a8dcd3feedd735039a8f0b5b7e3b118232b5eae288c4fd9ab200d41094/rpds_py-2026.5.1.tar.gz", hash = "sha256:07b24fea40541e28570e5b795a4a38fbdcd12550c06bd0748005ecc8116ca256", size = 64459, upload-time = "2026-05-28T12:02:13.232Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/e7/a78582dc57caa592dcc7d4fb69b61390561e908eb3d2f5df5928a8e354c0/rpds_py-2026.5.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3abe24a66e57adcfa645d718063a5fa5103ecc71ddbf26d78af8f9368018ff1d", size = 353040, upload-time = "2026-05-28T11:59:12.531Z" }, + { url = "https://files.pythonhosted.org/packages/a3/43/35e3f136343aef451e545ce8c38d36c2f93c0ed88703db8b64ba2b205c68/rpds_py-2026.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:58b1d94308ddf0b1982f61f2eb54bf92997c9ece8a8093ef014250f4a517906c", size = 345775, upload-time = "2026-05-28T11:59:13.827Z" }, + { url = "https://files.pythonhosted.org/packages/20/e1/0f2160c5982d3157734d5cb3ed63d8b2d583a73c9864f77b666449f32cf8/rpds_py-2026.5.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fa92420128dadce7f54bd73ba1825a273e9268fe9e35dbf7e6362890efa4e08", size = 376329, upload-time = "2026-05-28T11:59:15.271Z" }, + { url = "https://files.pythonhosted.org/packages/d0/11/ee0ba42aff83bf4effdbc576673c6be64c5e173978c3f6d537e94482f77d/rpds_py-2026.5.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ca653c6546386227cd9800d1bef6a348099acf8db4250341da6d90f663d6dfcb", size = 383539, upload-time = "2026-05-28T11:59:16.665Z" }, + { url = "https://files.pythonhosted.org/packages/11/df/d94aa6a499d4ac40afe2d7620f2c597fd3c0f182e854ad7cf3f596a81cb6/rpds_py-2026.5.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:66c93681c4729e4e3ecba31b8179fae083ff3118841672835140338b4b9867c1", size = 494674, upload-time = "2026-05-28T11:59:17.991Z" }, + { url = "https://files.pythonhosted.org/packages/1f/75/33d30f43bb2f458de11979486a591b1bf6e5651765ed1704c6197c2dc773/rpds_py-2026.5.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40ff257542e04796880e011e15cd4dc21c2599975df2aaa8f2c8495ca574e1a5", size = 389268, upload-time = "2026-05-28T11:59:19.434Z" }, + { url = "https://files.pythonhosted.org/packages/f4/1e/2c9096fc19d5fd084b0184ca2b651e659aa0a37e6fdbecf6ece47f147fe1/rpds_py-2026.5.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6825cc329b290e93c5f6a9be2393118a763f6ccf6abd83704e0c102ca583644", size = 376280, upload-time = "2026-05-28T11:59:21Z" }, + { url = "https://files.pythonhosted.org/packages/b9/e5/61ec9f8be8211ea7f48448195549e4aaf02004083475493b0e137702ecb2/rpds_py-2026.5.1-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:de42116e69cb53b911cc34aee5ab98f36c597b822545045d49e938818b99e5e4", size = 387233, upload-time = "2026-05-28T11:59:22.454Z" }, + { url = "https://files.pythonhosted.org/packages/0d/ca/bcec1005c4f4a234f92a29078631fee49206c7265ccae966f18fd332e80e/rpds_py-2026.5.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c0f920015df2a504bebaba6d4c31ccf3fcf942f92655c086da30b671aad19aa6", size = 405009, upload-time = "2026-05-28T11:59:23.845Z" }, + { url = "https://files.pythonhosted.org/packages/72/e6/4d5718c5cf26c522dc7c9999e238da1e77380b81d0c5d1df11e271ddfeb1/rpds_py-2026.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0408a24e44feb919423dc6d9da677cb5cddb894d2ca9e763967d156d9c60fab4", size = 553113, upload-time = "2026-05-28T11:59:25.184Z" }, + { url = "https://files.pythonhosted.org/packages/d4/25/2ee807bdb3e1f0b7eddf7782acd5665a8b5205a331a7d7244a52c4812fd9/rpds_py-2026.5.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cea68bcd53467561ae2f96a6bdad1544299ba97b5b0ddcd5ac3d376e5c781c24", size = 618838, upload-time = "2026-05-28T11:59:26.749Z" }, + { url = "https://files.pythonhosted.org/packages/6a/c1/7d4c26f167f8c41501cc073d30ee22082b16ce358cf5b00ec97cbc7804ea/rpds_py-2026.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4be8b1d2a705cc37d08256004e1d07de143fa0075c8e85a3df020b776f62b732", size = 582436, upload-time = "2026-05-28T11:59:28.11Z" }, + { url = "https://files.pythonhosted.org/packages/04/1d/9d12b0a337bab46f4769f8857f4007e3b2d639e14f9a44a0efe157696e64/rpds_py-2026.5.1-cp312-cp312-win32.whl", hash = "sha256:6736718bd4fc49cbcb538ba30516fdbef161522acefb739657d48b97bd864fed", size = 212734, upload-time = "2026-05-28T11:59:29.689Z" }, + { url = "https://files.pythonhosted.org/packages/c5/93/e4116f2de7f56bc7406a76033dc501811ddeb22b7f056b92d632871ebb0c/rpds_py-2026.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:0a7d1eec967df0e9b22614a5e177622e0c89611d03727fa0cb48e45028907870", size = 229045, upload-time = "2026-05-28T11:59:31.033Z" }, + { url = "https://files.pythonhosted.org/packages/cb/53/6c3419d85eb2ec5938a37627c585b42d76a63bb731d6e42ed4b079ebf486/rpds_py-2026.5.1-cp312-cp312-win_arm64.whl", hash = "sha256:1841d067089e117142d79b98aa0df2f08b52f2ecc1819dd2700636c0db74a473", size = 223967, upload-time = "2026-05-28T11:59:32.318Z" }, + { url = "https://files.pythonhosted.org/packages/6c/32/14c961ad295f490eb0849ada8b79683e93a59b9de3afdd983eaf55fa6867/rpds_py-2026.5.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:efef4ac29c6ff495531eb17ee705b62841ecaa291b7c7077e848ea03e237164d", size = 352787, upload-time = "2026-05-28T11:59:33.655Z" }, + { url = "https://files.pythonhosted.org/packages/ca/bb/d1b85117967c11191441a7274ae616c65d93901d082c588f89a50a8da5ae/rpds_py-2026.5.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c39f5b67a8a2e67179ada2a954227d670fe65fa9098457f698f56ddf248709b3", size = 345179, upload-time = "2026-05-28T11:59:35Z" }, + { url = "https://files.pythonhosted.org/packages/7c/46/d84105f062e626a1b233f863907288a4708c2d833b8b4c6fb2764bc080c0/rpds_py-2026.5.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5c30f3f04eef4fbd362226a6f31d7c8895ca4fbb6e0b790f6890a98d8da8559", size = 376173, upload-time = "2026-05-28T11:59:36.43Z" }, + { url = "https://files.pythonhosted.org/packages/e2/ae/469d7959ce5b1201e1de135dc735b86db3b35dd0d1734f6a44246d5f061c/rpds_py-2026.5.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:277f6c82f0580848796c7ecc8a7173aa3bfb928e4ff831261c2f60a81dc270db", size = 383162, upload-time = "2026-05-28T11:59:37.995Z" }, + { url = "https://files.pythonhosted.org/packages/dc/a2/57853d31a1116a561aa072794602ad3f6341e18d70a8523f1bd5b9fc1e5a/rpds_py-2026.5.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:63c2c4c213f1a4e3f3de28ecab029dbdee976324e729c0d7a55211be72576b02", size = 495093, upload-time = "2026-05-28T11:59:39.453Z" }, + { url = "https://files.pythonhosted.org/packages/99/63/3a8eabcad9314b7daf5c65f451d2c33d989235cd8a5762186cf2c3f5a4f8/rpds_py-2026.5.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3350ec808fb538fe71a1f94dfaa0e29c598dfad805ce49f0caec5ae3183c652b", size = 389829, upload-time = "2026-05-28T11:59:40.896Z" }, + { url = "https://files.pythonhosted.org/packages/4b/25/05678d97fc25e2622df14dc530fb82023174ecfff6733991ed0d78f167bd/rpds_py-2026.5.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1b964e3ab599e718dc46c018d104b1ebc007cbc6567d827c94a687fca56d77e", size = 374786, upload-time = "2026-05-28T11:59:42.626Z" }, + { url = "https://files.pythonhosted.org/packages/88/d1/8c90b6431e80a3b91b284a5c7c8c0c4f9c006444d90477a740d6e0f9c694/rpds_py-2026.5.1-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:19cb09fab7b7fc96b2a6e28f2e34b72a3705ff27b37edb77455316e5d3f3dc9b", size = 386920, upload-time = "2026-05-28T11:59:44.124Z" }, + { url = "https://files.pythonhosted.org/packages/ff/99/4638f672ab356682d633ee0da9255f5b67ce6efd0b85eb94ad3e255e65a5/rpds_py-2026.5.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:abe76bcdba31e576cb83eeb8797aa0d882b738fef6dc65d0601fc753806a5b46", size = 405059, upload-time = "2026-05-28T11:59:47.177Z" }, + { url = "https://files.pythonhosted.org/packages/66/3f/3546524b6eb4cc2e1f363a3d638fa52f6c24faae3500c25fb488b02f1740/rpds_py-2026.5.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8bff7073db3899158fff55ebf57b113a67030af26f80a18978f9f0aa60250ddf", size = 553030, upload-time = "2026-05-28T11:59:48.603Z" }, + { url = "https://files.pythonhosted.org/packages/c6/c3/7b3388c796fcf471bd17194242d4dc1a7608567c0fa422bcc1c5e79f9c1e/rpds_py-2026.5.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8ba264fa49be666cd9cc56bf34ec7002fb3d27a4aee5bcb4d43d0d18feb1bb6f", size = 618975, upload-time = "2026-05-28T11:59:50.314Z" }, + { url = "https://files.pythonhosted.org/packages/61/1e/a3cb07f2795075d1d88efddae2f541359fde5f08c81ee114c29c2949c90a/rpds_py-2026.5.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4860b603ddda0475a8885499b3729e90229d480105b42651962a5397d995fa89", size = 581178, upload-time = "2026-05-28T11:59:51.673Z" }, + { url = "https://files.pythonhosted.org/packages/a1/74/e758c03a5ef46f04c37f2651a2893db846d569ba8a7bca469d4b58939bcd/rpds_py-2026.5.1-cp313-cp313-win32.whl", hash = "sha256:7944270ae71383f6e2657dd7d5ce4eeb4ac2d0059a6738f0510583d462ab4842", size = 212481, upload-time = "2026-05-28T11:59:53.148Z" }, + { url = "https://files.pythonhosted.org/packages/70/ec/a2aca432db9c7359b40fa393eeeaa0d166c2f70175be956e75fa24197c44/rpds_py-2026.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:88647f43a73c4e01be19b04ceef0c8d3a1958153604d13c773becd8016f2a0cf", size = 228519, upload-time = "2026-05-28T11:59:54.505Z" }, + { url = "https://files.pythonhosted.org/packages/29/60/a73bfdd45b096574556acf303bbd9fa9eed36ca8a818b514e2a5d5fe2b9d/rpds_py-2026.5.1-cp313-cp313-win_arm64.whl", hash = "sha256:453895624ecf7db7063b1004e44037522bbaef9ff6a945e59bc71662d7a03abd", size = 223446, upload-time = "2026-05-28T11:59:56.081Z" }, + { url = "https://files.pythonhosted.org/packages/18/e2/408105fd611823f00882aea810f3989a30d26b1bab8b6beb20f98c724e0e/rpds_py-2026.5.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:b4e4bc98639ec915f512fde3aa7a95e0041d95d9c3cc86eea841fa63cb1e8600", size = 355287, upload-time = "2026-05-28T11:59:57.448Z" }, + { url = "https://files.pythonhosted.org/packages/8d/58/5c4a43436843c90d0f6d19f82c200c80e3843ca9fa07b237623327f6d384/rpds_py-2026.5.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cacedb7a6e167680acba45ad5716e89067d225dc80da0d7040cae8c81d4572fa", size = 347033, upload-time = "2026-05-28T11:59:58.881Z" }, + { url = "https://files.pythonhosted.org/packages/fb/c2/1a71acdacaf4e259b10278fb87b039ded3cf80041bcd89dd8a3ea702ded6/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68700371c5d7ae1412862ddfa719090925c93ecf351c566d66f09d04b136ea00", size = 376891, upload-time = "2026-05-28T12:00:00.516Z" }, + { url = "https://files.pythonhosted.org/packages/c2/c8/535f3d9b65addd8e28aa87b83c6e526799c3717a88273db8ea795beeef7a/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:296c799becfa849c779c8725494fe9ed94959ed886787df4364b058465bad7f0", size = 385646, upload-time = "2026-05-28T12:00:02.394Z" }, + { url = "https://files.pythonhosted.org/packages/1c/91/dc033f313345c354ade914dbe73cdb90b615a4409ea02430d5356794f3d8/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d3858b908218ee108d0bbfb2095ccc237648053c9bf98affad7cb079acaf1d97", size = 498830, upload-time = "2026-05-28T12:00:04.189Z" }, + { url = "https://files.pythonhosted.org/packages/27/fc/90fcbea459dbb8ddc18a2e0fd1de9412b48bc84ffff2db771cf714bacfd6/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4fb8d2e7cb2f850b169806d61d1b991738acec96500a75c30f49caf064ce7cef", size = 392830, upload-time = "2026-05-28T12:00:05.797Z" }, + { url = "https://files.pythonhosted.org/packages/b2/1d/46cd11a228c9750684a798d98f878be6f614aa762438da7378f035e79e35/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27b74c10ed6a8f190f4287f53bcfea348b92a84a9c9f70d30183d1e6172d580d", size = 379613, upload-time = "2026-05-28T12:00:07.433Z" }, + { url = "https://files.pythonhosted.org/packages/24/4a/d9b0c6af3a1de03eb93741bbe8be2bdce84d8fda8224f3005451d86df389/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:b9a6528956191c48c52294a592dbd4a8386d7048bdb25c0efcb6b966466c6d83", size = 388183, upload-time = "2026-05-28T12:00:09.227Z" }, + { url = "https://files.pythonhosted.org/packages/c5/b4/db7aaabdda6d020afc87d981bcc2f57a434c7dec60ecfc2ab3dd50b20351/rpds_py-2026.5.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:af03e34e860047bc7a352b842856fcf78798fbb81132cc98bd2f907ab4eb9cd2", size = 408578, upload-time = "2026-05-28T12:00:10.779Z" }, + { url = "https://files.pythonhosted.org/packages/08/d6/070f6a41cbb343e2ac4171859bf3f3623e0ab002f72619d6d505313ec2de/rpds_py-2026.5.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:fea6e836d10abbe191d557d33bd58bd5987725fe63aa1eefe557d230209855bd", size = 553573, upload-time = "2026-05-28T12:00:12.443Z" }, + { url = "https://files.pythonhosted.org/packages/75/ab/1a71ea3589c4345dac0a0518f0e6a031cb42689277851b683c46d27463a5/rpds_py-2026.5.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:fc0c0f878ea770a0a8a462456c5ad36fc9fe6358e6b76fdadc7f17575e0b8bf1", size = 620861, upload-time = "2026-05-28T12:00:14.09Z" }, + { url = "https://files.pythonhosted.org/packages/8a/22/9bf80a56069c0c443fcfefac639a86a744550a2898817a6dfd3e26654924/rpds_py-2026.5.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e0b360f316d966b048b085857630b3cc51f3db2f07b06f440eac8f695374d1e3", size = 585633, upload-time = "2026-05-28T12:00:15.66Z" }, + { url = "https://files.pythonhosted.org/packages/da/68/3b2c0a75c9e04125696f84ebdbbf304acf5a40b58ba4481cdb98a922c3ba/rpds_py-2026.5.1-cp313-cp313t-win32.whl", hash = "sha256:a2999883eedf72fdfb7520b92c7d4ec2572a71ff40239377aa604cc529eecafc", size = 210074, upload-time = "2026-05-28T12:00:17.291Z" }, + { url = "https://files.pythonhosted.org/packages/e7/8b/609157d5a25d37d4f29f92840ba531f416907c34ae5c5739dd21fc2bef98/rpds_py-2026.5.1-cp313-cp313t-win_amd64.whl", hash = "sha256:e07be2a9d7122bd6e82dea89814ef8dc893feb1aae97fec1630f3263bbb30e55", size = 228635, upload-time = "2026-05-28T12:00:18.73Z" }, + { url = "https://files.pythonhosted.org/packages/d4/6f/19c1918a4b590d8de87e712e4abe4b3875771eff60216fb6153cf6665c68/rpds_py-2026.5.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:1f2c391c3059798093b65df23aca2cac150460ae9c630d99dec83d703d9485b9", size = 349756, upload-time = "2026-05-28T12:00:20.217Z" }, + { url = "https://files.pythonhosted.org/packages/e5/60/a06fe7da34eca79dacbf958a2ba0c6eea85bc2b29de20080bf40f72f66fa/rpds_py-2026.5.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:413b424f7c4ee65ab5e5be91f5731be0f8b41a1ee2b12dfe810d716312e95a78", size = 343831, upload-time = "2026-05-28T12:00:21.711Z" }, + { url = "https://files.pythonhosted.org/packages/bf/ec/b2333b97b90e2a6ef6ca8ad386ee284968e74bcfe113b3f1a8d9036429a9/rpds_py-2026.5.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c595a1d9255dce0599e13130d1440ab2506654f2b50294226ee06402f8fef63", size = 375127, upload-time = "2026-05-28T12:00:23.326Z" }, + { url = "https://files.pythonhosted.org/packages/14/7f/e00aae54067f2b488c4637961d5f58204d470795fc791085fa3f15060d2e/rpds_py-2026.5.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1c27c5f6102eac8c03e7595a00827a53b271ba40a53b59ff8709170e0855ea4a", size = 379034, upload-time = "2026-05-28T12:00:24.89Z" }, + { url = "https://files.pythonhosted.org/packages/be/cc/423999bbb8ae8dc93c77fc1d5e984ade5eb89d237d3bb884ccfa72ae2890/rpds_py-2026.5.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6c7fcf61d44cacecaf3aea542b0e053db77972a4573e7ceda16fb2b399161195", size = 490823, upload-time = "2026-05-28T12:00:26.676Z" }, + { url = "https://files.pythonhosted.org/packages/0f/aa/c671bf660f12e68d3c52ff86c7066ed1372df5a0f4f2ff584e419b8207e7/rpds_py-2026.5.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2c817a189d4ee14290420e5ff051e4dd6baa13f3edf84685071dee07a6d538ee", size = 388144, upload-time = "2026-05-28T12:00:28.577Z" }, + { url = "https://files.pythonhosted.org/packages/19/c8/d63bb75b68afe77b229e3021c6031bcaf01da5db5b0e69d0d10f9ba679a7/rpds_py-2026.5.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21846aac0ed2e0589f38c12dc44e77bb64e494b771eadbcf169cba00566ba7ba", size = 371959, upload-time = "2026-05-28T12:00:30.304Z" }, + { url = "https://files.pythonhosted.org/packages/82/35/c51122014d8274ff37dc606d60049c3db7d83da02b5b282511e5a906a9a6/rpds_py-2026.5.1-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:b317c87a13f769a4e787819bd508aaa5d69aa09b0880de9af6d3a8a54571cdec", size = 383558, upload-time = "2026-05-28T12:00:31.764Z" }, + { url = "https://files.pythonhosted.org/packages/e3/f9/2790cb99c136a5363acdeacf5c27c56f3de0d4118a1f48fca83404c99c89/rpds_py-2026.5.1-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ce87129d9f2c14fa6c4a8601fb80eb4488c80d38a20cd13758ef11123e14995d", size = 402789, upload-time = "2026-05-28T12:00:33.247Z" }, + { url = "https://files.pythonhosted.org/packages/e5/1b/e4fb584f8c75d35c38150ff6a332cda949e6f97acba1f4fd123b14ab56fe/rpds_py-2026.5.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9cdddb6c1207d284d94fd1530adf57fbd797fe7c4b8704ba85f49414f2557e7d", size = 551405, upload-time = "2026-05-28T12:00:34.819Z" }, + { url = "https://files.pythonhosted.org/packages/d8/f7/a6731b4216cb3793ea1af5391da240f5683dacc0d13e034fe5fc3503f240/rpds_py-2026.5.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:4e237e139f94d3c036fd28eb9f564c99055476ff4ff05cd42be55ce349b5aa02", size = 616975, upload-time = "2026-05-28T12:00:36.268Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ea/2e051a81d95d8e63f4b35a1c463a87e8766bc3d083c067c5dfb6bf220747/rpds_py-2026.5.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ed0954b524873214369184a9c82b0eaa45a3fbb9a798cd95b17e0d98499e7ea0", size = 578701, upload-time = "2026-05-28T12:00:37.82Z" }, + { url = "https://files.pythonhosted.org/packages/65/56/b5f6fdb2083e32bca8a8993d89e70db114b4756c9e2c38421328126689d2/rpds_py-2026.5.1-cp314-cp314-win32.whl", hash = "sha256:2d88621d6a7d4dfa633d21abe90f280bb205274e16b1d1e61c6ad4640b2453b7", size = 209806, upload-time = "2026-05-28T12:00:39.492Z" }, + { url = "https://files.pythonhosted.org/packages/fb/80/65a5aa96c155e611d1ed844e4e1f57f3e36b021f396d9f8585d756e6b90d/rpds_py-2026.5.1-cp314-cp314-win_amd64.whl", hash = "sha256:cef8ac28d26f4dda3533060c20fbf80a325458fa9fd23ea72a73cdfa8e978838", size = 225985, upload-time = "2026-05-28T12:00:40.94Z" }, + { url = "https://files.pythonhosted.org/packages/27/7c/ad185212e87b05f196daef92bc5f3caf07298eb47c295b5585c3dd3093ac/rpds_py-2026.5.1-cp314-cp314-win_arm64.whl", hash = "sha256:eaaea962c68cdc68d4a533ba985ab8e9484277910bbfaa2ab3ef7732667bfed8", size = 221219, upload-time = "2026-05-28T12:00:43.15Z" }, + { url = "https://files.pythonhosted.org/packages/23/58/e14ae18759020334646b031e708ab4158d653a938822bfb7b95ef2e93aa3/rpds_py-2026.5.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:21942f52dbbd5f8758bf021213d28bd45c39e873e65e2407faf5f1846f5761ad", size = 352148, upload-time = "2026-05-28T12:00:44.638Z" }, + { url = "https://files.pythonhosted.org/packages/31/9b/5f4a1e2f960bca3ac5d052b139dd31eed97b259f9d909173821760d542e8/rpds_py-2026.5.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f414556f6e3958300ff941e40c9f97e3dc9774ddd1b3434c475d73dd354bbed3", size = 345196, upload-time = "2026-05-28T12:00:46.14Z" }, + { url = "https://files.pythonhosted.org/packages/1a/71/1d9574d6a2fa20ab60eaa55c7467f5aa20cbc770f341a05f09c0876f59e2/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef1013a8625c74043210190b246f5b1551e09757c1f356c6e4160ef96c5bc081", size = 374981, upload-time = "2026-05-28T12:00:47.531Z" }, + { url = "https://files.pythonhosted.org/packages/0c/9a/37e99f4915a80aa71670263c1267f7ae0af95f53a3f61e6c3bdc016d4515/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cc68e231a77a5f0d774ae278a1f8e55c0456501820847c1e4efb3829f3441df6", size = 379961, upload-time = "2026-05-28T12:00:49.216Z" }, + { url = "https://files.pythonhosted.org/packages/a8/ff/6e73f74b89d2e0715e0fc86b7dde893f9a61ae2f9b256ff3bdfe41ac4e94/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9baffb505aff33acc69b422a19f77806680f3c8632227d79f48de8a810d1c2c5", size = 495965, upload-time = "2026-05-28T12:00:51.111Z" }, + { url = "https://files.pythonhosted.org/packages/ea/e0/425faba25f59d74d4638b267f7c7a80e8649d2ef4db10a19b0c4a71e6e6f/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b8d2f912928d426e8cfa396f7f3f8d29a59e6689c86dcca3c420730c1096322b", size = 389526, upload-time = "2026-05-28T12:00:52.77Z" }, + { url = "https://files.pythonhosted.org/packages/c6/76/7a41960e3fddae47fab43a28684d5da981401dffd88253de0944148654cb/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90f628283be835db980c941767d41c9a27b5239e54ba0a9c1335247e82406964", size = 376190, upload-time = "2026-05-28T12:00:54.215Z" }, + { url = "https://files.pythonhosted.org/packages/27/60/5f38dc70824fc6951b51d35377e577a3a3a4c81a6769cc5a2de25ebe0ad1/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:1ebb2f0ab7e16132995a72de805170e0203df0c3dd22e1ef1cd1fdd90bd7a131", size = 383921, upload-time = "2026-05-28T12:00:55.673Z" }, + { url = "https://files.pythonhosted.org/packages/60/1a/d60a38caa1505f4b9483c3fbbde12c94e1079154f4f401a6da96f7e77621/rpds_py-2026.5.1-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f3df3d16ded76f1f8c9cdebd0e1ea55fdf4c23b812de189814da7cf229c22a81", size = 404766, upload-time = "2026-05-28T12:00:57.518Z" }, + { url = "https://files.pythonhosted.org/packages/87/ff/602fd3f174d6425f0bce05ad0dfbec0e96b38d0f7d08a79af5aa20083885/rpds_py-2026.5.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9af8905b8f854990e40d5206aa5ac58d9b0fe0b7f351ff2bb086c20f6c8c6a47", size = 551343, upload-time = "2026-05-28T12:00:58.978Z" }, + { url = "https://files.pythonhosted.org/packages/b8/c1/1be13327acdbead3eca1fde03b6a34dbb011f1e864e217f0d32cc1779a7f/rpds_py-2026.5.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:036a36a87fb1cd3b214d11c4b3c4f7d2ddad933625dca1c900b56a057c07740a", size = 618502, upload-time = "2026-05-28T12:01:00.656Z" }, + { url = "https://files.pythonhosted.org/packages/f3/d7/afb49b49d7f2be8b7ba1a9f0977fa5168003437b93086726f066544e8351/rpds_py-2026.5.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:62ae3853454fe9ef283a03c96c2d835d39e84b14643a9d62c82ef0fb87d702ca", size = 581916, upload-time = "2026-05-28T12:01:02.22Z" }, + { url = "https://files.pythonhosted.org/packages/25/d1/dbef8c1f8a10f07beb62b5f054e20099fd9924b3ec001b8f0b6ac7813a85/rpds_py-2026.5.1-cp314-cp314t-win32.whl", hash = "sha256:6c3d771a46ec18b12af06ce36243a9a80b07a5d0515236332d90863ca8bb326a", size = 207855, upload-time = "2026-05-28T12:01:03.821Z" }, + { url = "https://files.pythonhosted.org/packages/2a/72/bfa4e61ab8e7dc1c8adf397e05e6cbdd4239357bd72b248d3de662f23915/rpds_py-2026.5.1-cp314-cp314t-win_amd64.whl", hash = "sha256:c93c629be4636cf54337bd5f06c104d55e42ced54d681f6fe21ae510a65116f6", size = 225422, upload-time = "2026-05-28T12:01:05.194Z" }, + { url = "https://files.pythonhosted.org/packages/27/3a/7b5da92b640f67b6717ccafc83cdd06bfa7ff2395c3685c68922bb54d703/rpds_py-2026.5.1-cp315-cp315-macosx_10_12_x86_64.whl", hash = "sha256:3574b55c604b8f75dacb007136508bbc0db406e626301778096a133327e7f2fb", size = 349576, upload-time = "2026-05-28T12:01:06.722Z" }, + { url = "https://files.pythonhosted.org/packages/d7/8a/2aafd7ad355a1bd48ca76e2262b74b15e6432b5a1efe150efd4d779cd55d/rpds_py-2026.5.1-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:94068eb3ae6d43f5a786b7db96a406a34e6d5c24489feef32fd6e8946ea7b291", size = 343640, upload-time = "2026-05-28T12:01:08.441Z" }, + { url = "https://files.pythonhosted.org/packages/f7/7d/6c9523c1abbe840a1b7fba3c516d48e1d3487cc80fea4366c4071cf56784/rpds_py-2026.5.1-cp315-cp315-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3a5b10e8ce894825f380a8f1b6444cf73c294dfea62afbb2d13e3a9e630cec1", size = 375322, upload-time = "2026-05-28T12:01:09.934Z" }, + { url = "https://files.pythonhosted.org/packages/5a/5d/0b7b03fb1dc509321f01de3149784ab773e34c8573022029af8076afcb9c/rpds_py-2026.5.1-cp315-cp315-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fc09f82e63d4bcd58149572f857a431bae851dc747e313c3b5bdf7abb907fda8", size = 379066, upload-time = "2026-05-28T12:01:11.48Z" }, + { url = "https://files.pythonhosted.org/packages/d7/e2/8ef6012999ebf1cb1c22f876d9ce5e63d960fd4631d2af3202d3f480aa25/rpds_py-2026.5.1-cp315-cp315-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e10464d17df3b582745c25cec695cb9558bca2cb6ddb631aee1787fc72c767b2", size = 494586, upload-time = "2026-05-28T12:01:13.051Z" }, + { url = "https://files.pythonhosted.org/packages/80/af/1eeb029bec67582c226b7809172207cd005073af4ebd906e65ff494f4983/rpds_py-2026.5.1-cp315-cp315-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ba05adbf15d994c38ec0b7ab32e858e5110c21e9009a00a86545fd220f84e038", size = 388415, upload-time = "2026-05-28T12:01:14.631Z" }, + { url = "https://files.pythonhosted.org/packages/18/23/ffbe10711c4d766c1cab0557d6906c074f795814863c67b351355d29354a/rpds_py-2026.5.1-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77c004fdc7b891967106f78ddfd7b076bfe6813c6139c6fff6aed3bcaa960b26", size = 372427, upload-time = "2026-05-28T12:01:16.153Z" }, + { url = "https://files.pythonhosted.org/packages/bd/3a/30ba4a6ad457e5b070c18d742a33fb77d8d922b565cc881f8a5313d63bfe/rpds_py-2026.5.1-cp315-cp315-manylinux_2_31_riscv64.whl", hash = "sha256:83bcf894486c9d78dd290d3c0124ff6dd8875d3025e2090a8ec49fcc37c55fdd", size = 383615, upload-time = "2026-05-28T12:01:17.809Z" }, + { url = "https://files.pythonhosted.org/packages/d3/69/62e242b53ce39c0814bd24e1a6e6eba6c92be716277745f317f9540a2e7b/rpds_py-2026.5.1-cp315-cp315-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c3df104083952a0e0c6f10de33e440eabe98fb6317d23e1a58c68f6df08d01b9", size = 402786, upload-time = "2026-05-28T12:01:19.419Z" }, + { url = "https://files.pythonhosted.org/packages/38/c1/a770b9c186928a1ed0f7e6d7ae50e7f3950ed23e3f9e366dbc8e38cb55de/rpds_py-2026.5.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:980450826cf22e133c57e0835070bdd0dd3f73b9b708c3ce223def2cb9469e14", size = 551583, upload-time = "2026-05-28T12:01:21.013Z" }, + { url = "https://files.pythonhosted.org/packages/21/7c/68e8579b95375b70d2a963103c42e705856cdb98569258bd807f4423891c/rpds_py-2026.5.1-cp315-cp315-musllinux_1_2_i686.whl", hash = "sha256:205dde846f24332ab0c1188699a043b8d165b79bb84529ce272c45048ff6be01", size = 616941, upload-time = "2026-05-28T12:01:22.548Z" }, + { url = "https://files.pythonhosted.org/packages/70/a1/a6135aed5730ff03ab957182259987ac11e55fb392a28dc6f0592048a280/rpds_py-2026.5.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:3966b82dd563176396df030f3dd52a6e54cb69b718e95e78bd555ed3d1e0185d", size = 578349, upload-time = "2026-05-28T12:01:24.118Z" }, + { url = "https://files.pythonhosted.org/packages/09/6e/f24201a76a84e6c49d0bdfdfcb735210e21701e9b21c5bfc0ba497dd62f6/rpds_py-2026.5.1-cp315-cp315-win32.whl", hash = "sha256:7818f8d0a415be74d2be3590b0a1c1f463a642f4d0217e7d10602dceef5b79aa", size = 209922, upload-time = "2026-05-28T12:01:25.522Z" }, + { url = "https://files.pythonhosted.org/packages/9e/e4/966bc240bb0485fc265278f6de44d05834bf0b3618886e0b22e33d54c49a/rpds_py-2026.5.1-cp315-cp315-win_amd64.whl", hash = "sha256:b3cc20c0d800af78fd0fac68086e28c1856cec51ea528bb81ea851aa40d39325", size = 226003, upload-time = "2026-05-28T12:01:27.062Z" }, + { url = "https://files.pythonhosted.org/packages/5c/5c/a15a59269cd5e74472734516c73795c15eccfc841b3d4b0228c3f53f19d0/rpds_py-2026.5.1-cp315-cp315-win_arm64.whl", hash = "sha256:3609e9939a8a76cd904cf98a3f1f13b5dc7e150adeaee89e0ea09652ea213e16", size = 221245, upload-time = "2026-05-28T12:01:28.51Z" }, + { url = "https://files.pythonhosted.org/packages/e0/22/135ce03804e179a71ceb13be095deda4a279bc88f7a6b8fa161c5ad44e12/rpds_py-2026.5.1-cp315-cp315t-macosx_10_12_x86_64.whl", hash = "sha256:5d333a7127d4b307601ac37792bee01bb95c867cbfacf21b6375b804d6bbd723", size = 352015, upload-time = "2026-05-28T12:01:30.214Z" }, + { url = "https://files.pythonhosted.org/packages/3b/5f/f1f6d2652eb9d848f6eb369d8db83a2da6249bb49ad2c2a48f45d54538d3/rpds_py-2026.5.1-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:b5f077b44a4f7808520f66dae234988d867deb9aed9be5da057ce9ba831b2a41", size = 345016, upload-time = "2026-05-28T12:01:31.656Z" }, + { url = "https://files.pythonhosted.org/packages/88/66/b74182775691ea2290c99e52ac8d5db844e56fbec90ce421f107658c8314/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55d8f9b7b78c9538fc9e04e82ec0e888ff0c3cffcfad152c77e57cd09351a98a", size = 374775, upload-time = "2026-05-28T12:01:33.136Z" }, + { url = "https://files.pythonhosted.org/packages/ff/8f/15e5a61d9f0a43902d36561d4f07cae6ae9f4716be825159fd72717f33af/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e3a8ae58895ac107ed934a6bf51e5846f95c53b9b940c2c6d310838fd5846358", size = 380270, upload-time = "2026-05-28T12:01:34.574Z" }, + { url = "https://files.pythonhosted.org/packages/02/c3/f859b12763a80540cdf2af0f15b19904cf756a71d7bdd3f82ff3e5b1bbf9/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0957cf3c2b8632ec7aaebffebea8005b353cc2a237b6e2ae3c2cac0820704cfb", size = 495285, upload-time = "2026-05-28T12:01:36.127Z" }, + { url = "https://files.pythonhosted.org/packages/1c/c7/ff27c2ac8411d30b03b1829fd88cae8dad1a4d0da48dd25e57c4038042e6/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c396c1304de421050b3681ea70f371874b54d41b0151e96109758144c231e30b", size = 389581, upload-time = "2026-05-28T12:01:37.635Z" }, + { url = "https://files.pythonhosted.org/packages/6e/67/fe92ee32a6cc05c77228a2f8b1762e7124f386ec20ff83d0757b762d58d0/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aad1bff7f666b9598e573815affd666aac6a13a585dde336f843e33350c7fadc", size = 376041, upload-time = "2026-05-28T12:01:39.307Z" }, + { url = "https://files.pythonhosted.org/packages/f8/91/b4d6685c27aba55bd82f25b278be8237038117d05f9659a6213ad3408130/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_31_riscv64.whl", hash = "sha256:656a042550878f12d45752452d47094b7cfe5ad1e9d7b87b5a22ad3ae5ff8015", size = 383946, upload-time = "2026-05-28T12:01:41.043Z" }, + { url = "https://files.pythonhosted.org/packages/bd/79/2c1d832a53c8e0f8e98fc970ec257b950fecd4f62be2ab7182b500a0cbc8/rpds_py-2026.5.1-cp315-cp315t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:73c4bd4f70294737b5206a3e8e30ccadbf8a60301831c8ea23eec5dbeea1ecfa", size = 405526, upload-time = "2026-05-28T12:01:43.032Z" }, + { url = "https://files.pythonhosted.org/packages/78/c4/c98117b03c6a8581ab2c2dfccfe9a5ad82bd8128a3c28b46a6ad2d97c393/rpds_py-2026.5.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:43bca78665423cabae77146f2fe7ce55272b6c8d55d82cca83effd42c7e13972", size = 551165, upload-time = "2026-05-28T12:01:44.648Z" }, + { url = "https://files.pythonhosted.org/packages/3b/c1/bc479ca069200af730881b1bd525e3114b2b391a351509fcb1b772f28086/rpds_py-2026.5.1-cp315-cp315t-musllinux_1_2_i686.whl", hash = "sha256:42d0f20e85e549c870749d0e247f0c10d318a45b7e9676d575d2dcb04a1b2e66", size = 618778, upload-time = "2026-05-28T12:01:46.337Z" }, + { url = "https://files.pythonhosted.org/packages/77/65/38ab2f90df44c2febfb63cc10ced40763d9b4bc94d173e734528663fe7f5/rpds_py-2026.5.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:b1be5c35683684d5331b93600c210e8367c254683d8a6df6bd21bd2da3a334fb", size = 581839, upload-time = "2026-05-28T12:01:48.109Z" }, + { url = "https://files.pythonhosted.org/packages/15/2d/ce1f605fe036aadd460e5822e578c6c7ec3a860936cca37d6e0f299daa77/rpds_py-2026.5.1-cp315-cp315t-win32.whl", hash = "sha256:75808f6c38ce7749bb68cc2770161aae5045e6c6f6781a9782e74b93304399df", size = 207866, upload-time = "2026-05-28T12:01:49.648Z" }, + { url = "https://files.pythonhosted.org/packages/79/cb/966040123eb102371559746908ef2c9471f4d43e17ec9a645a2258dab64b/rpds_py-2026.5.1-cp315-cp315t-win_amd64.whl", hash = "sha256:90bd6630002a1c7f09e7843dd79f0d24f3d2897cc25a753480917865d14f15b3", size = 225441, upload-time = "2026-05-28T12:01:51.408Z" }, +] + +[[package]] +name = "ruff" +version = "0.15.18" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/74/98/1295ad5a5aa9bc85bdcdfa5d82fe7b49c61af5657df4f227637ff9de0da6/ruff-0.15.18.tar.gz", hash = "sha256:2698a964c70e8bf402dcb99c8810472d270d141e7aa8c4e13599fd52033a2f33", size = 4761437, upload-time = "2026-06-18T18:25:39.224Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/d0/686e984941269621e2be72612d5c1e461f8f7b38415a2a7d7a81c8ae6715/ruff-0.15.18-py3-none-linux_armv6l.whl", hash = "sha256:8b6850172348c8381b8b3084c5915a4393c2373b9b54cd5b5e1ea15812bc10df", size = 10887308, upload-time = "2026-06-18T18:25:03.062Z" }, + { url = "https://files.pythonhosted.org/packages/ed/21/bc4123e3f5515ee99f8ce1eb93a14a0628fe4d1678663cd08f933ac16931/ruff-0.15.18-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:3fccc153a85417dcd976883160cacce486997b0a0058dd18f54b8aaaac7d1ce2", size = 11281305, upload-time = "2026-06-18T18:25:30.026Z" }, + { url = "https://files.pythonhosted.org/packages/51/93/4769464c25cf7ab2acb3c7dda9cad3d867eb41c59565b3e2a9d17249c90c/ruff-0.15.18-py3-none-macosx_11_0_arm64.whl", hash = "sha256:08d4c86a68f2c3ec2c9d56380a71fb4a4f65373055cbb8caabd645e9102f38d4", size = 10641215, upload-time = "2026-06-18T18:25:15.802Z" }, + { url = "https://files.pythonhosted.org/packages/6c/42/56926d17120db2c208d76bf60a1a019644dd9e91dc27f0f95c9caddb1366/ruff-0.15.18-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37e5108745c2c0705da916d7d4de533ddf547051ef45f62888c31bae73f66318", size = 10957224, upload-time = "2026-06-18T18:25:36.955Z" }, + { url = "https://files.pythonhosted.org/packages/22/4f/d43fab8d8189afde803103022d000a8ef9f230616d436d52a8b2b8d63b50/ruff-0.15.18-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:56949a6ce8b3abde54c0bcb22cebfe57e8771cadc84b407ae8b8eaf67ebdcd43", size = 10699024, upload-time = "2026-06-18T18:25:05.707Z" }, + { url = "https://files.pythonhosted.org/packages/63/42/1e3e4c68bd408b9768cf3e439acbe2c78245225faef253f7028a0cdb63e0/ruff-0.15.18-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01a754cd6a1b630d3f97e33eb452cf7a98040482318e870f8bc52a5a30e62657", size = 11491458, upload-time = "2026-06-18T18:25:20.275Z" }, + { url = "https://files.pythonhosted.org/packages/20/77/47a3484bea8521e14a203d98c389c5c97846675e4f02734672da4a69b52a/ruff-0.15.18-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6ba7a07e03a44dbf10bb086ee06705b173625014ec99f73a7e6836a5e5590a0c", size = 12383752, upload-time = "2026-06-18T18:25:22.535Z" }, + { url = "https://files.pythonhosted.org/packages/0a/ca/054159590787023d83b658a1a1819c4c8910114e7015069340b71c0961cb/ruff-0.15.18-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5a2c40a41a4cadbcf5897b548ab29dfe248b20c540961c0247d98a3973c70403", size = 11577923, upload-time = "2026-06-18T18:25:10.702Z" }, + { url = "https://files.pythonhosted.org/packages/6d/ff/d353d6b7bbd73cc0ec37f4463d7540e45e894338abdd9964eee0de332708/ruff-0.15.18-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f0480ce690cbb6c4db6e5d08f19fce98e10ba131a8b60c1bcdac42771e3ae2d", size = 11583925, upload-time = "2026-06-18T18:25:32.391Z" }, + { url = "https://files.pythonhosted.org/packages/c1/4a/891f89b9c296ed3e5f3ece1a5629badc989d9a8fdaa30431aaf4774bc1c2/ruff-0.15.18-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:2330215f1f393fa8733f55edce04fcf94c36a2c460fcde31f78cc84e4951e9b1", size = 11582834, upload-time = "2026-06-18T18:25:27.309Z" }, + { url = "https://files.pythonhosted.org/packages/32/a3/ed9e370154bf85de360b93c03026157f02d4943b2d01ff4945f4429f8e8a/ruff-0.15.18-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a6aa6a3d979e48ae617578183674bf264fbe7d0114a796a26bd678d67963c7ff", size = 10927328, upload-time = "2026-06-18T18:25:34.676Z" }, + { url = "https://files.pythonhosted.org/packages/f5/d1/5cf5909329fedb5d39d555ee818ba5cf4638e1a301b89785d34f2905bfcb/ruff-0.15.18-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a81beadbbff2c9c245561ae3f77b16709d87f35eec650d0501679239d3449b22", size = 10693187, upload-time = "2026-06-18T18:25:08.245Z" }, + { url = "https://files.pythonhosted.org/packages/fd/44/ff6c635cf2c4f4e7b618b6640da057376baa36014695487d88aed4794268/ruff-0.15.18-py3-none-musllinux_1_2_i686.whl", hash = "sha256:2186d9e940ae332ab293623a75b5f4fe49565f449954d50a72a046683aa6b809", size = 11208721, upload-time = "2026-06-18T18:25:41.327Z" }, + { url = "https://files.pythonhosted.org/packages/88/d9/5baa2a30861adfb7022cf33c1e35b2fc18085b08c16f83eff4c7b99a5f48/ruff-0.15.18-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5c2abf140438032bc77b2284a6c9944ecd8a19e5f1c7b52b1b8e4a0a80d19a7a", size = 11678599, upload-time = "2026-06-18T18:25:13.607Z" }, + { url = "https://files.pythonhosted.org/packages/c3/1a/0725a7cfdc32ff769efb96ee782bec882e16448c5d9e3be947ec4c04ce27/ruff-0.15.18-py3-none-win32.whl", hash = "sha256:02299e6e9fa5b297a3f6d5d10d7bcd655c925b028bb8b9d4588214549c6b9ec4", size = 10901903, upload-time = "2026-06-18T18:25:24.755Z" }, + { url = "https://files.pythonhosted.org/packages/f3/51/805d9f6fb7970505c3504794a5ec350f605361b807fef4dcf214ebd35e72/ruff-0.15.18-py3-none-win_amd64.whl", hash = "sha256:dac80dc8d26b2257dbefabed62f5d255c3937b4ccb122da1fc634794fa3578b3", size = 12041189, upload-time = "2026-06-18T18:25:17.915Z" }, + { url = "https://files.pythonhosted.org/packages/29/4c/67bb45e41609eb4726f1bfeb59e083cf91d14c696d4bd14c234a980be93d/ruff-0.15.18-py3-none-win_arm64.whl", hash = "sha256:b2c9257fcbd4a3e5b977a1904e6facca016bafe2edc17df24db67cfaee03b4e4", size = 11329958, upload-time = "2026-06-18T18:25:43.686Z" }, +] + [[package]] name = "shellingham" version = "1.5.4" @@ -703,7 +1286,8 @@ version = "2.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, - { name = "starlette" }, + { name = "starlette", version = "0.46.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.14'" }, + { name = "starlette", version = "1.3.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.14'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/71/a4/80d2a11af59fe75b48230846989e93979c892d3a20016b42bb44edb9e398/sse_starlette-2.2.1.tar.gz", hash = "sha256:54470d5f19274aeed6b2d473430b08b4b379ea851d953b11d7f1c4a2c118b419", size = 17376, upload-time = "2024-12-25T09:09:30.616Z" } wheels = [ @@ -714,36 +1298,78 @@ wheels = [ name = "starlette" version = "0.46.0" source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.14'", +] dependencies = [ - { name = "anyio" }, + { name = "anyio", marker = "python_full_version < '3.14'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/44/b6/fb9a32e3c5d59b1e383c357534c63c2d3caa6f25bf3c59dd89d296ecbaec/starlette-0.46.0.tar.gz", hash = "sha256:b359e4567456b28d473d0193f34c0de0ed49710d75ef183a74a5ce0499324f50", size = 2575568, upload-time = "2025-02-22T17:34:45.949Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/41/94/8af675a62e3c91c2dee47cf92e602cfac86e8767b1a1ac3caf1b327c2ab0/starlette-0.46.0-py3-none-any.whl", hash = "sha256:913f0798bd90ba90a9156383bcf1350a17d6259451d0d8ee27fc0cf2db609038", size = 71991, upload-time = "2025-02-22T17:34:43.786Z" }, ] +[[package]] +name = "starlette" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14'", +] +dependencies = [ + { name = "anyio", marker = "python_full_version >= '3.14'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/e3/7c1dc7381d9f8ab7d854328ebfa884e62cb3f3d8549ddfd37c7814f42afa/starlette-1.3.1.tar.gz", hash = "sha256:05d0213193f2fbaae60e2ecb593b4add4262ad4e46536b54abe36f11a71724e0", size = 2703240, upload-time = "2026-06-12T09:23:11.602Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/bb/2799cc2ede3ed41131f8975621e7213dfc7ef4acbbaadfa440f32500c370/starlette-1.3.1-py3-none-any.whl", hash = "sha256:c7372aae11c3c3f26a42df7bd626cec2f47d03483d261d369516a615a53714c6", size = 73632, upload-time = "2026-06-12T09:23:10.017Z" }, +] + +[[package]] +name = "tqdm" +version = "4.68.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/87/d7/0535a28b1f5f24f6612fb3ff1e89fb1a8d160fee0f976e0aa6803862134b/tqdm-4.68.3.tar.gz", hash = "sha256:00dfa48452b6b6cfae3dd9885636c23d3422d1ec97c66d96818cbd5e0821d482", size = 170596, upload-time = "2026-06-17T07:36:52.105Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d8/8e/bb97bb0c71802080bfc8952937d174e49cfc50de5c951dd47b2496f0dcdb/tqdm-4.68.3-py3-none-any.whl", hash = "sha256:39832cc2def2789a6f29df83f172db7416cea70052c0907a57801c5f2fdccb03", size = 78337, upload-time = "2026-06-17T07:36:50.132Z" }, +] + [[package]] name = "typer" -version = "0.15.1" +version = "0.26.7" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "click" }, + { name = "annotated-doc" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, { name = "rich" }, { name = "shellingham" }, - { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/cb/ce/dca7b219718afd37a0068f4f2530a727c2b74a8b6e8e0c0080a4c0de4fcd/typer-0.15.1.tar.gz", hash = "sha256:a0588c0a7fa68a1978a069818657778f86abe6ff5ea6abf472f940a08bfe4f0a", size = 99789, upload-time = "2024-12-04T17:44:58.956Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/ed/ef06584ccdd5c410df0837951ecd7e15d9a6144ea1bd4c73cecab1a89891/typer-0.26.7.tar.gz", hash = "sha256:e314a34c617e419c091b2830dda3ea1f257134ff593061a8f5b9717ab8dddb3a", size = 201709, upload-time = "2026-06-03T07:18:06.843Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d0/cc/0a838ba5ca64dc832aa43f727bd586309846b0ffb2ce52422543e6075e8a/typer-0.15.1-py3-none-any.whl", hash = "sha256:7994fb7b8155b64d3402518560648446072864beefd44aa2dc36972a5972e847", size = 44908, upload-time = "2024-12-04T17:44:57.291Z" }, + { url = "https://files.pythonhosted.org/packages/24/25/2201973529af2c954de0bb725323c3aaed6d7f0ceee8f550dec9185df013/typer-0.26.7-py3-none-any.whl", hash = "sha256:5c87cfbc5d34491c5346ebf49c23e18d56ccb863268d3a8d592b26087c2f5e58", size = 122456, upload-time = "2026-06-03T07:18:05.732Z" }, ] [[package]] name = "typing-extensions" -version = "4.12.2" +version = "4.15.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321, upload-time = "2024-06-07T18:52:15.995Z" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438, upload-time = "2024-06-07T18:52:13.582Z" }, + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, ] [[package]] @@ -767,3 +1393,177 @@ sdist = { url = "https://files.pythonhosted.org/packages/4b/4d/938bd85e5bf2edeec wheels = [ { url = "https://files.pythonhosted.org/packages/61/14/33a3a1352cfa71812a3a21e8c9bfb83f60b0011f5e36f2b1399d51928209/uvicorn-0.34.0-py3-none-any.whl", hash = "sha256:023dc038422502fa28a09c7a30bf2b6991512da7dcdb8fd35fe57cfc154126f4", size = 62315, upload-time = "2024-12-15T13:33:27.467Z" }, ] + +[package.optional-dependencies] +standard = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "httptools" }, + { name = "python-dotenv" }, + { name = "pyyaml" }, + { name = "uvloop", marker = "platform_python_implementation != 'PyPy' and sys_platform != 'cygwin' and sys_platform != 'win32'" }, + { name = "watchfiles" }, + { name = "websockets" }, +] + +[[package]] +name = "uvloop" +version = "0.22.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/06/f0/18d39dbd1971d6d62c4629cc7fa67f74821b0dc1f5a77af43719de7936a7/uvloop-0.22.1.tar.gz", hash = "sha256:6c84bae345b9147082b17371e3dd5d42775bddce91f885499017f4607fdaf39f", size = 2443250, upload-time = "2025-10-16T22:17:19.342Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/ff/7f72e8170be527b4977b033239a83a68d5c881cc4775fca255c677f7ac5d/uvloop-0.22.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:fe94b4564e865d968414598eea1a6de60adba0c040ba4ed05ac1300de402cd42", size = 1359936, upload-time = "2025-10-16T22:16:29.436Z" }, + { url = "https://files.pythonhosted.org/packages/c3/c6/e5d433f88fd54d81ef4be58b2b7b0cea13c442454a1db703a1eea0db1a59/uvloop-0.22.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:51eb9bd88391483410daad430813d982010f9c9c89512321f5b60e2cddbdddd6", size = 752769, upload-time = "2025-10-16T22:16:30.493Z" }, + { url = "https://files.pythonhosted.org/packages/24/68/a6ac446820273e71aa762fa21cdcc09861edd3536ff47c5cd3b7afb10eeb/uvloop-0.22.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:700e674a166ca5778255e0e1dc4e9d79ab2acc57b9171b79e65feba7184b3370", size = 4317413, upload-time = "2025-10-16T22:16:31.644Z" }, + { url = "https://files.pythonhosted.org/packages/5f/6f/e62b4dfc7ad6518e7eff2516f680d02a0f6eb62c0c212e152ca708a0085e/uvloop-0.22.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b5b1ac819a3f946d3b2ee07f09149578ae76066d70b44df3fa990add49a82e4", size = 4426307, upload-time = "2025-10-16T22:16:32.917Z" }, + { url = "https://files.pythonhosted.org/packages/90/60/97362554ac21e20e81bcef1150cb2a7e4ffdaf8ea1e5b2e8bf7a053caa18/uvloop-0.22.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e047cc068570bac9866237739607d1313b9253c3051ad84738cbb095be0537b2", size = 4131970, upload-time = "2025-10-16T22:16:34.015Z" }, + { url = "https://files.pythonhosted.org/packages/99/39/6b3f7d234ba3964c428a6e40006340f53ba37993f46ed6e111c6e9141d18/uvloop-0.22.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:512fec6815e2dd45161054592441ef76c830eddaad55c8aa30952e6fe1ed07c0", size = 4296343, upload-time = "2025-10-16T22:16:35.149Z" }, + { url = "https://files.pythonhosted.org/packages/89/8c/182a2a593195bfd39842ea68ebc084e20c850806117213f5a299dfc513d9/uvloop-0.22.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:561577354eb94200d75aca23fbde86ee11be36b00e52a4eaf8f50fb0c86b7705", size = 1358611, upload-time = "2025-10-16T22:16:36.833Z" }, + { url = "https://files.pythonhosted.org/packages/d2/14/e301ee96a6dc95224b6f1162cd3312f6d1217be3907b79173b06785f2fe7/uvloop-0.22.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cdf5192ab3e674ca26da2eada35b288d2fa49fdd0f357a19f0e7c4e7d5077c8", size = 751811, upload-time = "2025-10-16T22:16:38.275Z" }, + { url = "https://files.pythonhosted.org/packages/b7/02/654426ce265ac19e2980bfd9ea6590ca96a56f10c76e63801a2df01c0486/uvloop-0.22.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e2ea3d6190a2968f4a14a23019d3b16870dd2190cd69c8180f7c632d21de68d", size = 4288562, upload-time = "2025-10-16T22:16:39.375Z" }, + { url = "https://files.pythonhosted.org/packages/15/c0/0be24758891ef825f2065cd5db8741aaddabe3e248ee6acc5e8a80f04005/uvloop-0.22.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0530a5fbad9c9e4ee3f2b33b148c6a64d47bbad8000ea63704fa8260f4cf728e", size = 4366890, upload-time = "2025-10-16T22:16:40.547Z" }, + { url = "https://files.pythonhosted.org/packages/d2/53/8369e5219a5855869bcee5f4d317f6da0e2c669aecf0ef7d371e3d084449/uvloop-0.22.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bc5ef13bbc10b5335792360623cc378d52d7e62c2de64660616478c32cd0598e", size = 4119472, upload-time = "2025-10-16T22:16:41.694Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ba/d69adbe699b768f6b29a5eec7b47dd610bd17a69de51b251126a801369ea/uvloop-0.22.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1f38ec5e3f18c8a10ded09742f7fb8de0108796eb673f30ce7762ce1b8550cad", size = 4239051, upload-time = "2025-10-16T22:16:43.224Z" }, + { url = "https://files.pythonhosted.org/packages/90/cd/b62bdeaa429758aee8de8b00ac0dd26593a9de93d302bff3d21439e9791d/uvloop-0.22.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3879b88423ec7e97cd4eba2a443aa26ed4e59b45e6b76aabf13fe2f27023a142", size = 1362067, upload-time = "2025-10-16T22:16:44.503Z" }, + { url = "https://files.pythonhosted.org/packages/0d/f8/a132124dfda0777e489ca86732e85e69afcd1ff7686647000050ba670689/uvloop-0.22.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4baa86acedf1d62115c1dc6ad1e17134476688f08c6efd8a2ab076e815665c74", size = 752423, upload-time = "2025-10-16T22:16:45.968Z" }, + { url = "https://files.pythonhosted.org/packages/a3/94/94af78c156f88da4b3a733773ad5ba0b164393e357cc4bd0ab2e2677a7d6/uvloop-0.22.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:297c27d8003520596236bdb2335e6b3f649480bd09e00d1e3a99144b691d2a35", size = 4272437, upload-time = "2025-10-16T22:16:47.451Z" }, + { url = "https://files.pythonhosted.org/packages/b5/35/60249e9fd07b32c665192cec7af29e06c7cd96fa1d08b84f012a56a0b38e/uvloop-0.22.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c1955d5a1dd43198244d47664a5858082a3239766a839b2102a269aaff7a4e25", size = 4292101, upload-time = "2025-10-16T22:16:49.318Z" }, + { url = "https://files.pythonhosted.org/packages/02/62/67d382dfcb25d0a98ce73c11ed1a6fba5037a1a1d533dcbb7cab033a2636/uvloop-0.22.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b31dc2fccbd42adc73bc4e7cdbae4fc5086cf378979e53ca5d0301838c5682c6", size = 4114158, upload-time = "2025-10-16T22:16:50.517Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/f1171b4a882a5d13c8b7576f348acfe6074d72eaf52cccef752f748d4a9f/uvloop-0.22.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:93f617675b2d03af4e72a5333ef89450dfaa5321303ede6e67ba9c9d26878079", size = 4177360, upload-time = "2025-10-16T22:16:52.646Z" }, + { url = "https://files.pythonhosted.org/packages/79/7b/b01414f31546caf0919da80ad57cbfe24c56b151d12af68cee1b04922ca8/uvloop-0.22.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:37554f70528f60cad66945b885eb01f1bb514f132d92b6eeed1c90fd54ed6289", size = 1454790, upload-time = "2025-10-16T22:16:54.355Z" }, + { url = "https://files.pythonhosted.org/packages/d4/31/0bb232318dd838cad3fa8fb0c68c8b40e1145b32025581975e18b11fab40/uvloop-0.22.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:b76324e2dc033a0b2f435f33eb88ff9913c156ef78e153fb210e03c13da746b3", size = 796783, upload-time = "2025-10-16T22:16:55.906Z" }, + { url = "https://files.pythonhosted.org/packages/42/38/c9b09f3271a7a723a5de69f8e237ab8e7803183131bc57c890db0b6bb872/uvloop-0.22.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:badb4d8e58ee08dad957002027830d5c3b06aea446a6a3744483c2b3b745345c", size = 4647548, upload-time = "2025-10-16T22:16:57.008Z" }, + { url = "https://files.pythonhosted.org/packages/c1/37/945b4ca0ac27e3dc4952642d4c900edd030b3da6c9634875af6e13ae80e5/uvloop-0.22.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b91328c72635f6f9e0282e4a57da7470c7350ab1c9f48546c0f2866205349d21", size = 4467065, upload-time = "2025-10-16T22:16:58.206Z" }, + { url = "https://files.pythonhosted.org/packages/97/cc/48d232f33d60e2e2e0b42f4e73455b146b76ebe216487e862700457fbf3c/uvloop-0.22.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:daf620c2995d193449393d6c62131b3fbd40a63bf7b307a1527856ace637fe88", size = 4328384, upload-time = "2025-10-16T22:16:59.36Z" }, + { url = "https://files.pythonhosted.org/packages/e4/16/c1fd27e9549f3c4baf1dc9c20c456cd2f822dbf8de9f463824b0c0357e06/uvloop-0.22.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6cde23eeda1a25c75b2e07d39970f3374105d5eafbaab2a4482be82f272d5a5e", size = 4296730, upload-time = "2025-10-16T22:17:00.744Z" }, +] + +[[package]] +name = "watchfiles" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cd/41/5e1a4bb12aac5f1493fa1bdc11154eca3b258ca4eba65d39c473fe19d8e9/watchfiles-1.2.0.tar.gz", hash = "sha256:c995fba777f1ea992f090f9236e9284cf7a5d1a0130dd5a3d82c598cacd76838", size = 108252, upload-time = "2026-05-18T04:32:04.251Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b8/2f/e42c992d2afda3108ea1c02acecc991b9f31d05c14adc2a7cee9ee211fc4/watchfiles-1.2.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:bc13eb17538be00c874699dc0abe4ee2bc8d50bb1166a6b9e175ef3fd7eb8f26", size = 400115, upload-time = "2026-05-18T04:32:02.06Z" }, + { url = "https://files.pythonhosted.org/packages/5f/8f/6af2ea19065c91d8b0ea3516fdfc8c0d349f407e8e9fbf4e5a17360de8ad/watchfiles-1.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2d95ddc1eb6914154253d239089900813f6a767e174b8e6a50e7fdacb7e4236c", size = 393659, upload-time = "2026-05-18T04:30:50.951Z" }, + { url = "https://files.pythonhosted.org/packages/13/01/b32a967c56fb3e3e5be3db52c3d3b87fa4513aa367d8ed1ad96d42952e5f/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f70d8b291ef6e88d19b1f297a6905ddb978888d9272b0d05e6f53309856bcfc", size = 453207, upload-time = "2026-05-18T04:31:04.231Z" }, + { url = "https://files.pythonhosted.org/packages/04/98/97557a812180338cb1abd32e1cffcc4588f59b5f23e0cb006b2ba95ba64a/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:56d8641cf834c2836922899105bd3ce3d0dfc69291d52edf0b4d0436829b34c0", size = 459273, upload-time = "2026-05-18T04:31:50.377Z" }, + { url = "https://files.pythonhosted.org/packages/e8/a8/b4b08dcb7653b8087c6586f7ce649505900e866bbcfe40dc9587af02e686/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2581a94056e55d7d0a31a823ea92bf73749c489ca2285bfdc0fbe6b2bb49d50c", size = 489927, upload-time = "2026-05-18T04:31:42.485Z" }, + { url = "https://files.pythonhosted.org/packages/50/94/3dceea03545d2e5ddfd839f0ddd5e1cecbf1697b5a428d5ba11cef6af95d/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:41bc1199f7523b3f82843c88cbb979180c949caef0342cf90968f178e5d49b01", size = 570476, upload-time = "2026-05-18T04:31:03.071Z" }, + { url = "https://files.pythonhosted.org/packages/cc/f2/d39a5450c3532092b91f81d274360e613c2371bc874a89c7a1a3c5e8d138/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7571e4464cb6e434958f867f7f730b8ab0b75e3f8e5eac0499168486ab3c33a8", size = 465650, upload-time = "2026-05-18T04:30:12.701Z" }, + { url = "https://files.pythonhosted.org/packages/22/24/ed72f68cbc1333ca9b9f2200aa048bb6658ae41709bc1caad4310f4bdffd/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e53a384f76b631c3ae5334ce6a52f0baa3a911eb94a4eac7f160079868b716d5", size = 456398, upload-time = "2026-05-18T04:30:13.784Z" }, + { url = "https://files.pythonhosted.org/packages/0d/64/982ef4a4e5bab5b6e5b6becc8cd5e732f6130a78b855f0abec6439a9a135/watchfiles-1.2.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:d20029a60a71a052a24c4db7673bc4de39ab89adbaccbfb5d67987c5d73f424d", size = 465140, upload-time = "2026-05-18T04:31:52.111Z" }, + { url = "https://files.pythonhosted.org/packages/a0/0c/95282abf4ed680b6096010bcfc30c5fa7a041fc5aa5a2ad17a2cc6c75bba/watchfiles-1.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:2cb93af48550faf1cea04c303107c8b75833de7013e57ce27d3b8d21d8d0f58c", size = 630259, upload-time = "2026-05-18T04:31:25.676Z" }, + { url = "https://files.pythonhosted.org/packages/30/45/607c1de1530c4bdcf2cf1d1ecc2505ddba5d96bd43ba9f2b0e79876f850f/watchfiles-1.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2995c176de7692b86a2e4c58d9ec718f753150a979cb4a754e2b4ffa38e70906", size = 659859, upload-time = "2026-05-18T04:30:24.333Z" }, + { url = "https://files.pythonhosted.org/packages/fa/08/d9e2e0f9e8e6791d33aefc694ad7eefa7f901f63caff84a81ded38692f9c/watchfiles-1.2.0-cp312-cp312-win32.whl", hash = "sha256:7a2cffd17d27d2ecbb310c2b1d8174f222a5495b1a721894afa88ec11e25b898", size = 275480, upload-time = "2026-05-18T04:30:31.307Z" }, + { url = "https://files.pythonhosted.org/packages/1c/e6/9d42569c0102645cc8cea5d8c7d8a1e9d4ada2cb7f05f75e554b8aa2202a/watchfiles-1.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:f155b3a1b2a5fc89cdc70d47ee5d54e3b75e88efa34982028a35daef9ba00379", size = 288718, upload-time = "2026-05-18T04:32:10.745Z" }, + { url = "https://files.pythonhosted.org/packages/0a/26/88e0dc6ee3898169d7fa22bb6a69cabf2502d2ee25cb8c876d1262d204f8/watchfiles-1.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:8fa585ede612ee9f9e91b18bebf9ba11b9ae29a4e3a0d0cf6fca3e382133f0d5", size = 281026, upload-time = "2026-05-18T04:30:22.23Z" }, + { url = "https://files.pythonhosted.org/packages/d1/4d/70a7feced9f87e2ff26dba42667290f41694fc64646c67261fbb8cab5d5c/watchfiles-1.2.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:01ea8d66f0693b9b60a6541c8d10263091ca9a9060d242f3c1f3143f9aad2c98", size = 399730, upload-time = "2026-05-18T04:31:38.162Z" }, + { url = "https://files.pythonhosted.org/packages/31/3a/0da302f2307aee316922806ebd5726c542cbd787c938271cf14a074c7daf/watchfiles-1.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7ba0480b9a74af058f43b337e937a451e109295c420916d68ad24e3dc02f5e44", size = 392842, upload-time = "2026-05-18T04:30:27.051Z" }, + { url = "https://files.pythonhosted.org/packages/db/ef/d5bdb705c224dbc256aa0c1ec47bf4e61ec52558f2afb44a71a1fe4d7015/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f34e26a19f91f710c08e0183429f0d1d15df734e6bc78c31e77b9ea9c433658", size = 452989, upload-time = "2026-05-18T04:31:11.945Z" }, + { url = "https://files.pythonhosted.org/packages/71/29/5495f2c1661949ef7a35e4d71111d129cfe7606414a26887a919d0a55406/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b4e77f6a55f858504069abd35d336a637555c09bca453dde1ee1e5ada8a6a1fb", size = 458978, upload-time = "2026-05-18T04:30:52.606Z" }, + { url = "https://files.pythonhosted.org/packages/d5/8c/7f9c07c433811c2fffd93e13fdfb7135de9aab5f2ae41be08960fa0047dc/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0cb4d80e212f116474a545c21c912b445f16bb0cef9e6a73a498164223e14e2f", size = 490248, upload-time = "2026-05-18T04:31:36.003Z" }, + { url = "https://files.pythonhosted.org/packages/3c/11/d93632febc52fbc21be90231bb7c17fd5387f46c9076fd40a5f9c2ae6910/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b974946a10af379d425e2eef5b62f5c6ebeaccf91d45eaad6f5b27ecd4f91aa0", size = 571847, upload-time = "2026-05-18T04:31:10.862Z" }, + { url = "https://files.pythonhosted.org/packages/55/b4/383173e73aabb07ad1d9c7aa859d95437ac46a6d6a1e11005facda0c9d19/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86bc13c25a8d1fcd70b51d0ce7c9b65e90de5666fcbfd3e34957cc73ee19aeb5", size = 465974, upload-time = "2026-05-18T04:30:17.006Z" }, + { url = "https://files.pythonhosted.org/packages/a7/6c/89b1a230a78f57c52dd8893adb1f92f94411721b6ec12596c56d98c74356/watchfiles-1.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca148d73dea36c9763aaa351e4d7a51780ec1584217c45276f4fe8239c768b71", size = 454782, upload-time = "2026-05-18T04:30:35.656Z" }, + { url = "https://files.pythonhosted.org/packages/24/62/1732118367cfff0a9fce3bf62ff4bfded09ef5df21d9d446b858b3f70a96/watchfiles-1.2.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:c525543d91961c6955b2636b308569e84a1d1c5f5f2932041ab9ef46422f43e3", size = 465182, upload-time = "2026-05-18T04:30:20.846Z" }, + { url = "https://files.pythonhosted.org/packages/28/96/716f7e5f51339bf22963f3345f9f27d7f3b30e2eadc597e257c881dd3c53/watchfiles-1.2.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:a204794696ffb8f9b10fba6f7cb5216d42f3b2b71860ccac6b6e42f5f10973b0", size = 629841, upload-time = "2026-05-18T04:31:05.397Z" }, + { url = "https://files.pythonhosted.org/packages/4c/fe/c40783950fd771ccf66ab3ec2722d188a9af1c7f96c6e811f36e40c6e03f/watchfiles-1.2.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:10d86db20695afe7997ac9e1717637d6714a8d0220458c33f3d2061f54cec427", size = 658028, upload-time = "2026-05-18T04:31:48.22Z" }, + { url = "https://files.pythonhosted.org/packages/71/72/4508db1856d1d87fcbb3b63f4839bab1b5682cb0e8d224d122263c09654a/watchfiles-1.2.0-cp313-cp313-win32.whl", hash = "sha256:eb283ee99e21ad6443c8cdb06ac5b34b1308c329cbdf03fa02b445363714c799", size = 275183, upload-time = "2026-05-18T04:30:59.57Z" }, + { url = "https://files.pythonhosted.org/packages/f9/36/14b76ca57652e5cc5fd1c11f32a261292c08a0d19a00351013c2549cbfb2/watchfiles-1.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:a0f27f01bee51861392bb6b7c4fdb290b27d1eb194e9e28788d68102a0e898d9", size = 288059, upload-time = "2026-05-18T04:32:07.937Z" }, + { url = "https://files.pythonhosted.org/packages/1b/8d/0a85e395398d8d20fadfe5c5d32c726eee17a519e78fb356f2cf7531bffe/watchfiles-1.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:3651aa7058595e9cfb75d35dd5ada2bf9f48a5b8a0f3562821d3e210c507e077", size = 280186, upload-time = "2026-05-18T04:31:54.484Z" }, + { url = "https://files.pythonhosted.org/packages/37/68/36db056f1fdcc5f07302f56e631774d6835bcd6fa3ace402304621d5f9e5/watchfiles-1.2.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:faea288b6f0ab1902ef08f4ca6de005dccf856c4e0c4f21b8c5fce02d90a1b08", size = 399031, upload-time = "2026-05-18T04:30:44.576Z" }, + { url = "https://files.pythonhosted.org/packages/c1/64/01a9d6f66a82a5c101ce939274106cc72759d62427e153f01edd2b9f87c2/watchfiles-1.2.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:01859b11fd9fbca670f4d5da00fbac282cfea9bd67a2125d8b2833a3b5617ea9", size = 391205, upload-time = "2026-05-18T04:30:25.413Z" }, + { url = "https://files.pythonhosted.org/packages/84/2c/0a44fe058cb4bb7b8ede6b6670698bbb7c0400740e378d00022189b7b31d/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fff610d7bb2256a317bb1e96f0d7862c7aa8076733ee5df0fd41bbe76a24a4f4", size = 451892, upload-time = "2026-05-18T04:32:14.005Z" }, + { url = "https://files.pythonhosted.org/packages/67/a1/351e0d56cd35e6488b5c8b4fb11a809a5bc923e8fe8fed9faf8920be0c89/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b141a4891c995a039cd89e9a49e62df1dc8a559a5d1a6e4c7106d16c12777a55", size = 458867, upload-time = "2026-05-18T04:31:22.279Z" }, + { url = "https://files.pythonhosted.org/packages/d5/7d/9d09605187f1b838998624049fcf8bf47b73c1a3b76901fcac1782f62277/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f22943b7770483f6ea0721c6b11d022947a98eb0acae14694de034f4d0d38925", size = 490217, upload-time = "2026-05-18T04:31:43.657Z" }, + { url = "https://files.pythonhosted.org/packages/60/5d/a17a16eccb182f04188cd308ec24b1a71a9b5c4e7098269cf35d9fa56d02/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1bc6195825b7dcd217968bb1f801a60fd4c16e8eeab5bedc7fe917d7d5995ab4", size = 571458, upload-time = "2026-05-18T04:32:11.875Z" }, + { url = "https://files.pythonhosted.org/packages/d3/3d/4dd457062083ab1938e5dfd45032eb425cee2ac817287ca8ff4356183e5d/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d4a4b147f5dca2a5d325a06a832fb43f345751adfbc63204aec30e0d9ca965a2", size = 464707, upload-time = "2026-05-18T04:30:43.492Z" }, + { url = "https://files.pythonhosted.org/packages/c6/71/ea8c57b128f5383de74d0c7d2d9c57ad7c9a65a930c451bd25d524b295b7/watchfiles-1.2.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4543579a9bdb0c9560039b4ffddbdb39545707659fbc430ce4c10f3f68d557f9", size = 454663, upload-time = "2026-05-18T04:30:16.061Z" }, + { url = "https://files.pythonhosted.org/packages/53/fd/2e812bf938406d7db351f0703ddd3fc6c061cf30d96153a77bc79a943a44/watchfiles-1.2.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:20aa0e708b920bde876a4aa82dc7dd6ebea228a63a67cda6632c2fc87b787efa", size = 463537, upload-time = "2026-05-18T04:31:44.9Z" }, + { url = "https://files.pythonhosted.org/packages/86/56/d17a7f1dd1bc3035f1072694a551301272f1739c2d8e319c927cb9e29b38/watchfiles-1.2.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:d413349d565dab74297f2a63e84a097936be69bf8f3b3801f27f380e32040f44", size = 629194, upload-time = "2026-05-18T04:31:14.141Z" }, + { url = "https://files.pythonhosted.org/packages/be/06/f1ff66bf5cae50aa4062779a0ecd0bbaf15e466195719074078947d9a17d/watchfiles-1.2.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:f28b2725eb8cce327b9b3ab02415c853011dc55c95832fe90de6bc56f5315f72", size = 656194, upload-time = "2026-05-18T04:31:47.14Z" }, + { url = "https://files.pythonhosted.org/packages/e7/54/a9c7ea9a82a4ac65e7004c0a03920b5cdd2f9c3b678757d9cd425aa51d53/watchfiles-1.2.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:b8c8358484d5fa12ef34f05b7f4168eaf1932f408725ff6d023c33ec17bd79d4", size = 400205, upload-time = "2026-05-18T04:32:05.153Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5d/c9ab3534374a4a67450696905d6ef16a04405448b8dc52bd752ae50423d4/watchfiles-1.2.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9f04b092229ad2c50126dd3c922c8822e51e605993764a33058d4a791ab42281", size = 392508, upload-time = "2026-05-18T04:30:54.849Z" }, + { url = "https://files.pythonhosted.org/packages/26/ca/1ad30103535cf0cecd7b993e8d50edc5351b1820e38f2d22e3df58962feb/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a7ce236284f002a156f70add88efe5c70879cccbb658be0822c54b1306fc09d", size = 452448, upload-time = "2026-05-18T04:30:53.727Z" }, + { url = "https://files.pythonhosted.org/packages/37/a1/ceee2cdf2afbd715fa07758d39c9859513eae411b23196f7fd039e5feedd/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b9909cc2b48468b575eefa944919e1fe8a36c5849d5c7c168f80a8c1db69398e", size = 459605, upload-time = "2026-05-18T04:30:23.312Z" }, + { url = "https://files.pythonhosted.org/packages/e8/f6/421e30fd1cb3907a84ed92ab3f1983e37ba2dca015e9a894a048418417a2/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a37faaed405c67e28e6be45a1fa4f206ef5a2860f27c237db9fa30704c38242", size = 490757, upload-time = "2026-05-18T04:30:47.358Z" }, + { url = "https://files.pythonhosted.org/packages/41/b0/55ed1b97ed08be7bba6f9a541cac15f2a858e1d74d2b07b6da70a82aab00/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9649193aa27bd9ff2e80ff29bfaa93085496c7a3a377592823cc58b77ee88add", size = 568672, upload-time = "2026-05-18T04:30:38.915Z" }, + { url = "https://files.pythonhosted.org/packages/d1/cf/d8ae8a80dd7bafab395ea7681c10237311bbf34d37704a8c744e7cf31fc7/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e4ff8e37f99cf1da89e255e07c9c4b37c214038c4283707bdec308cb1b0ea1f", size = 464197, upload-time = "2026-05-18T04:30:09.914Z" }, + { url = "https://files.pythonhosted.org/packages/7c/8a/3076c496ca8dafe0e8cd03fcebdfc47be4b1174b4e5b24ff6e396e6b3af2/watchfiles-1.2.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:054dc20fd2e3132b4c3883b4a00d72fd6e1f56fdaf89fccd12e8057d74cd74d7", size = 453181, upload-time = "2026-05-18T04:30:14.829Z" }, + { url = "https://files.pythonhosted.org/packages/e5/10/9745e17c98e7b8a86454df0a3c7b5686bd650383f1e9f26e4ebcbd6cc0c0/watchfiles-1.2.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:e140ed30ebde76796b686e67c182cff10ea2fbab186fafd1560f74bb5a473a6e", size = 465109, upload-time = "2026-05-18T04:30:28.123Z" }, + { url = "https://files.pythonhosted.org/packages/8f/95/8ef4a95481d3e0cb52d62a06fa6e972e81424be2d9698b91a2fecca9904c/watchfiles-1.2.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:bb7e52ecf68ba46d22df23467b87cffeb2146908aa523ebfe803019618cfda06", size = 630653, upload-time = "2026-05-18T04:31:49.304Z" }, + { url = "https://files.pythonhosted.org/packages/fd/e4/3b3bf36b0f829b50c6ebcb8d031583863c59f923d6a6af3d485e470d0fac/watchfiles-1.2.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:23282a321c8baf9b3a3c4afff673f9fe65eb7fdc2338d765ccad9d3d1916a5ba", size = 657838, upload-time = "2026-05-18T04:31:06.497Z" }, + { url = "https://files.pythonhosted.org/packages/21/b1/6cbbb50c1f3002ab568777d44aa21206dfb8807a840990c4037523b51812/watchfiles-1.2.0-cp314-cp314-win32.whl", hash = "sha256:c0db965c5f79aa49fe672d297cf1febc5ad149b658594944f49a54a2b96270a7", size = 275108, upload-time = "2026-05-18T04:30:06.891Z" }, + { url = "https://files.pythonhosted.org/packages/92/45/190ce6db8dcb4536682cf75d3889ff1a27182a58cb519d343cb6d9ea63d8/watchfiles-1.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:71283b39fd17e5408eb123bd37aeecfd9d54c81fc184421943208aadb879d103", size = 288441, upload-time = "2026-05-18T04:32:12.901Z" }, + { url = "https://files.pythonhosted.org/packages/74/0d/3eae1c2313ab08378431d907c3f8095ecca00f3eda33111cf4f0f2591799/watchfiles-1.2.0-cp314-cp314-win_arm64.whl", hash = "sha256:c5c19526f4e54a00f2666a6c0e9e40d582c09e865055ea7378bf0009aab857b3", size = 280684, upload-time = "2026-05-18T04:31:26.902Z" }, + { url = "https://files.pythonhosted.org/packages/b1/75/fb64e6c25d6b5ca636d03df34ffb1c6e9873303e76d27967e045f8df088f/watchfiles-1.2.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:d73a585accffa5ae39c17264c36ec3166d2fad7000c780f5ef83b2722afb9dd2", size = 398857, upload-time = "2026-05-18T04:32:17.108Z" }, + { url = "https://files.pythonhosted.org/packages/73/4e/9f7adf01754cbf81843722ccfec169d8f26c69778281a302855cecd2ee08/watchfiles-1.2.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ae99b14c5f21e026e0e9d96f40e07d8570ebee6cafd9d8fc318354606daa7a28", size = 392413, upload-time = "2026-05-18T04:31:07.911Z" }, + { url = "https://files.pythonhosted.org/packages/47/c8/bec626bcc2d69f44b9acb24ce7d60ed7b16b73628eea747fcbd169d8edda/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4429f3b105524a10b72c3a819b091c495d2811d419c1e1e8df773a5a5974f831", size = 452409, upload-time = "2026-05-18T04:31:20.142Z" }, + { url = "https://files.pythonhosted.org/packages/00/b7/b6362068e81e7c556d155a34c35d40ac3ef42d747b06d7f6e5bf58e359c2/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:43d818978d06062d9b22c4fab2ebe44cf5213d42dc8e62bda8c2760cfa2eeb33", size = 458827, upload-time = "2026-05-18T04:32:06.219Z" }, + { url = "https://files.pythonhosted.org/packages/67/f8/9a813fa42afb1e0b4625e75f0479826644d3ee8dc287e093799bc01f390c/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b9f732dc58b2dbe69e464ccf8fff7a03b0dd0be439da4c0720d3558527d3d6b4", size = 490104, upload-time = "2026-05-18T04:31:56.034Z" }, + { url = "https://files.pythonhosted.org/packages/2f/bf/27dfb6094ca4c9aad21298b5525b6c53cb36121ee454331d05161e58d130/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f200104103feb097de4cab8fe4f5dd18a2026934c7dea98c55a2f5fd6d5a33b", size = 571360, upload-time = "2026-05-18T04:31:57.133Z" }, + { url = "https://files.pythonhosted.org/packages/fb/39/44a096d67270ea93df91d33877dbe91fbda3aa4f8ec2edf799d93eda8736/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:63ac26eefbf4af1741247d6fb68b11c49a25b2f7413fbd318a83a12aaa9cf666", size = 464644, upload-time = "2026-05-18T04:30:57.33Z" }, + { url = "https://files.pythonhosted.org/packages/0e/80/c7472203bad6268e3ef1ad260739704847898938ad7ea8b63a5131f46b50/watchfiles-1.2.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c4997d4e4a55f0d02b6cde327322daf3a0400e5df6c6b15948994bf72497925", size = 454771, upload-time = "2026-05-18T04:30:48.736Z" }, + { url = "https://files.pythonhosted.org/packages/51/cf/3b10b268b4b7f0fc26e9debb5eef1998b515887840f444cd3ec80c688755/watchfiles-1.2.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:4c887eba18b7945ac73067a8b4a66f21cd46c2539b2bc68588f7be6c7eb6d26b", size = 463494, upload-time = "2026-05-18T04:31:33.826Z" }, + { url = "https://files.pythonhosted.org/packages/3d/3e/a4302545cd589262a0dc7d140e86f7688eba3f9c72776c27f7e23b8864c4/watchfiles-1.2.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:3416ff151bb6b5a8d8d11664974fbef4d9305b9b2957839ab5a270468fd8df30", size = 629383, upload-time = "2026-05-18T04:31:15.596Z" }, + { url = "https://files.pythonhosted.org/packages/db/99/d5649df0a9a410d45b7c882304d0b790903ac9b6e8f2cfd12114e0c6b9f2/watchfiles-1.2.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:0e831a271c035d89789cffc386b6aa1375f39f1cd25eb7ca0997e4970d152fc5", size = 656093, upload-time = "2026-05-18T04:31:58.707Z" }, + { url = "https://files.pythonhosted.org/packages/92/b9/362702539275019a54dd2e94511b31a9b89c5f9e6a21966de7eb692549fc/watchfiles-1.2.0-cp315-cp315-macosx_10_12_x86_64.whl", hash = "sha256:37a6721cdf3f65dbb13aa9503510ccb4451603ac837e44d265d7992a597e1374", size = 400109, upload-time = "2026-05-18T04:31:16.879Z" }, + { url = "https://files.pythonhosted.org/packages/8f/75/71d5ba62db781e5587bded1d944c675374bc4aa37ff33d5018d98e8b6538/watchfiles-1.2.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:2b37d10b5a63bd4d87e18472d80fa525bd670586fae62e5dd580452764879b65", size = 392167, upload-time = "2026-05-18T04:31:28.058Z" }, + { url = "https://files.pythonhosted.org/packages/3c/01/c66dd95d0423fe30d31820e2d1d5bda773764131bbb6ac0cb1cf303ac328/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a105bc2283f67e8fbec74253ec2d94925de92ed72c0393f1206bf326b7b7b69", size = 452372, upload-time = "2026-05-18T04:31:00.836Z" }, + { url = "https://files.pythonhosted.org/packages/91/15/2fe99557e72f85627c6a8eed50d889e8d101623e060a22ad75b875cb932d/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5327989a465505f05cfe06f04fa9d0c2fd5432bb243e10e6f012b1bdca3c8579", size = 459596, upload-time = "2026-05-18T04:31:34.96Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/d4acfa0023367428ed48351b3b9b267893037b6cadae55620c61c24bcfd4/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ecb47f183a8025b2aa18b546725c3657e542112ae9c0613a2af79b4fa8d04ad7", size = 490869, upload-time = "2026-05-18T04:31:59.923Z" }, + { url = "https://files.pythonhosted.org/packages/a4/5f/3164cbdce06c9fb95c4f7b9e2f9760b5e2797af43a9ecc317ef42a23a278/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8520a4ab0e37f770afc34459c4f8f7019e153f9124dc101c15538365875d1ab2", size = 571641, upload-time = "2026-05-18T04:32:00.948Z" }, + { url = "https://files.pythonhosted.org/packages/41/e6/85d3731c55e65cd7690f3f803d24c139588aaf863e4bf2148fe7a7fa1a19/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:71cd71740ed2c15211ebb237ced4e39a1cdf6f80566e5fe95428da1626f4fde6", size = 464444, upload-time = "2026-05-18T04:30:34.298Z" }, + { url = "https://files.pythonhosted.org/packages/f4/7d/562641012b8b09872742c3b8adf9629ec479fd78f8d68ae4a0c13da8add6/watchfiles-1.2.0-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f88af53d6ddaf72179ef613ddc905e6f4785f712b49b80b3bef9f3525e6194b4", size = 453593, upload-time = "2026-05-18T04:31:23.464Z" }, + { url = "https://files.pythonhosted.org/packages/56/fe/cb8ef3d6f929d14158fdaaad9925985b7310abc9384dcd4d82dd0016fb59/watchfiles-1.2.0-cp315-cp315-manylinux_2_31_riscv64.whl", hash = "sha256:cee9d5efd929efdac5f7e58f72b3376f676b64050a91c5b99a7094c5b2317488", size = 465096, upload-time = "2026-05-18T04:31:30.384Z" }, + { url = "https://files.pythonhosted.org/packages/25/91/80908e835e100527a9267147b08c0eee1fa6ab0ffec15edc04d1d44885f7/watchfiles-1.2.0-cp315-cp315-musllinux_1_1_aarch64.whl", hash = "sha256:b718bf356bbc15e559bd8ef41782b573b8ae0e3f177ab244b440568d7ea02cfb", size = 630638, upload-time = "2026-05-18T04:30:49.89Z" }, + { url = "https://files.pythonhosted.org/packages/46/4b/95ab2f256bb4af3cb2eb23b9317bda984ee6e0f11733a5c004a6c95b06e3/watchfiles-1.2.0-cp315-cp315-musllinux_1_1_x86_64.whl", hash = "sha256:922c0e019fe68b3ae392965a766b02a71ba1168c932cebc3733cd52c5fe5b377", size = 657684, upload-time = "2026-05-18T04:31:32.027Z" }, +] + +[[package]] +name = "websockets" +version = "16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/04/24/4b2031d72e840ce4c1ccb255f693b15c334757fc50023e4db9537080b8c4/websockets-16.0.tar.gz", hash = "sha256:5f6261a5e56e8d5c42a4497b364ea24d94d9563e8fbd44e78ac40879c60179b5", size = 179346, upload-time = "2026-01-10T09:23:47.181Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/7b/bac442e6b96c9d25092695578dda82403c77936104b5682307bd4deb1ad4/websockets-16.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:71c989cbf3254fbd5e84d3bff31e4da39c43f884e64f2551d14bb3c186230f00", size = 177365, upload-time = "2026-01-10T09:22:46.787Z" }, + { url = "https://files.pythonhosted.org/packages/b0/fe/136ccece61bd690d9c1f715baaeefd953bb2360134de73519d5df19d29ca/websockets-16.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8b6e209ffee39ff1b6d0fa7bfef6de950c60dfb91b8fcead17da4ee539121a79", size = 175038, upload-time = "2026-01-10T09:22:47.999Z" }, + { url = "https://files.pythonhosted.org/packages/40/1e/9771421ac2286eaab95b8575b0cb701ae3663abf8b5e1f64f1fd90d0a673/websockets-16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:86890e837d61574c92a97496d590968b23c2ef0aeb8a9bc9421d174cd378ae39", size = 175328, upload-time = "2026-01-10T09:22:49.809Z" }, + { url = "https://files.pythonhosted.org/packages/18/29/71729b4671f21e1eaa5d6573031ab810ad2936c8175f03f97f3ff164c802/websockets-16.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9b5aca38b67492ef518a8ab76851862488a478602229112c4b0d58d63a7a4d5c", size = 184915, upload-time = "2026-01-10T09:22:51.071Z" }, + { url = "https://files.pythonhosted.org/packages/97/bb/21c36b7dbbafc85d2d480cd65df02a1dc93bf76d97147605a8e27ff9409d/websockets-16.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e0334872c0a37b606418ac52f6ab9cfd17317ac26365f7f65e203e2d0d0d359f", size = 186152, upload-time = "2026-01-10T09:22:52.224Z" }, + { url = "https://files.pythonhosted.org/packages/4a/34/9bf8df0c0cf88fa7bfe36678dc7b02970c9a7d5e065a3099292db87b1be2/websockets-16.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a0b31e0b424cc6b5a04b8838bbaec1688834b2383256688cf47eb97412531da1", size = 185583, upload-time = "2026-01-10T09:22:53.443Z" }, + { url = "https://files.pythonhosted.org/packages/47/88/4dd516068e1a3d6ab3c7c183288404cd424a9a02d585efbac226cb61ff2d/websockets-16.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:485c49116d0af10ac698623c513c1cc01c9446c058a4e61e3bf6c19dff7335a2", size = 184880, upload-time = "2026-01-10T09:22:55.033Z" }, + { url = "https://files.pythonhosted.org/packages/91/d6/7d4553ad4bf1c0421e1ebd4b18de5d9098383b5caa1d937b63df8d04b565/websockets-16.0-cp312-cp312-win32.whl", hash = "sha256:eaded469f5e5b7294e2bdca0ab06becb6756ea86894a47806456089298813c89", size = 178261, upload-time = "2026-01-10T09:22:56.251Z" }, + { url = "https://files.pythonhosted.org/packages/c3/f0/f3a17365441ed1c27f850a80b2bc680a0fa9505d733fe152fdf5e98c1c0b/websockets-16.0-cp312-cp312-win_amd64.whl", hash = "sha256:5569417dc80977fc8c2d43a86f78e0a5a22fee17565d78621b6bb264a115d4ea", size = 178693, upload-time = "2026-01-10T09:22:57.478Z" }, + { url = "https://files.pythonhosted.org/packages/cc/9c/baa8456050d1c1b08dd0ec7346026668cbc6f145ab4e314d707bb845bf0d/websockets-16.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:878b336ac47938b474c8f982ac2f7266a540adc3fa4ad74ae96fea9823a02cc9", size = 177364, upload-time = "2026-01-10T09:22:59.333Z" }, + { url = "https://files.pythonhosted.org/packages/7e/0c/8811fc53e9bcff68fe7de2bcbe75116a8d959ac699a3200f4847a8925210/websockets-16.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:52a0fec0e6c8d9a784c2c78276a48a2bdf099e4ccc2a4cad53b27718dbfd0230", size = 175039, upload-time = "2026-01-10T09:23:01.171Z" }, + { url = "https://files.pythonhosted.org/packages/aa/82/39a5f910cb99ec0b59e482971238c845af9220d3ab9fa76dd9162cda9d62/websockets-16.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e6578ed5b6981005df1860a56e3617f14a6c307e6a71b4fff8c48fdc50f3ed2c", size = 175323, upload-time = "2026-01-10T09:23:02.341Z" }, + { url = "https://files.pythonhosted.org/packages/bd/28/0a25ee5342eb5d5f297d992a77e56892ecb65e7854c7898fb7d35e9b33bd/websockets-16.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:95724e638f0f9c350bb1c2b0a7ad0e83d9cc0c9259f3ea94e40d7b02a2179ae5", size = 184975, upload-time = "2026-01-10T09:23:03.756Z" }, + { url = "https://files.pythonhosted.org/packages/f9/66/27ea52741752f5107c2e41fda05e8395a682a1e11c4e592a809a90c6a506/websockets-16.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c0204dc62a89dc9d50d682412c10b3542d748260d743500a85c13cd1ee4bde82", size = 186203, upload-time = "2026-01-10T09:23:05.01Z" }, + { url = "https://files.pythonhosted.org/packages/37/e5/8e32857371406a757816a2b471939d51c463509be73fa538216ea52b792a/websockets-16.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:52ac480f44d32970d66763115edea932f1c5b1312de36df06d6b219f6741eed8", size = 185653, upload-time = "2026-01-10T09:23:06.301Z" }, + { url = "https://files.pythonhosted.org/packages/9b/67/f926bac29882894669368dc73f4da900fcdf47955d0a0185d60103df5737/websockets-16.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6e5a82b677f8f6f59e8dfc34ec06ca6b5b48bc4fcda346acd093694cc2c24d8f", size = 184920, upload-time = "2026-01-10T09:23:07.492Z" }, + { url = "https://files.pythonhosted.org/packages/3c/a1/3d6ccdcd125b0a42a311bcd15a7f705d688f73b2a22d8cf1c0875d35d34a/websockets-16.0-cp313-cp313-win32.whl", hash = "sha256:abf050a199613f64c886ea10f38b47770a65154dc37181bfaff70c160f45315a", size = 178255, upload-time = "2026-01-10T09:23:09.245Z" }, + { url = "https://files.pythonhosted.org/packages/6b/ae/90366304d7c2ce80f9b826096a9e9048b4bb760e44d3b873bb272cba696b/websockets-16.0-cp313-cp313-win_amd64.whl", hash = "sha256:3425ac5cf448801335d6fdc7ae1eb22072055417a96cc6b31b3861f455fbc156", size = 178689, upload-time = "2026-01-10T09:23:10.483Z" }, + { url = "https://files.pythonhosted.org/packages/f3/1d/e88022630271f5bd349ed82417136281931e558d628dd52c4d8621b4a0b2/websockets-16.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8cc451a50f2aee53042ac52d2d053d08bf89bcb31ae799cb4487587661c038a0", size = 177406, upload-time = "2026-01-10T09:23:12.178Z" }, + { url = "https://files.pythonhosted.org/packages/f2/78/e63be1bf0724eeb4616efb1ae1c9044f7c3953b7957799abb5915bffd38e/websockets-16.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:daa3b6ff70a9241cf6c7fc9e949d41232d9d7d26fd3522b1ad2b4d62487e9904", size = 175085, upload-time = "2026-01-10T09:23:13.511Z" }, + { url = "https://files.pythonhosted.org/packages/bb/f4/d3c9220d818ee955ae390cf319a7c7a467beceb24f05ee7aaaa2414345ba/websockets-16.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:fd3cb4adb94a2a6e2b7c0d8d05cb94e6f1c81a0cf9dc2694fb65c7e8d94c42e4", size = 175328, upload-time = "2026-01-10T09:23:14.727Z" }, + { url = "https://files.pythonhosted.org/packages/63/bc/d3e208028de777087e6fb2b122051a6ff7bbcca0d6df9d9c2bf1dd869ae9/websockets-16.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:781caf5e8eee67f663126490c2f96f40906594cb86b408a703630f95550a8c3e", size = 185044, upload-time = "2026-01-10T09:23:15.939Z" }, + { url = "https://files.pythonhosted.org/packages/ad/6e/9a0927ac24bd33a0a9af834d89e0abc7cfd8e13bed17a86407a66773cc0e/websockets-16.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:caab51a72c51973ca21fa8a18bd8165e1a0183f1ac7066a182ff27107b71e1a4", size = 186279, upload-time = "2026-01-10T09:23:17.148Z" }, + { url = "https://files.pythonhosted.org/packages/b9/ca/bf1c68440d7a868180e11be653c85959502efd3a709323230314fda6e0b3/websockets-16.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:19c4dc84098e523fd63711e563077d39e90ec6702aff4b5d9e344a60cb3c0cb1", size = 185711, upload-time = "2026-01-10T09:23:18.372Z" }, + { url = "https://files.pythonhosted.org/packages/c4/f8/fdc34643a989561f217bb477cbc47a3a07212cbda91c0e4389c43c296ebf/websockets-16.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a5e18a238a2b2249c9a9235466b90e96ae4795672598a58772dd806edc7ac6d3", size = 184982, upload-time = "2026-01-10T09:23:19.652Z" }, + { url = "https://files.pythonhosted.org/packages/dd/d1/574fa27e233764dbac9c52730d63fcf2823b16f0856b3329fc6268d6ae4f/websockets-16.0-cp314-cp314-win32.whl", hash = "sha256:a069d734c4a043182729edd3e9f247c3b2a4035415a9172fd0f1b71658a320a8", size = 177915, upload-time = "2026-01-10T09:23:21.458Z" }, + { url = "https://files.pythonhosted.org/packages/8a/f1/ae6b937bf3126b5134ce1f482365fde31a357c784ac51852978768b5eff4/websockets-16.0-cp314-cp314-win_amd64.whl", hash = "sha256:c0ee0e63f23914732c6d7e0cce24915c48f3f1512ec1d079ed01fc629dab269d", size = 178381, upload-time = "2026-01-10T09:23:22.715Z" }, + { url = "https://files.pythonhosted.org/packages/06/9b/f791d1db48403e1f0a27577a6beb37afae94254a8c6f08be4a23e4930bc0/websockets-16.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:a35539cacc3febb22b8f4d4a99cc79b104226a756aa7400adc722e83b0d03244", size = 177737, upload-time = "2026-01-10T09:23:24.523Z" }, + { url = "https://files.pythonhosted.org/packages/bd/40/53ad02341fa33b3ce489023f635367a4ac98b73570102ad2cdd770dacc9a/websockets-16.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:b784ca5de850f4ce93ec85d3269d24d4c82f22b7212023c974c401d4980ebc5e", size = 175268, upload-time = "2026-01-10T09:23:25.781Z" }, + { url = "https://files.pythonhosted.org/packages/74/9b/6158d4e459b984f949dcbbb0c5d270154c7618e11c01029b9bbd1bb4c4f9/websockets-16.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:569d01a4e7fba956c5ae4fc988f0d4e187900f5497ce46339c996dbf24f17641", size = 175486, upload-time = "2026-01-10T09:23:27.033Z" }, + { url = "https://files.pythonhosted.org/packages/e5/2d/7583b30208b639c8090206f95073646c2c9ffd66f44df967981a64f849ad/websockets-16.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:50f23cdd8343b984957e4077839841146f67a3d31ab0d00e6b824e74c5b2f6e8", size = 185331, upload-time = "2026-01-10T09:23:28.259Z" }, + { url = "https://files.pythonhosted.org/packages/45/b0/cce3784eb519b7b5ad680d14b9673a31ab8dcb7aad8b64d81709d2430aa8/websockets-16.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:152284a83a00c59b759697b7f9e9cddf4e3c7861dd0d964b472b70f78f89e80e", size = 186501, upload-time = "2026-01-10T09:23:29.449Z" }, + { url = "https://files.pythonhosted.org/packages/19/60/b8ebe4c7e89fb5f6cdf080623c9d92789a53636950f7abacfc33fe2b3135/websockets-16.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bc59589ab64b0022385f429b94697348a6a234e8ce22544e3681b2e9331b5944", size = 186062, upload-time = "2026-01-10T09:23:31.368Z" }, + { url = "https://files.pythonhosted.org/packages/88/a8/a080593f89b0138b6cba1b28f8df5673b5506f72879322288b031337c0b8/websockets-16.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:32da954ffa2814258030e5a57bc73a3635463238e797c7375dc8091327434206", size = 185356, upload-time = "2026-01-10T09:23:32.627Z" }, + { url = "https://files.pythonhosted.org/packages/c2/b6/b9afed2afadddaf5ebb2afa801abf4b0868f42f8539bfe4b071b5266c9fe/websockets-16.0-cp314-cp314t-win32.whl", hash = "sha256:5a4b4cc550cb665dd8a47f868c8d04c8230f857363ad3c9caf7a0c3bf8c61ca6", size = 178085, upload-time = "2026-01-10T09:23:33.816Z" }, + { url = "https://files.pythonhosted.org/packages/9f/3e/28135a24e384493fa804216b79a6a6759a38cc4ff59118787b9fb693df93/websockets-16.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b14dc141ed6d2dde437cddb216004bcac6a1df0935d79656387bd41632ba0bbd", size = 178531, upload-time = "2026-01-10T09:23:35.016Z" }, + { url = "https://files.pythonhosted.org/packages/6f/28/258ebab549c2bf3e64d2b0217b973467394a9cea8c42f70418ca2c5d0d2e/websockets-16.0-py3-none-any.whl", hash = "sha256:1637db62fad1dc833276dded54215f2c7fa46912301a24bd94d45d46a011ceec", size = 171598, upload-time = "2026-01-10T09:23:45.395Z" }, +]