Input Schemas

Add an embedded schema block when you want email input validation and stronger TypeScript inference.

Input schemas validate template input before ViteHub renders the email. They also improve TypeScript inference for renderEmail() and sendEmail().

The schema is not a separate file. It lives in the Markdown email itself as an embedded ts vitehub-email-schema block.

Start without a schema if you are new to the package. Add one when you want input checks, better editor help, or stricter runtime guarantees.

Add an embedded schema block

Place the schema block at the top of the Markdown file. When frontmatter exists, put the schema block immediately after it.

If the email also has an embedded css vitehub-email-style block, place the CSS block immediately after the schema block.

welcome.md
---
subject: "Welcome {{ name }}"
---
```ts vitehub-email-schema
import * as v from 'valibot'

const input = v.object({
  actionUrl: v.pipe(v.string(), v.url()),
  name: v.string(),
})

export default input
```

Hello {{ name }}

The schema block must use a default export. ViteHub reads that exported schema during discovery and uses it at runtime.

Supported libraries

ViteHub works with validators that implement the Standard Schema contract. That includes Valibot, Zod, ArkType, and other compatible libraries.

Terminal
pnpm add valibot
welcome.md
```ts vitehub-email-schema
import * as v from 'valibot'

const input = v.object({
  actionUrl: v.pipe(v.string(), v.url()),
  name: v.string(),
})

export default input
```

Runtime behavior

When a schema exists, ViteHub validates the input before rendering the email. If validation succeeds, ViteHub uses the validated value for interpolation and rendering.

If validation fails, ViteHub throws before it renders or sends the message. If no schema exists, ViteHub skips validation and renders with the input you pass in.

Ordering rules

ViteHub expects the embedded authoring blocks in a fixed order:

  1. Frontmatter
  2. ts vitehub-email-schema
  3. css vitehub-email-style
  4. Markdown body

Use this order whenever you combine metadata, validation, and styles in the same email file.

Keep the schema block at the top of the file. Do not place it later in the body.

Frontmatter still handles email metadata

Keep message metadata in frontmatter and keep validation in the embedded schema block.

welcome.md
---
subject: "Welcome {{ name }}"
preheader: Finish setting up your account
layout: transactional
---
```ts vitehub-email-schema
const input = mySchemaLibrary.object({
  name: mySchemaLibrary.string(),
})

export default input
```

# Welcome, {{ name }}

Frontmatter keys such as subject, preheader, layout, delivery, and envelope still live in the Markdown file. The full reference lives on Email frontmatter.

Type inference

The schema improves the inferred input type for renderEmail() and sendEmail(). Your editor can then autocomplete the expected fields and flag missing or invalid values earlier.

Use this workflow when you are adding a new email:

  1. Create the Markdown email and frontmatter first.
  2. Render it with renderEmail() while the template is still simple.
  3. Add an embedded schema block once the template input stabilizes.
  4. Add an embedded CSS block only if you need custom styling beyond inline attributes and utility classes.