Add a SQLite database to your app

Add persistent storage to your Webflow Cloud app using SQLite and Drizzle ORM
Beta

This tutorial guides you through connecting a SQLite database to your Webflow Cloud app, defining your schema, and building API routes to store and retrieve user data.

SQLite for Webflow Cloud is in Beta

To continue with this tutorial, you’ll need access to SQLite for Webflow Cloud. Apply for access here.

What you’ll learn:

  • How to add a SQLite database to your Webflow Cloud project
  • 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:

  • Access to the Webflow Cloud Storage Beta
  • A Webflow Cloud project linked to a GitHub repository
  • A Webflow Cloud environment set up
  • Node.js 20+ and npm 10+
  • Basic familiarity with JavaScript/TypeScript
New to Webflow Cloud?

If you haven’t already, follow the Quick Start guide to set up your project and environment.

Add a SQLite database

Set up a SQLite database so your Webflow Cloud app can store data. This step connects your project to a reliable, easy-to-use SQLite database.

1

Update your project in Webflow Cloud (Optional)

If you created a Webflow Cloud prior to 7/16/2025, you’ll need to update your project in the Webflow Cloud dashboard to use Webflow Cloud’s new storage system.

  1. Go to your project in the Webflow Cloud dashboard.
  2. Select the ”…” icon in the “Actions” section of the menu.
  3. Select “Edit” (you don’t actually need to edit anything).
  4. Press “Save Changes” to update your project.
2

Open your project in your IDE.

Navigate to your project in your IDE, and open the wrangler.json file.

3

Add a SQLite database binding to the bindings array in wrangler.json.

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

1"d1_databases": [
2 {
3 "binding": "DB", // A valid JavaScript variable name
4 "database_name": "db",
5 "id": "1234", // Webflow Cloud will generate this for you once you deploy your app
6 "migrations_dir": "drizzle" // We'll create this directory later
7 }
8]

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

  • binding: The variable name you’ll use to access the database in your code. This must be a valid JavaScript variable name.
  • database_name: The name for your database.
  • id: A unique identifier for your database (Webflow Cloud will generate this for you once you deploy your app).
  • migrations_dir: The directory where your migration files are stored.
4

Generate TypeScript types for your binding.

Terminal
$wrangler types

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

Add Drizzle ORM

Drizzle ORM is a tool that helps you work with your 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 your data safely and reliably.

1

Install Drizzle ORM and Drizzle Kit.

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

$npm i drizzle-orm
>npm i -D drizzle-kit tsx better-sqlite3
  • drizzle-orm: The ORM for interacting with your database.
  • drizzle-kit: CLI for migrations and schema generation.
  • tsx and better-sqlite3: Required for local development and SQLite support.
2

Create the Drizzle config file.

In the root of your project, create a file named drizzle.config.ts. Configure the schema path and output path:

drizzle.config.ts
1import { defineConfig } from "drizzle-kit";
2
3export default defineConfig({
4 schema: "./src/db/schema/index.ts",
5 out: "./drizzle",
6 dialect: "sqlite",
7});
3

Add local migration scripts to your package.json.

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

1"scripts": {
2 // ... existing scripts ...
3 "db:generate": "drizzle-kit generate",
4 "db:apply:local": "wrangler d1 migrations apply DB --local",
5}
  • db:apply:local: Applies migrations to your local database.
  • db:generate: Generates migration files from your schema.

Create a schema

A database schema defines the data your app can store. Here, you’ll set up your first table to keep things organized as your project grows.

1

Create the schema directory.

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

$cd src
>mkdir -p db/schema
2

Define your first table.

Create an index.ts file in src/db/schema Import the necessary Drizzle ORM methods use them to define a usersTable:

src/db/schema/index.ts
1// Import the necessary Drizzle ORM functions
2import { sqliteTable, text, int } from "drizzle-orm/sqlite-core";
3
4// Define the users table with the following fields:
5// - id: The primary key of the table
6// - name: The name of the user
7// - age: The age of the user
8// - email: The email of the user
9
10export const usersTable = sqliteTable("users_table", {
11 id: int().primaryKey({ autoIncrement: true }),
12 name: text().notNull(),
13 age: int().notNull(),
14 email: text().notNull().unique(),
15});

This code defines a usersTable with multiple fields.

