Primitive/backend5 min read

Introducing the backend stack: Postgres, storage, auth, and edge functions for agent-built apps

Every fullstack app your agent ships gets a managed Postgres database, file storage, end-user auth, and edge functions — driven through one API key, with no Supabase dashboard and no service-role secrets ever leaving the runtime.

Primitive/backend
TL;DR
  • /backend the data layer for agent-built apps: managed Postgres, file storage, end-user auth, and edge functions, all on one fullstack app
  • One call provisions everything create a `type: "fullstack"` app and the database, storage, auth, and functions come online together
  • Database raw SQL, PostgREST CRUD, and tracked migrations against managed Postgres, with the project ref auto-resolved
  • Storage create buckets and read/write objects on the app's project without touching a storage dashboard
  • Auth configure end-user authentication and manage users through an admin API, service-role key held server-side
  • Edge functions deploy and invoke serverless functions; the agent never sees a raw provider secret

Today we're launching the /backend stack — the data layer for the apps your agent ships. Managed Postgres, file storage, end-user authentication, and edge functions, all provisioned together and driven through the same Naïve API key. No Supabase dashboard. No service-role secret ever leaving the runtime. Your agent stands up a real backend the same way it does everything else: one call.

The problem: a deployed app is only half a product

/apps lets an agent deploy a real web application from a prompt. But an app without a backend is a brochure. The moment you want to store a user, save a record, upload a file, or run a bit of server logic, an agent hits the same wall a human does:

  • Provisioning a database means a dashboard. Click through a project creation flow, copy a connection string, paste it into env vars. Agents don't click dashboards.
  • The service-role key is radioactive. It's the key that bypasses every security rule. Handing it to an autonomous agent — or worse, embedding it in generated app code — is exactly the kind of mistake the runtime should make impossible.
  • Four services, four integrations. Database, storage, auth, and functions are usually four SDKs, four sets of credentials, and four mental models. For an agent, that's four ways to get stuck.

The result is that "build me an app with login and a database" quietly becomes a human task again. Until now.

How the backend stack works

Everything hangs off a single fullstack app. When you create an app with type: "fullstack", Naïve provisions a managed Postgres + Supabase project alongside the Vercel deployment. That one project is what the four backend primitives operate on:

  1. Database — raw SQL, PostgREST CRUD, and tracked migrations against managed Postgres.
  2. Storage — buckets and objects for files and assets.
  3. Auth — end-user authentication config plus admin user management.
  4. Edge Functions — deploy and invoke serverless functions.

Every call routes through an app-scoped proxy. The project's service-role key lives server-side; the agent authenticates with its normal nv_sk_* key, and Naïve attaches the right project credential behind the scenes. The SDK auto-resolves your single fullstack app, so methods never take a project ref — pass { appId } only if a user owns more than one.

# Stand up the app that owns the backend
naive apps create --name "My SaaS" --type fullstack

Database: SQL, CRUD, and migrations

The database client gives you three ways to work, from raw SQL to typed CRUD:

// Apply a schema change (DDL)
await naive.database.migrate(
  "create table posts (id uuid primary key default gen_random_uuid(), title text, created_at timestamptz default now())"
);
 
// PostgREST-style CRUD
await naive.database.from("posts").insert({ title: "Hello world" });
const recent = await naive.database.from("posts").select("*", "order=created_at.desc");
 
// Raw SQL when you need it
const rows = await naive.database.query("select count(*) from posts");
 
// Introspect the schema
const { tables } = await naive.database.tables();

query and the from(...) CRUD helpers use the service-role key and bypass row-level security — they're built for trusted agent and server-side use. For your end-users' app traffic, set up RLS policies and use the anon/auth keys in your app code as usual.

Storage: buckets and objects

File storage is one call away — no separate bucket provisioning step, no storage dashboard:

await naive.storage.createBucket("uploads", { public: true });
await naive.storage.upload("uploads", "notes.txt", "hello world");
 
const objects = await naive.storage.list("uploads", { prefix: "" });
const file = await naive.storage.download("uploads", "notes.txt");

