Input Schemas
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.
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.
---
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.
pnpm add valibot
```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:
- Frontmatter
ts vitehub-email-schemacss vitehub-email-style- 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.
---
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.
Recommended workflow
Use this workflow when you are adding a new email:
- Create the Markdown email and frontmatter first.
- Render it with
renderEmail()while the template is still simple. - Add an embedded schema block once the template input stabilizes.
- Add an embedded CSS block only if you need custom styling beyond inline attributes and utility classes.