Database
Use SQLite databases in your Next.js app with Ploy.
Database
Add SQLite databases to your Next.js application with Ploy.
Generated bindings work directly in getPloyContext(), so existing SQL access patterns transfer cleanly.
Configuration
Add a database binding in your ploy.yaml:
kind: dynamic
build: pnpm build
out: dist
db:
DB: defaultGenerate types:
pnpm ploy typesMigrations
Create SQL migrations in migrations/. Ploy runs them in filename order and tracks applied files in ploy_internal_db_migrations inside the same database.
For a single DB binding:
migrations/
└── 20260403171410_init/
├── migration.sql
└── snapshot.jsonOnly .sql files are executed, so sibling files such as snapshot.json are ignored.
If your project uses multiple DB bindings, scope migrations by binding name:
migrations/
├── DB/
│ └── 001_create_users.sql
└── ANALYTICS_DB/
└── 20260403171410_init/
└── migration.sqlProduction deploys on the default branch apply pending migrations before upload and log them in the build output. During local development, ploy dev applies project migrations on startup.
API Route Example
This example assumes your users table already exists from a migration in migrations/.
import { NextResponse } from "next/server";
import { getPloyContext } from "@meetploy/nextjs";
interface User {
id: number;
name: string;
email: string;
}
export async function GET() {
const { env } = getPloyContext();
const result = await env.DB.prepare("SELECT * FROM users").all<User>();
return NextResponse.json({ users: result.results });
}
export async function POST(request: Request) {
const { env } = getPloyContext();
const { name, email } = await request.json();
const result = await env.DB.prepare(
"INSERT INTO users (name, email) VALUES (?, ?)",
)
.bind(name, email)
.run();
return NextResponse.json({ success: true, meta: result.meta });
}Server Component Example
import { getPloyContext } from "@meetploy/nextjs";
interface User {
id: number;
name: string;
}
export default async function UsersPage() {
const { env } = getPloyContext();
const { results: users } = await env.DB.prepare(
"SELECT * FROM users"
).all<User>();
return (
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}Query Methods
all() - Get All Rows
const { results } = await env.DB.prepare("SELECT * FROM users").all<User>();first() - Get First Row
const user = await env.DB.prepare("SELECT * FROM users WHERE id = ?")
.bind(1)
.first<User>();run() - Execute Statement
const result = await env.DB.prepare("INSERT INTO users (name) VALUES (?)")
.bind("Alice")
.run();exec() - Run Raw SQL
const result = await env.DB.exec(`
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
PRAGMA optimize;
`);
console.log(result.count);withSession() - Session Compatibility
const session = env.DB.withSession("first-primary");
const user = await session
.prepare("SELECT * FROM users WHERE id = ?")
.bind(1)
.first<User>();Always use prepared statements with .bind() to prevent SQL injection.
Local Development
During development, the local Ploy dashboard at http://localhost:4000 lets you:
- Browse table contents
- Execute SQL queries
- Inspect database structure
Project migrations are applied on startup, and data persists across restarts in the .ploy directory.
Next Steps
How is this guide?
Last updated on