# Design your output schemas

A session's `schema` is a contract: it's the exact JSON shape the model must return, and it's what every downstream session and your application reads. A good schema makes the model more accurate (it knows precisely what to produce) and makes the blueprint's output easy to consume. This guide covers the full syntax and the design habits that keep schemas clean.

## Prerequisites

- The [Anatomy of a session](/content/docs/guides/anatomy-of-a-session/index.html) guide — you should know where the `schema` sits in a session.

## The basics

A schema block lists fields and their types:

```
schema {
    summary:   string
    keyPoints: string[]
    published: bool
}
```

Syntax rules:

- Field names are **unquoted identifiers** — `summary`, not `"summary"`.
- Types: `string`, `int`, `float`, `bool`, nested `{ }` objects, `Type[]` arrays, and `$ComponentName` refs.
- Mark a field **optional** by appending `?`: `url?: string`. Everything else is required.

## Comments are instructions, not decoration

This is the highest-leverage habit in the whole language. Inline `#` comments on schema fields are **read by the model** and used as field descriptions:

```
schema {
    summary:   string    # one concise paragraph, no bullet points
    sentiment: string    # exactly one of: positive, neutral, negative
    score:     int       # confidence 0-100
}
```

Those comments meaningfully steer the output. Always add one to every field unless the name is completely self-explanatory — they're the cheapest quality win available.

## Keep the root flat

The session name is _already_ the outer wrapper for its output. A session called `overview` publishes under `context.overview`, so wrapping the fields in another `overview` object just creates redundant nesting:

```
# AVOID — produces context.overview.overview.summary
schema {
    overview: {
        summary:   string
        keyPoints: string[]
    }
}

# PREFER — produces context.overview.summary
schema {
    summary:   string
    keyPoints: string[]
}
```

Nest only when the data genuinely has sub-structure:

```
# FINE — metadata is a real sub-object
schema {
    summary:  string
    metadata: {
        author:    string
        createdAt: string   # ISO 8601 timestamp
    }
}
```

## Scalar and array shorthand

When a session's whole output is a single value or a flat list, skip the block and declare the type directly:

```
schema string[]     # the output is a list of strings
schema int          # the output is a single integer
```

This pairs naturally with `iterate`: an iterating session runs once per element and collects the results into an array, so its schema **must be an array**, either `schema type[]` or a block with `[]`:

```
session("elaborate",
        after="gather",
        iterate="context.gather.keyPoints") {
    - Expand the key point "{{ .it }}" into a paragraph.
    schema string[]     # array required when iterate is set
}
```

## Reuse shapes with `components`

When the same structure appears in more than one session, define it once in a root-level `components` block and reference it with `$Name`:

```
components {
    schema("SourceRef") {
        id:    string   # unique identifier of the source
        title: string   # human-readable title
        url?:  string   # optional URL; omit if unavailable
    }
}

session("gather") {
    - Extract the sources used.
    schema {
        summary: string
        sources: $SourceRef[]   # reuse the component as an array
    }
}
```

Component schemas follow the exact same syntax as session schema: fields, optionals, comments, nesting. Reference one anywhere a type is expected: `sources: $SourceRef[]` or `primary: $SourceRef`.

## Sessions without a schema

A schema is optional. Omit it and the session still runs — its output is just the model's raw text response instead of a structured object:

```
parameter("query", type=string, title="Search Query")

session("search_web") {
    use search

+ Search the internet for information about: {{ .params.query }}

- Provide a comprehensive answer based on the search results.
}
```

The session's result lands in `context.search_web` (and in the blueprint output) as a single string — the model's free-form answer verbatim:

```
{
  "search_web": "Spain won the 2026 FIFA World Cup, held across Canada, Mexico, and the United States, defeating Argentina 1–0 in the final. Ferran Torres scored the winning goal in the 106th minute of extra time at New York/New Jersey Stadium on July 19, 2026."
}
```

Skip the schema when the session's job is to produce prose for a person to read, or when a downstream session will consume the text as free-form input. Add a schema as soon as you need to reference specific fields (`context.search_web.answer`), enforce a shape, or hand structured data to another tool — a downstream session can't reach into a raw string.

## Common mistakes

| Mistake | Fix |
| --- | --- |
| Wrapping fields in a top-level object named after the session | Define fields flat at the root — the session name is the wrapper |
| Quoting field names | Use unquoted identifiers: `summary: string` |
| A non-array schema on an `iterate` session | Use `schema type[]` or `schema { ... }[]` |
| Skipping field comments | Add a `#` description to every non-obvious field, the model reads them |
| Referencing `$Name` with no `components` block | Define the type in a root-level `components { }` block |

## Next steps

- Feed schema output between sessions in [Data flow: params, vars & context](/content/docs/guides/data-flow-in-a-plan/index.html).
- Produce one result per item with `iterate` in [Control the flow of a blueprint](/content/docs/guides/control-plan-flow/index.html).

Shape a session's typed output — field syntax, comments the model reads as instructions, flat-vs-nested structure, scalar shorthand, and reusable $Component refs.
