How Bolt handles data today
Bolt.new, built by StackBlitz, is unusual among AI builders: it runs an entire Node toolchain in your browser through WebContainers. The app you prompt into existence is genuinely running — installing packages, serving a dev server — in the tab. Data has kept pace. New projects created with the Claude agent get a Bolt-managed database out of the box, and Bolt also ships a native Supabase integration you can authorize from your account settings.
So a brand-new Bolt app can store data with zero setup. As with Lovable, the question is not can it persist data — it is whose database that is, and how easily you can take it elsewhere.
The case for owning the backend
Bolt itself is candid that the default has friction: switching from a Bolt database to Supabase later requires extra steps. A built-in database is the right call for a throwaway prototype. It is the wrong call when:
- The app is going to live, and you want the database — and its backups — under your own account.
- Another service, a webhook, or a second app needs to read the same data.
- You want the freedom to move off Bolt without an export project.
Connecting Cradler from the start sidesteps the later migration: the data is in an independent PostgreSQL database you own on day one. The native Supabase route does this too — if you are happy writing schemas and Row Level Security policies in SQL. Cradler is for the builder who wants ownership without the administration.
Connecting Cradler to a Bolt app, step by step
Because Bolt runs a real Node environment, this is just npm and a .env file.
- 01
Create a Cradler project
Sign up at cradler.ai and create a project. Cradler provisions an isolated PostgreSQL database and file storage, and gives you a gateway URL, a project ID, an anon key, and a service key.
- 02
Install @cradler/sdk in the Bolt editor
Bolt runs a real Node environment in the browser. Ask Bolt to add @cradler/sdk to package.json, or install it from Bolt's terminal — it installs like any npm package.
- 03
Add your keys as environment variables
Put the Cradler gateway URL, project ID, and anon key in your project's .env file inside Bolt. Bolt picks them up immediately in its live preview.
- 04
Prompt Bolt and deploy
Tell Bolt in plain language to save and read your data through the Cradler client. Cradler creates tables and columns as the data arrives, backs up daily, and Bolt deploys the app as usual.
The code: a small SaaS in Bolt
Suppose you prompted Bolt into a simple project tracker and you want tasks stored, updated, and queried. The full data layer is a handful of functions over one client:
import { createClient } from "@cradler/sdk";
const cradler = createClient({
url: import.meta.env.VITE_CRADLER_URL,
projectId: import.meta.env.VITE_CRADLER_PROJECT_ID,
apiKey: import.meta.env.VITE_CRADLER_ANON_KEY,
});
// Create a task. The "tasks" table is created on the
// first call; "done" is inferred as a boolean column.
export async function addTask(title: string, projectId: string) {
const { rows } = await cradler.from("tasks").insert({
title,
projectId,
done: false,
});
return rows[0];
}
// Mark a task complete.
export async function completeTask(id: string) {
await cradler.from("tasks").update({ done: true }).eq("id", id);
}
// Open tasks for one project, oldest first.
export async function openTasks(projectId: string) {
const { rows } = await cradler
.from("tasks")
.select("id", "title", "createdAt")
.eq("projectId", projectId)
.eq("done", false)
.order("createdAt");
return rows;
}
// Delete a task — a filter is required, so you can never
// wipe the whole table by accident.
export async function removeTask(id: string) {
await cradler.from("tasks").delete().eq("id", id);
}One detail worth calling out: delete() refuses to run without at least one filter. There is no way to empty a collection with a stray call — a small guardrail that matters when an AI agent is writing the code.
Handling file uploads
If a task can carry an attachment, storage is already in the same Cradler project — no extra integration to authorize in Bolt:
async function attachFile(taskId: string, file: File) {
const path = `tasks/${taskId}/${file.name}`;
await cradler.storage.upload(path, file, {
contentType: file.type,
});
await cradler
.from("tasks")
.update({ attachmentPath: path })
.eq("id", taskId);
}
// List everything stored under one task.
async function taskFiles(taskId: string) {
return cradler.storage.list(`tasks/${taskId}/`);
}Gotchas worth knowing
Vite env vars must be prefixed. Bolt apps are usually Vite-based, and Vite only exposes variables that begin with VITE_ to client code. Name your variables VITE_CRADLER_URL, VITE_CRADLER_PROJECT_ID, and VITE_CRADLER_ANON_KEY, and keep the service key out of anything client-side.
Set table permissions before you go live. The anon key is visible in the browser bundle. In the Cradler dashboard, decide per table whether the anon key may read, insert, update, or delete — the default is deny. The service key, for server code only, ignores those toggles.
Schema growth is one-directional. Cradler adds columns and tables automatically but never removes or renames them on its own. Destructive changes are explicit, confirmed, backed-up dashboard actions, so a wayward Bolt prompt cannot delete a column.
Why Cradler fits Bolt
Bolt's magic is that the whole stack runs and deploys from one browser tab. Cradler keeps the backend just as low-friction without trapping your data inside the tool: it is standard PostgreSQL you own, with a typed SDK, generated types, an llms.txt, and an MCP server (@cradler/mcp) so Bolt's agent generates accurate data code. The full SDK reference lives on the @cradler/sdk npm page.