The field syntax behind vela generate - types, modifiers, relations and nested models

Every vela generator reads the same one-line field syntax. This is the whole grammar with an example of what each piece generates, from name:text! to select(...), current_user and users/pets.

4 min read generatorsschemapocketbasezodsveltekit

vela generate form, schema, resource, scaffold and migration all take the same input: a model name followed by fields. Learn it once and every generator reads the same way. This page is the grammar, with real generated output for each rule.

The shape

$ vela generate <type> <name> [field:type[modifier] ...]

The name is the model. It is pluralised for you (pet and pets both give a pets collection) and can be nested with a slash: users/pets. Each field is name:type, and modifiers hang off the end.

Types

TypeZodNotes
textz.string()plain string; string is an alias
emailz.email()validated address
urlz.url()validated URL
numberz.number()integer, int, float, decimal and double are aliases
boolz.boolean()renders as a checkbox; boolean is an alias
datez.string() (ISO)datetime and timestamp are aliases
editorz.string()long or rich text, renders as a textarea
passwordz.string()stored hashed
jsonz.any()a JSON blob
geoPoint{ lat, lon }latitude and longitude
autodateset on create or update; name it created or updated
file / filesone upload, or many when plural
select(...)z.enum([...])options in the parentheses, see below
<model>z.string()any existing model name is a relation, see below
current_userz.string()relation to the signed-in user, see below

Modifiers

! makes a field required, both in the collection and in the Zod schema. Everything else is optional and gets .optional():

src/lib/schemas/address.ts
import { z } from "zod";

export const addressSchema = z.object({
  street: z.string().nonempty(),
  city: z.string().nonempty(),
  postal: z.string().nonempty(),
  note: z.string().optional(),
});

Plural is meaningful. A plural select is a multi-select, a plural files accepts several uploads, and a plural relation is many-to-many.

Selects

Options go inside the parentheses as value:Label pairs, or bare values when the label is the same:

$ vela generate schema settings theme:select(light:Light,dark:Dark) notifications:bool
src/lib/schemas/setting.ts
import { z } from "zod";

export const settingSchema = z.object({
  theme: z.enum(["light", "dark"]).optional(),
  notifications: z.boolean().default(false).optional(),
});

In a scaffold the labels become the options of a select input and the values are what gets stored.

Relations

Use the name of another model as the type. Singular means one, plural means many:

$ vela generate scaffold pets name:text! owner:user
$ vela generate scaffold teams name:text! members:users

references infers the target from the field name, Rails-style: author:references relates to authors. Relations are stored as record ids, so they are strings in the schema.

Ownership: current_user

With auth enabled, current_user is a relation to users with two extra behaviours: generated forms leave the field out and fill it from the session, and the collection’s access rules restrict rows to their owner.

$ vela generate resource articles title:text! body:editor author:current_user
src/lib/schemas/article.ts
import { z } from "zod";
import type { Schemas } from "@velastack/pocketbase";

export const articleSchema = z.object({
  id: z.string().optional(),
  collectionId: z.string().optional(),
  title: z.string().nonempty(),
  body: z.string().optional(),
  author: z.string(),
}) satisfies Schemas["articles"];

The create action a scaffold generates for it reads the user from the session rather than the form:

src/routes/(app)/pets/new/+page.server.ts
import { fail, redirect } from "@sveltejs/kit";
import { superValidate } from "sveltekit-superforms";
import { zod4 } from "sveltekit-superforms/adapters";
import { setPocketbaseErrors } from "@velastack/pocketbase/form";
import { petSchema } from "$lib/schemas/pet";

export const load = async ({ locals }) => {
  return { form: await superValidate(zod4(petSchema)) };
};

export const actions = {
  default: async ({ locals, request }) => {
    const form = await superValidate(request, zod4(petSchema));

    if (!form.valid) {
      return fail(400, { form });
    }

    let pet;

    try {
      pet = await locals.pb.collection("pets").create({
        ...form.data,
        owner: locals.pb.authStore.record?.id,
      });
    } catch (error) {
      setPocketbaseErrors(form, error);
      return fail(400, { form });
    }

    return redirect(303, `/pets/${pet.id}`);
  },
};

With teams enabled, current_team does the same for the active team.

Nested models

A slash nests one model under another, which nests the routes and scopes the collection:

$ vela generate scaffold users/pets name:text!

That generates routes under /users/[user_id]/pets and a pets collection with a user relation. When the parent (or a parent of the parent) has a relation to the signed-in user, access rules follow the chain: only members reach the nested rows.

Placement: --route

Generators put routes under (app) when auth is enabled and (public) when it is not. --route overrides that, dynamic segments included:

$ vela generate scaffold projects name:text! --route "(app)/[team_id]/projects"

Every generated href and redirect interpolates params.team_id. Keep the route you used: vela destroy scaffold projects --route ... needs it to find the files again.

Tip
Once a collection exists you can omit the fields entirely. vela generate scaffold pets reads the current fields from the database and generates the pages for them, which is how you add a UI to a model that was created some other way.

Going further

Related tutorials

Define a database collection and schema for SvelteKit from the command line

A new model in a SvelteKit app means a database table, a migration, a Zod schema and TypeScript types that agree with each other. vela generate resource creates all of them from one field list, without opening an admin UI.

3 min read

CRUD with SvelteKit remote functions - a scaffold without form actions

The same list, create, view and edit pages as the classic scaffold, but the forms post to typed remote functions instead of form actions. vela generate scaffold --remote writes the routes, the .remote.ts handlers, the schema, the collection and the tests.

3 min read

SvelteKit CRUD in one command - list, create, view and edit pages with a database table

A full CRUD interface in SvelteKit is ten files, a data table, two forms and a collection. vela generate scaffold writes all of it from a field list, tested, so you can spend the afternoon on the parts that are specific to your app.

4 min read