Code generation
Studio generates plain SvelteKit code you own - there is no runtime and no
lock-in. The same scaffold() core powers the CLI, the
AI generator, and the visual designer, so
all three emit identical files.

What gets generated
From one EntitySchema, three files:
| File | Contents |
|---|---|
src/lib/<name>.schema.ts |
The EntitySchema literal + a typed row type. |
src/routes/api/<name>/+server.ts |
The API route - createKitHandlers over a data source. |
src/routes/<name>/+page.svelte |
The screen - grid + edit panel, wired to the route. |
The generated page is a full data screen: server-side sort, filter, and global
search; a native pagination footer; a validated create / edit modal;
multi-select optimistic delete; and loading / error / empty states. Any
relation (foreign-key) field also gets a searchable lookup wired to the
related entity's API route (Relations).
The three files, up close
1. The schema is the single source of truth - a plain literal you can edit by hand or regenerate:
// src/lib/customers.schema.ts
import type { EntitySchema } from '@svgrid/enterprise'
export type CustomersRow = {
id: string; name: string; email: string; mrr: number; active: boolean
}
export const customersSchema: EntitySchema<CustomersRow> = {
name: 'customers',
idField: 'id',
fields: [
{ field: 'id', type: 'text', primaryKey: true, readonly: true },
{ field: 'name', type: 'text', required: true },
{ field: 'email', type: 'text', format: 'email', required: true },
{ field: 'mrr', type: 'number', min: 0 },
{ field: 'active', type: 'boolean' },
],
}
2. The API route is a single POST endpoint. createKitHandlers speaks one
JSON wire protocol for read + create + update + delete, so the client needs just
the one handler:
// src/routes/api/customers/+server.ts
import { createInMemoryDataSource, createKitHandlers } from '@svgrid/enterprise'
import { customersSchema, type CustomersRow } from '$lib/customers.schema'
const source = createInMemoryDataSource<CustomersRow>([], customersSchema)
export const { POST } = createKitHandlers({ schema: customersSchema, source })
Swap createInMemoryDataSource for a SQL, Supabase, or REST source and the page
does not change - that is the ServerDataSource contract at
work.
3. The screen wires the grid and the edit panel to that route through the
controller - server-side sort / filter / page, the create-edit modal, and
optimistic delete. It is a normal +page.svelte you own; see the
Studio live SQL demo for the
whole thing running.
Managed regions & safe regeneration
Every generated file wraps its body in markers:
// svgrid:managed:start
// Regenerated by SvGrid Studio. Edits inside these markers are overwritten.
...generated code...
// svgrid:managed:end
Re-running generation replaces only the managed region and preserves everything outside it. So you can:
- Add imports, helpers, and layout outside the markers - they survive regeneration.
- Re-run
addafter a schema or database change to pull in new columns without losing your customizations.
This idempotent regeneration is what makes the generator safe to keep using, rather than a one-shot scaffold you fork away from.
Choosing the data source
The generated +server.ts backend depends on how you scaffold:
| Scaffold | Backend |
|---|---|
--db <dialect> |
Connected to that driver via process.env.DATABASE_URL. |
--sql |
createSqlDataSource with an execute() stub to fill in. |
--from (no --db) |
In-memory, so the screen runs immediately. |
See Databases and Data binding.
Render mode: SPA or SSR per screen
An ssr screen emits idiomatic server-rendered SvelteKit: a
+page.server.ts with a load function and form actions, URL-driven sort /
filter / page state, and progressive enhancement. A spa screen emits a
client page where the browser talks to the API route through the data-source
controller.
New apps built on a database or a REST API get ssr for free. When you
generate an app - svgrid-studio init, the designer's New app wizard, or
crudAppFromSchemas - every screen that qualifies starts in ssr. You can still
switch any screen either way in the app designer.
In-memory and PGlite apps stay spa, on purpose. Those sources are module
singletons, so a server-rendered screen would read and write the server's copy of
the rows while the app's remaining client screens read the browser's: add a row
on one and the other never sees it. SQL and REST have no such split, because
every path goes to the same database or the same remote API.
How the app is wired
Once an app has at least one server-rendered screen, the root src/routes/+layout.ts
leaves SvelteKit's own default in place - server rendering on - and each
client-only screen opts out in its own +page.ts:
// src/routes/<screen>/+page.ts
export const ssr = false
So the nav shell, the home page, and the sign-in pages all render on the server,
and only the screens that fetch in the browser skip it. An app with nothing to
server-render keeps the single export const ssr = false in the root layout, as
before.
Not every screen shape can emit as SSR. The rules:
- The screen must be entity-bound, without a code-behind companion.
- Its data source must be
memory(runs in-process),sql(reuses the connected/apiroute viaevent.fetch), orreston an absolute URL (the server calls the remote API directly; a relative URL has no origin to resolve against there, so it stays SPA).supabaseandpglitescreens stay SPA - PGlite only exists in the browser, and a Supabase read carries the signed-in user's token, which a server-side call with the anon key would silently drop. - A grid, optionally with a facet panel, emits as load + form actions (no
tree data, no scheduler view). The facet panel becomes a plain
GETform whose controls are named for the URL params theloadreads, so filtering works with JavaScript off and every filtered view has a shareable URL. - Read-only block screens - any mix of chart, pivot, dashboard, KPI, gauge, tree, detail, and master-detail - emit as a load-only page.
- Anything else (boards, calendars, UI component blocks, containers, grids with extras) stays SPA.
The designer only offers the toggle when the screen qualifies; a screen set to
ssr that stops qualifying falls back to the SPA emit.
Verification
The AI path compiles the generated page (via the Svelte compiler) before handing it back, and the recommended final step everywhere is your project's own check:
npx svelte-check
See also
Related articles
- SvGrid Studio Tips and Tricks: Build Svelte Data Apps, Fast - Practical SvGrid Studio tips - AI app generation, a designer that emits real SvelteKit, schema-driven edit forms, code-behind with ctx.grid, production auth/data/deploy toggles, and real data-source binding - each with a snippet.