Use VAIF with AI app builders
AI app builders such as Lovable and v0, and coding agents in editors such as Cursor, can build a frontend on VAIF from these docs and the SDK. They tend to get the same few things wrong unless they are told, so this page lists them and ends with a prompt you can paste into the tool.
Which key goes where
Section titled “Which key goes where”| Key | Prefix | Where it goes |
|---|---|---|
| Publishable key | vk_pub_ |
The frontend. Row rules apply to it, so it is safe in a browser bundle |
| Secret key | vk_srv_ |
Never the frontend. It bypasses row rules. Only your own server or CI uses it, and the API refuses it from browser origins |
Both are on Security > Credentials, next to the project ID. A frontend needs only the project ID and the publishable key. Signed-in users get their own access token from the SDK.
Environment variables for each tool
Section titled “Environment variables for each tool”Use these names everywhere, so the tool, the docs and your teammates agree:
| Tool | What it builds | Variables |
|---|---|---|
| Lovable | Vite and React apps | VITE_VAIF_PROJECT_ID, VITE_VAIF_PUBLISHABLE_KEY |
| v0 | Next.js apps | NEXT_PUBLIC_VAIF_PROJECT_ID, NEXT_PUBLIC_VAIF_PUBLISHABLE_KEY |
| Coding agents in your editor | Whatever your project uses | The Vite or Next.js names above for a frontend; VAIF_PROJECT_ID and VAIF_PUBLISHABLE_KEY (and VAIF_SECRET_KEY, on a server only) for Node.js |
import type { ReactNode } from "react";import { createVaifClient } from "@vaifstudio/client";import { VaifProvider } from "@vaifstudio/react";
const vaif = createVaifClient({ baseUrl: "https://api.vaif.studio", projectId: import.meta.env.VITE_VAIF_PROJECT_ID, apiKey: import.meta.env.VITE_VAIF_PUBLISHABLE_KEY, // vk_pub_: never the secret key});
export function Providers({ children }: { children: ReactNode }) { return <VaifProvider client={vaif}>{children}</VaifProvider>;}In a browser with a publishable key, the SDK keeps the user signed in by itself: the refresh token travels in the response body and is replaced on every refresh. The app stores no tokens of its own. See Session management.
Give every table a row rule
Section titled “Give every table a row rule”A table with no rules at all is open to every signed-in user, for reading and writing: any user of the app can read and change every row. Requests with no signed-in user are denied unless the table has a public rule. So for every table the tool creates:
- a claim rule for a table that holds a user’s data. In a SQL file, put
-- vaif:owner author_idon the line aboveCREATE TABLE; - a public rule, usually for reading only, for what signed-out visitors may see. In a SQL file, put
-- vaif:public-readaboveCREATE TABLE.
With a claim rule that covers inserts, the server fills the owner column from the signed-in user: the app sends a post without author_id, and a row that names someone else is refused. See Row rules.
Tables are created with a SQL migration file and vaif db push, or on the console’s Schema page. The SQL editor cannot run CREATE TABLE.
Column names are the table’s own
Section titled “Column names are the table’s own”Columns are whatever the SQL says, which is usually snake_case: created_at, author_id, user_id. Tools trained on other SDKs often write createdAt. VAIF does not rename columns, so createdAt names a column that does not exist. Write the row types with the real names, or generate them with vaif generate.
Query options can be inline
Section titled “Query options can be inline”With @vaifstudio/react, query options may be written inline in the component. A query runs again only when what the options say changes, not on every render.
import { useQuery } from "@vaifstudio/react";
type Post = { id: string; title: string; author_id: string; created_at: string };
export function MyPosts() { const { data, isLoading } = useQuery<Post>("posts", { orderBy: { field: "created_at", direction: "desc" }, limit: 20, }); if (isLoading) return <p>Loading…</p>; return <ul>{data.map((p) => <li key={p.id}>{p.title}</li>)}</ul>;}Redirect URLs for preview deploys
Section titled “Redirect URLs for preview deploys”Social sign-in and magic links send the user back to a page of your app, and that page’s origin must be on the project’s allowed redirect URLs (Authentication > URL configuration). Matching is by exact origin, with no wildcards. A preview deploy usually has an origin of its own, so add each one you test sign-in on, plus http://localhost:5173 or http://localhost:3000 for local development.
A prompt to paste into the tool
Section titled “A prompt to paste into the tool”This app uses VAIF Studio as its backend, with @vaifstudio/client and @vaifstudio/react.
Keys and config- The frontend uses ONLY the project ID and the PUBLISHABLE key (starts with vk_pub_). Never put the secret key (vk_srv_) in frontend code, env files the browser reads, or the repo.- Vite: import.meta.env.VITE_VAIF_PROJECT_ID and import.meta.env.VITE_VAIF_PUBLISHABLE_KEY. Next.js: process.env.NEXT_PUBLIC_VAIF_PROJECT_ID and process.env.NEXT_PUBLIC_VAIF_PUBLISHABLE_KEY.- Create one client with createVaifClient({ baseUrl: "https://api.vaif.studio", projectId, apiKey }) and wrap the app in <VaifProvider client={vaif}>. Use useAuth() for sign-up, sign-in and sign-out. Do not store tokens yourself; the SDK keeps the session.
Data- Column names are exactly the table's (snake_case: created_at, author_id). Never camelCase them.- Every table that holds user data has an owner column (for example author_id uuid NOT NULL) and the line "-- vaif:owner author_id" directly above its CREATE TABLE. A table with no rule is open to every signed-in user.- Do NOT send the owner column on insert: the server fills it from the signed-in user.- Only tables signed-out visitors may read get "-- vaif:public-read" above their CREATE TABLE.- Create tables with SQL migration files in ./migrations and `vaif db push`, not the SQL editor.- useQuery and useCount options can be inline objects. Filter with where: { field: "status", operator: "eq", value: "active" }; order with orderBy: { field: "created_at", direction: "desc" }.
Sign-in redirects- Social sign-in: useOAuth().signInWithOAuth("google" or "github", redirectUrl). On the return page, call client.auth.handleOAuthCallback() and pass the result to setSession() from useVaif().- Every page users return to must have its exact origin on the project's allowed redirect URLs, including each preview deploy and localhost.