> This page is for Webflow Cloud.

> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://developers.webflow.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://developers.webflow.com/_mcp/server.

# Add a SQLite database

> Add a SQLite database

This tutorial guides you through connecting a [SQLite database](/webflow-cloud/storing-data/sqlite)
to your Webflow Cloud app, defining your schema, and building API routes to store
and retrieve user data.

You'll learn these things:

* How to add a SQLite database to your Webflow Cloud app
* How to define and manage your schema with Drizzle ORM
* How to run and apply database migrations
* How to create API routes to add and fetch data

## Prerequisites

Before you begin, make sure you have:

* A Webflow Cloud app linked to a GitHub repository
* A Webflow Cloud environment set up
* Node.js 22 or later and `npm` installed
* Basic familiarity with JavaScript/TypeScript

> **New to Webflow Cloud?**
>
> If you haven't already, follow the [Quick Start guide](/webflow-cloud/getting-started) to set up your app and environment.

## Add a SQLite database

Set up a SQLite database so your Webflow Cloud app can store data.

#### Open the app in your IDE.

Navigate to the app's source code in your IDE and open the `wrangler.json` file.

#### Add a SQLite database binding to the \`d1_databases\` array in \`wrangler.json\`.

Add the `d1_databases` array to the `wrangler.json` file, and declare a new database binding.

```json
"d1_databases": [
  {
    "binding": "DB", // A valid JavaScript variable name
    "database_name": "db",
    "database_id": "1234", // Webflow Cloud generates this when you deploy the app
    "migrations_dir": "drizzle" // You'll create this directory later
  }
]
```

This configuration tells Webflow Cloud to create a SQLite database for your app and to apply any migrations found in the `drizzle` directory.

| Property         | Description                                                                                                       |
| ---------------- | ----------------------------------------------------------------------------------------------------------------- |
| `binding`        | The variable name to use to access the database in your code. This name must be a valid JavaScript variable name. |
| `database_name`  | The name for the database.                                                                                        |
| `database_id`    | A unique identifier for the database (Webflow Cloud generates this for you when you deploy your app).             |
| `migrations_dir` | The directory to store the migration files in.                                                                    |

#### Generate TypeScript types for your binding.

#### Astro

**`Terminal`**

```bash title="Terminal"
wrangler types
```

#### Next.js

```bash
npm run cf-typegen
```

This command updates the app's type definitions so you can safely access the database in your code.

## Add Drizzle ORM

