Simple form
The simple way to validate forms in your fullstack app.
---import { z } from "zod";import { createForm } from "simple:form";
const checkout = createForm({ quantity: z.number(), email: z.string().email(), allowAlerts: z.boolean(),});
const result = await Astro.locals.form.getData(checkout);
if (result?.data) { await myDb.insert(result.data); // proceed to checkout}---
<form method="POST"> <label for="quantity">Quantity</label> <input id="quantity" {...checkout.inputProps.quantity} />
<label for="email">Email</label> <input id="email" {...checkout.inputProps.email} />
<label for="allowAlerts">Allow alerts</label> <input id="allowAlerts" {...checkout.inputProps.allowAlerts} /></form>Installation
Simple form is an Astro integration. You can install and configure this via the Astro CLI using astro add:
npm run astro add simple-stack-formAfter installing, you’ll need to add a type definition to your environment for editor hints. Add this reference to a new or existing src/env.d.ts file:
/// <reference types="simple-stack-form/types" />Simple form can be used with any framework. You can install it via npm:
# npmnpm i simple-stack-form
# pnpmpnpm i simple-stack-formCreate a validated form
Type: createForm(ZodRawShape): { inputProps: Record<string, InputProps>, validator: ZodRawShape }
You can create a simple form with the createForm() function. This lets you specify a validation schema using Zod, where each input corresponds to an object key. Simple form supports string, number, or boolean (checkbox) fields.
import { createForm } from "simple:form";import z from "zod";
const signupForm = createForm({ name: z.string(), age: z.number().min(18).optional(), newsletterOptIn: z.boolean(),});createForm() returns both a validator and the inputProps object. inputProps converts each key of your validator to matching HTML props / attributes. The following props are generated today:
name- the object key.type-checkboxfor booleans,numberfor numbers, andtextfor strings.aria-required-trueby default,falsewhen.optional()is used. Notearia-requiredis used to add semantic meaning for screenreaders, but leave room to add a custom error banner.
Our signupForm example generates the following inputProps object:
const signupForm = createForm({ name: z.string(), age: z.number().min(18).optional(), newsletterOptIn: z.boolean(),});
signupForm.inputProps;/* name: { name: 'name', type: 'text', 'aria-required': true } age: { name: 'age', type: 'number', 'aria-required': false } newsletterOptIn: { name: 'newsletterOptIn', type: 'checkbox', 'aria-required': true }*/Handle array values
You may want to submit multiple form values under the same name. This is common for multi-select file inputs, or generated inputs like “add a second contact.”
You can aggregate values under the same name using z.array() in your validator:
import { createForm } from "simple:form";import z from "zod";
const contact = createForm({ contactNames: z.array(z.string()),});Now, all inputs with the name contactNames will be aggregated. This uses FormData.getAll() behind the scenes:
---import { createForm } from "simple:form";import z from "zod";
const contact = createForm({ contactNames: z.array(z.string()),});
const res = await Astro.locals.form.getData(contact);console.log(res?.data);// contactNames: ["Ben", "George"]---
<form method="POST"> <label for="contact-1">Contact 1</label> <input id="contact-1" {...contact.inputProps.contactNames} /> {res.fieldErrors?.contactNames?.[0]} <label for="contact-2">Contact 2</label> <input id="contact-2" {...contact.inputProps.contactNames} /> {res.fieldErrors?.contactNames?.[1]}</form>Note that fieldErrors can be retrieved by index. For example, to get parse errors for the second input, use fieldErrors.contactNames[1].
Sanitize User Input
You may need to sanitize user input with rich text content. This is important for any text rendered as HTML to prevent Cross-Site Scripting (XSS) attacks. You can use the sanitize-html library for this:
npm install --save sanitize-htmlnpm install --save-dev @types/sanitize-htmlNext, call sanitize-html from your text validator with a Zod transform():
import sanitizeHtml from "sanitize-html";
const signupForm = createForm({ name: z.string(), name: z.string().transform((dirty) => sanitizeHtml(dirty)), age: z.number().min(18).optional(), newsletterOptIn: z.boolean(),});You can find a sanitization example in our Astro playground