Text and JSON objects upload directly; for binary uploads, request a signed URL and stream the file to it. Objects live on the same project as your database, so a record and the file it points at never drift apart.

Auth: end-user authentication and admin users

The auth primitive covers both halves of authentication — configuring how end-users sign in, and managing those users administratively:

// Configure auth for the app
await naive.auth.updateConfig({ site_url: "https://myapp.com" });
 
// Admin user management (GoTrue)
await naive.auth.users.create({ email: "user@example.com", password: "a-strong-password" });
const { users } = await naive.auth.users.list();

Admin user management runs through GoTrue with the service-role key held server-side, so an agent can provision and manage your application's users without ever holding the credential that lets it do so.

Edge functions: serverless logic on demand

When an app needs a bit of server-side logic — a webhook handler, a scheduled cleanup, a signed-URL minter — deploy it as an edge function and invoke it like any other call:

// Invoke a deployed function
const result = await naive.functions.invoke("send-welcome", { email: "user@example.com" });
 
// List what's deployed
const fns = await naive.functions.list();

Functions run on the app's project, so they share its database, storage, and auth without extra wiring.

What you can build with the backend stack

Ship a full SaaS from a single prompt — Pair /backend with /apps and /orchestration. An engineer Employee scaffolds the schema with migrate, wires up auth, deploys the frontend, and the product is live with real persistence — not a mock.

Give every customer their own isolated backend — Provision a fullstack app per tenant. Each customer gets a dedicated database, storage, and auth project, so data isolation is physical, not a WHERE tenant_id = clause you hope nobody forgets.

Let an agent own its application data — A support or analytics Employee can read and write to the same Postgres your app uses, run ad-hoc SQL to answer questions, and store results — all auditable, all through the runtime.

Store generated assets next to their records — Generate a logo with /image, drop it in a storage bucket, and reference it from a row — one project, one key, no cross-service plumbing.

Add server logic without a deploy pipeline — Push an edge function for the one operation that shouldn't run on the client, and invoke it from the app or from an agent loop.

Get started

Drop this starter prompt into any coding agent to wire up Naïve:

Read https://usenaive.ai/skill.md and use it to set up Naïve in my project.

Frequently Asked Questions
What is the /backend stack?+
It's the set of Build primitives — database, storage, auth, and edge functions — that operate on a fullstack Naïve app's managed Supabase project. Together they give an agent everything it needs to stand up application data and logic behind the apps it deploys, all through the same Naïve API key.
Do I need an app first?+
Yes. The database, storage, auth, and functions primitives all operate on a fullstack app's project. Create one with naive.apps.create({ name, type: "fullstack" }) and wait for provisioning. After that the SDK auto-resolves your single fullstack app, so you never pass a project ref or app id unless you own several apps.
Where does my data actually live?+
On a managed Postgres + Supabase project provisioned for your app. Naïve routes every call through an app-scoped proxy using the project's service-role key, which is held server-side — it never reaches the agent or the client. You get the power of direct database access without handing a long-lived secret to an autonomous agent.
How do migrations work?+
naive.database.migrate(sql) applies schema changes (DDL) via the Management API query endpoint, so it works on any plan. For everyday reads and writes use naive.database.query(sql) for raw SQL or naive.database.from(table) for PostgREST-style CRUD.
Is the database access RLS-protected?+
The SDK's CRUD and query helpers use the service-role key, which bypasses row-level security — they're meant for trusted server-side and agent use. For your end-users' app traffic, configure RLS policies and use the anon/auth keys in your app code as usual.
How do I get started with the backend stack?+
Create a fullstack app, then call naive.database, naive.storage, naive.auth, and naive.functions. Per-primitive guides live at usenaive.ai/docs/getting-started/database, /storage, /auth, and /functions, and the quickstart is at usenaive.ai/docs/getting-started/quickstart.
DZ
Dennis ZaxCTO

CTO of Naïve. Building the open-source agent runtime.

@denniszax