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

`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

```sh
$ 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

| Type | Zod | Notes |
| --- | --- | --- |
| `text` | `z.string()` | plain string; `string` is an alias |
| `email` | `z.email()` | validated address |
| `url` | `z.url()` | validated URL |
| `number` | `z.number()` | `integer`, `int`, `float`, `decimal` and `double` are aliases |
| `bool` | `z.boolean()` | renders as a checkbox; `boolean` is an alias |
| `date` | `z.string()` (ISO) | `datetime` and `timestamp` are aliases |
| `editor` | `z.string()` | long or rich text, renders as a textarea |
| `password` | `z.string()` | stored hashed |
| `json` | `z.any()` | a JSON blob |
| `geoPoint` | `{ lat, lon }` | latitude and longitude |
| `autodate` | | set on create or update; name it `created` or `updated` |
| `file` / `files` | | one 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_user` | `z.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**

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

```sh
$ vela generate schema settings theme:select(light:Light,dark:Dark) notifications:bool
```

**src/lib/schemas/setting.ts**

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

```sh
$ vela generate scaffold pets name:text! owner:user
```

```sh
$ 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](https://velastack.dev/tutorials/sveltekit-login-and-signup) 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.

```sh
$ vela generate resource articles title:text! body:editor author:current_user
```

**src/lib/schemas/article.ts**

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

```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](https://velastack.dev/tutorials/sveltekit-teams-and-invites) 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:

```sh
$ 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:

```sh
$ 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

- [Generate a schema](https://velastack.dev/tutorials/sveltekit-zod-schema-generator), [a form](https://velastack.dev/tutorials/sveltekit-form-validation-zod-superforms), [a model](https://velastack.dev/tutorials/sveltekit-database-model-from-cli) or [a full scaffold](https://velastack.dev/tutorials/sveltekit-crud-scaffold) with this syntax
- [Change fields later with migrations](https://velastack.dev/patterns/generate-migration), which use the same syntax for `add` and `references`
- [Docs: vela generate](https://docs.velastack.dev/generate) is the reference version of this page