[Drizzle ORM](https://orm.drizzle.team/docs/overview) is a tool that helps you work with a database using JavaScript or TypeScript code instead of writing raw SQL.
It makes it easier to define your database structure, run migrations, and interact with data safely and reliably.

#### Install Drizzle ORM and Drizzle Kit.

Install the required packages for working with Drizzle ORM and managing migrations:

```bash
npm i drizzle-orm
npm i -D drizzle-kit tsx better-sqlite3
```

* `drizzle-orm`: The ORM for interacting with the database.
* `drizzle-kit`: CLI for migrations and schema generation.
* `tsx` and `better-sqlite3`: Required for local development and SQLite support.

#### Create the Drizzle config file.

In the root of your app, create a file named `drizzle.config.ts`.
Configure the schema path and output path as in this example:

**`drizzle.config.ts`**

```typescript title="drizzle.config.ts"
import { defineConfig } from "drizzle-kit";

export default defineConfig({
    schema: "./src/db/schema/index.ts",
    out: "./drizzle",
    dialect: "sqlite",
});
```

#### Add local migration scripts to your package.json.

Add the following scripts to the `package.json` file to manage local migrations:

```json
"scripts": {
  // ... existing scripts ...
  "db:generate": "drizzle-kit generate",
  "db:apply:local": "wrangler d1 migrations apply DB --local",
}
```

* `db:apply:local`: Applies migrations to the local database.
* `db:generate`: Generates migration files from the schema.

## Create a schema

A database schema defines the data that the app can store.
In these steps you set up a table to keep things organized as the app grows.

#### Create the schema directory.

To keep the schema organized, create a `db/schema` folder inside your `src` directory to hold all your schema files.

```bash
cd src
mkdir -p db/schema
```

#### Define your first table.

Create an `index.ts` file in `src/db/schema` and import the necessary Drizzle ORM methods and use them to define a table of users named `usersTable`:

**`src/db/schema/index.ts`**

```typescript title="src/db/schema/index.ts"
// Import the necessary Drizzle ORM functions
import { sqliteTable, text, int } from "drizzle-orm/sqlite-core";

// Define the users table with the following fields:
// - id: The primary key of the table
// - name: The name of the user
// - age: The age of the user
// - email: The email of the user

export const usersTable = sqliteTable("users_table", {
    id: int().primaryKey({ autoIncrement: true }),
    name: text().notNull(),
    age: int().notNull(),
    email: text().notNull().unique(),
});
```

By defining the schema in code, you ensure that the database structure is always documented and version-controlled.
To learn more about defining schemas with Drizzle ORM, see the [Drizzle ORM documentation](https://orm.drizzle.team/docs/sql-schema-declaration).

#### Initialize the database.

Whenever you change the schema, you must generate a migration.
A migration is a set of instructions for updating the database structure.
Migrations let you evolve your database structure safely over time, without losing data or breaking the app.

Because you just created a new table, you must generate a migration file to update the database.
In the terminal, run the following commands to generate a new migration file in the `drizzle` directory from the schema defined in `src/db/schema/index.ts` and apply it to the local database:

```bash
npm run db:generate
npm run db:apply:local
```

## Connect to the database

Now you'll connect the app to the database to make it easy to add, find, and update information as users interact with the app.

#### Create a helper function to get the database instance.

To avoid repeating connection logic, create a reusable helper that returns a Drizzle ORM instance.
This instance ensures that you always use the correct schema and [environment](/webflow-cloud/environment), and improves performance by reusing connections.

In `src/db`, create a `getDb.ts` file and add the following code:

#### Astro

**`src/db/getDb.ts`**

```typescript title="src/db/getDb.ts"
import { drizzle } from "drizzle-orm/d1";
import * as schema from "./schema";

// For dynamic routes
export const getDb = (locals: App.Locals) => {
  const { env } = locals.runtime;
  return drizzle(env.DB, { schema });
};

// For static routes
export const getDbAsync = async (locals: App.Locals) => {
  const { env } = locals.runtime;
  return drizzle(env.DB, { schema });
};
```

#### Next.js

**`src/db/getDb.ts`**

```typescript title="src/db/getDb.ts"
import { getCloudflareContext } from "@opennextjs/cloudflare";
import { drizzle } from "drizzle-orm/d1";
import { cache } from "react";
import * as schema from "./schema";

// For dynamic routes
export const getDb = cache(() => {
  const { env } = getCloudflareContext();
  return drizzle(env.DB, { schema });
});

// For static routes
export const getDbAsync = cache(async () => {
  const { env } = await getCloudflareContext({ async: true });
  return drizzle(env.DB, { schema });
});
```

#### Create a route to add users to the users table.

To allow the app to add new users, create an API route that accepts user details and inserts them into the `usersTable` table.
This pattern is common for building RESTful APIs and helps keep your data layer organized.

#### Astro

In `src/pages/api`, create a `users.ts` file with the following code:

**`src/pages/api/users.ts`**

```typescript title="src/pages/api/users.ts"
import { APIRoute } from "astro";
import { getDb } from "../../db/getDb";
import { usersTable } from "../../db/schema";

// Define the expected shape of the request body
type UserInput = {
  name: string;
  age: number;
  email: string;
};

// Define CORS headers
const corsHeaders = {
  "Access-Control-Allow-Origin": "*", // Or specify your domain instead of *
  "Access-Control-Allow-Methods": "GET,POST,OPTIONS",
  "Access-Control-Allow-Headers": "Content-Type",
};

// Handle OPTIONS requests for CORS preflight
export const OPTIONS: APIRoute = async ({ request }) => {
  return new Response(null, {
    headers: corsHeaders,
  });
};

// Handle POST requests to add a new user
export const POST: APIRoute = async ({ request, locals }) => {
  const db = getDb(locals); // Get the database instance
  const { name, age, email }: UserInput = await request.json();
  try {
    // Insert the new user into the usersTable
    const newUser = await db
      .insert(usersTable)
      .values({ name, age, email })
      .returning();
    // Return the created user
    return Response.json(newUser[0], { status: 201 });
  } catch (error) {
    // Handle errors (e.g., duplicate email)
    return Response.json({ error: "Failed to create user" }, { status: 500 });
  }
};
```

#### Next.js

In `src/app/api/users`, create a `route.ts` file:

**`src/app/api/users/route.ts`**

```typescript title="src/app/api/users/route.ts"
import { NextRequest, NextResponse } from "next/server";
import { getDb } from "../../../../../db/getDb";
import { usersTable } from "../../../../../db/schema";

type UserInput = {
  name: string;
  age: number;
  email: string;
};

export async function POST(request: NextRequest) {
  const db = getDb();
  const { name, age, email }: UserInput = await request.json();
  try {
    const newUser = await db
      .insert(usersTable)
      .values({ name, age, email })
      .returning();
    return NextResponse.json(newUser[0], { status: 201 });
  } catch (error) {
    return NextResponse.json({ error: "Failed to create user" }, { status: 500 });
  }
}
```

This code runs these main steps:

1. The route receives a POST request with user data.
2. It inserts the data into the table.
3. On success, it returns the new user; on error, it returns a failure message.

#### Create a route to get users from the users table.

To retrieve data, create a GET route that returns all users from the database:

#### Astro

In the same `users.ts` file, add:

**`src/pages/api/users.ts`**

```typescript title="src/pages/api/users.ts"
export const GET: APIRoute = async ({ locals }) => {
  const db = getDb(locals);
  try {
    const users = await db.select().from(usersTable);
    return Response.json(users, { status: 200 });
  } catch (error) {
    return Response.json({ error: "Failed to fetch users" }, { status: 500 });
  }
};
```

#### Next.js

In the same `route.ts` file, add:

**`src/app/api/users/route.ts`**

```typescript title="src/app/api/users/route.ts"
export async function GET(request: NextRequest) {
  const db = getDb();
  try {
    const users = await db.select().from(usersTable);
    return NextResponse.json(users, { status: 200 });
  } catch (error) {
    return NextResponse.json(
      { error: "Failed to fetch users" },
      { status: 500 }
    );
  }
}
```

This code runs these main steps:

1. The route receives a GET request.
2. It queries all users from the database.
3. It returns the list of users or an error message.

#### Test your routes.

Test your API routes to make sure you can add and retrieve users from your database.

1. Start the app by running the following command in your terminal:

   ```bash
   npm run dev
   ```

2. In a different terminal window, add a user by sending a POST request as in the following example.
   Replace `<YOUR_PORT>` with your actual port number, for example, `4321`:

   ```bash
   curl -X POST http://localhost:<YOUR_PORT>/<YOUR_APP_MOUNT_PATH>/api/users \
     -H "Content-Type: application/json" \
     -d '{"name": "Arthur Dent", "age": 33, "email": "arthur.dent@bbc.co.uk"}'
   ```

   A successful response returns the new user as JSON, for example:

   ```json
   {
     "id": 1,
     "name": "Arthur Dent",
     "age": 33,
     "email": "arthur.dent@bbc.co.uk"
   }
   ```

   Try adding more users with different data to populate your database.

3. Retrieve all users by sending a GET request:

   ```bash
   curl http://localhost:<YOUR_PORT>/<YOUR_APP_MOUNT_PATH>/api/users
   ```

   The response should include a list of users.

## Deploy and view the app

Webflow Cloud applies database migrations automatically when you update the application.
From here, you can connect to your database through the app's endpoints, and any data you add will persist.
You can also view and manage your data directly in the Webflow Cloud UI.

#### Deploy the app.

Use the Webflow CLI to trigger a new deployment of the app manually or commit and push your changes to your GitHub repository to trigger a new deployment:

**`Webflow CLI`**

```bash title="Webflow CLI"
webflow cloud deploy
```

**`GitHub`**

```bash title="GitHub"
git add .
git commit -m "Add users table"
git push
```

#### View the app and database in Webflow Cloud.

1. Open the app's environment in Webflow Cloud.
2. After the deployment completes, click the "Storage" tab in the environment.

   ![](/_fern-img/2ab4fecbc3e9a35cb90853ab6f598b60f62f65d5cb095ab660f60fc9b2c74ff1.webp)
3. Select the `DB` binding to view the database.
4. You should see the `users_table`.
   For now, it is empty, because all the previous changes were local.
   In the "Storage" tab, you can add and update users through the UI.

   ![Database viewer in Webflow Cloud](/_fern-img/29730530f2a03935c0930c5f7c7a4a8c5ac70f3d892e15408572a49717914399.webp)

> **Try calling the API routes**
>
> You can also try calling the API routes to add and retrieve users from the deployed app.
> To make these calls, put the app URL and path from the Webflow Cloud environment into the path of the call, as in this example:
>
> ```bash
> curl -X POST https://<your-app-url>/<your-app-mount-path>/api/users \
>   -H "Content-Type: application/json" \
>   -d '{"name": "Arthur Dent", "age": 33, "email": "arthur.dent@bbc.co.uk"}'
> ```

You've now added persistent storage to your Webflow Cloud app using SQLite and Drizzle ORM. You learned how to:

* Connect a SQLite database to your app
* Define and manage your schema in code
* Run and apply migrations
* Build API routes to add and retrieve data

## Next steps

Ready to build more? Check out related guides and resources to keep growing your app's capabilities.

* Learn more about [SQLite in Webflow Cloud](/webflow-cloud/storing-data/sqlite)
* Learn about other [storage options in Webflow Cloud](/webflow-cloud/storing-data/overview)
* See examples for [adding authentication to your app](/webflow-cloud/examples)