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.

The generated files: schema module, +server.ts API route, and +page.svelte screen, with svgrid:managed markers.

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:

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 get ssr wherever a screen qualifies. When you generate an app - svgrid-studio init, the designer's New app wizard, or crudAppFromSchemas - every qualifying screen on a SQL, REST, or in-memory entity starts in ssr. You can still switch any screen either way in the app designer, and a screen you set to spa stays spa.

In-memory data is held once, on the server. The seeded rows live in src/lib/server/store.ts, and every screen reaches them through the entity's /api/<entity> route, the same way a SQL entity does. So a server-rendered screen and a client screen read and write the same rows, and the route's validation, access rules, and triggers apply to both. See In-memory for what that means in a deployed app.

PGlite apps stay spa: PGlite keeps its rows in the browser's IndexedDB, which the server cannot read.

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 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