Drizzle <> Appwrite MySQL

This guide assumes familiarity with:

Appwrite offers dedicated, native MySQL databases that you connect to directly over TLS. There is no Appwrite-specific driver or abstraction layer, so you can use Drizzle with the mysql2 driver through the drizzle-orm/mysql2 package, the same way you would with any MySQL server.

To get your connection string, open your database in the Appwrite Console and click Credentials, or fetch it from the Appwrite API with mysql.get(). The username is admin.<hash>, where the hash matches the one in the hostname, and the database name is always default. Appwrite’s proxy routes your connection by this username, so always use the full value from the connection string.

DATABASE_URL="mysql://admin.<hash>:<password>@db-<hash>.<region>.appwrite.center:3306/default?ssl=true"

Step 1 - Install packages

npm
yarn
pnpm
bun
npm i drizzle-orm@rc mysql2
npm i -D drizzle-kit@rc

Step 2 - Initialize the driver and make a query

mysql2
mysql2 with config
import { drizzle } from "drizzle-orm/mysql2";

const db = drizzle(process.env.DATABASE_URL);

const response = await db.select().from(...)

If you need to provide your existing driver:

Client connection
Pool connection
import { drizzle } from "drizzle-orm/mysql2";
import mysql from "mysql2/promise";

const connection = await mysql.createConnection(process.env.DATABASE_URL);

const db = drizzle({ client: connection });
IMPORTANT

Connections on Appwrite Cloud are encrypted with TLS. If your runtime or mysql2 configuration does not infer TLS from the connection string, pass ssl: { rejectUnauthorized: true } in the connection options.

For the built in migrate function with DDL migrations we and drivers strongly encourage you to use a single client connection.

Connection pooling for serverless

Appwrite runs a connection pooler on port 6033 of the same hostname. In serverless and edge environments, route your application traffic through the pooler so short-lived instances don’t exhaust the engine’s connection limit, and keep Drizzle Kit pointed at the direct engine port 3306, because migrations issue DDL that needs a session-level connection.

# Runtime: pooled, transaction mode
DATABASE_URL="mysql://admin.<hash>:<password>@db-<hash>.<region>.appwrite.center:6033/default?ssl=true"

# Migrations & introspection: direct connection to the engine
DIRECT_URL="mysql://admin.<hash>:<password>@db-<hash>.<region>.appwrite.center:3306/default?ssl=true"
drizzle.config.ts
import { defineConfig } from 'drizzle-kit';

export default defineConfig({
  dialect: 'mysql',
  schema: './src/schema.ts',
  out: './drizzle',
  dbCredentials: {
    url: process.env.DIRECT_URL!,
  },
});

The pooler defaults to transaction mode, which does not keep a backend connection across statements. Drizzle’s regular query builder sends text queries and works with transaction pooling, but session-level features such as explicit prepared queries, user variables and temporary tables need the direct port or a session-mode pooler.

What’s next?