By defining your schema in code, you ensure your database structure is always documented and version-controlled. To learn more about defining schemas with Drizzle ORM, see the Drizzle ORM documentation.

3

Initialize the database

Whenever you change your schema, you need to 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 your app.

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

$npm run db:generate
>npm run db:apply:local

Connect to the database

Now you’ll connect your app to the database. This makes it easy to add, find, and update information as your users interact with your app.

1

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 ensures you always use the correct schema and environment, and improves performance by reusing connections.

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

src/db/getDb.ts
1import { drizzle } from "drizzle-orm/d1";
2import * as schema from "./schema";
3
4// For dynamic routes
5export const getDb = (locals: App.Locals) => {
6const { env } = locals.runtime;
7return drizzle(env.DB, { schema });
8};
9
10// For static routes
11export const getDbAsync = async (locals: App.Locals) => {
12const { env } = locals.runtime;
13return drizzle(env.DB, { schema });
14};
2

Create a route to add users to the users table.

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

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

src/pages/api/users.ts
1import { APIRoute } from "astro";
2import { getDb } from "../../db/getDb";
3import { usersTable } from "../../db/schema";
4
5// Define the expected shape of the request body
6type UserInput = {
7name: string;
8age: number;
9email: string;
10};
11
12// Define CORS headers
13const corsHeaders = {
14"Access-Control-Allow-Origin": "*", // Or specify your domain instead of *
15"Access-Control-Allow-Methods": "GET,POST,OPTIONS",
16"Access-Control-Allow-Headers": "Content-Type",
17};
18
19// Handle OPTIONS requests for CORS preflight
20export const OPTIONS: APIRoute = async ({ request }) => {
21 return new Response(null, {
22 headers: corsHeaders,
23 });
24};
25
26// Handle POST requests to add a new user
27export const POST: APIRoute = async ({ request, locals }) => {
28const db = getDb(locals); // Get the database instance
29const { name, age, email }: UserInput = await request.json();
30try {
31 // Insert the new user into the usersTable
32 const newUser = await db
33 .insert(usersTable)
34 .values({ name, age, email })
35 .returning();
36 // Return the created user
37 return Response.json(newUser[0], { status: 201 });
38} catch (error) {
39 // Handle errors (e.g., duplicate email)
40 return Response.json({ error: "Failed to create user" }, { status: 500 });
41}
42};

What’s happening?

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

Create a route to get users from the users table.

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

In the same users.ts file, add:

src/pages/api/users.ts
1export const GET: APIRoute = async ({ locals }) => {
2 const db = getDb(locals);
3 try {
4 const users = await db.select().from(usersTable);
5 return Response.json(users, { status: 200 });
6 } catch (error) {
7 return Response.json({ error: "Failed to fetch users" }, { status: 500 });
8 }
9};

What’s happening?

  • The route receives a GET request.
  • It queries all users from the database.
  • Returns the list of users or an error message.
4

Test your routes.

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

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

    $npm run dev
  2. Add a user by sending a POST request (replace <YOUR_PORT> with your actual port number, for example, 4321):

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

    1{
    2 "id": 1,
    3 "name": "Arthur Dent",
    4 "age": 33,
    5 "email": "arthur.dent@bbc.co.uk"
    6}

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

  3. Retrieve all users by sending a GET request:

    $curl http://localhost:<YOUR_PORT>/{YOUR_APP_MOUNT_PATH}/api/users

    You should see a list of users in the response.

Deploy and view your app

When you deploy to Webflow Cloud, your database migrations are applied automatically and your remote database is updated. From here, you can connect to your database through your app’s endpoints, and any data you add will persist. You can also view and manage your data directly in the Webflow Cloud UI.

1

Deploy your app.

Use the Webflow CLI to trigger a new deployment of your app. Additionaly, you can commit and push your changes to your GitHub repository to trigger a new deployment as well.

$webflow cloud deploy
2

View your app and database in Webflow Cloud

  1. Open your project’s environment in Webflow Cloud.

  2. After your deployment completes, click the “Bindings” tab in your environment.

  3. Select the DB binding to view your database.

  4. You should see the users_table. For now, it will be empty, since all the previous changes were local. In the bindings tab, you can add and update users through the UI.

Try calling your API routes

You can also try calling your API routes to add and retrieve users from your deployed app.

$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 project
  • 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.