Build interactive forms in .astro components with typed binding, browser and server validation, encrypted view state, signed sessions, and chunked uploads.
npm install @astro-utils/forms @astrojs/nodeastro.config.mjs
import { defineConfig } from 'astro/config';
import node from '@astrojs/node';
import forms from '@astro-utils/forms/dist/integration.js';
export default defineConfig({
output: 'server',
adapter: node({ mode: 'standalone' }),
integrations: [forms]
});src/middleware.ts
import forms from '@astro-utils/forms';
export const onRequest = forms();These defaults are for local development. Configure a stable environment secret before deploying; see configuration. Use Astro's sequence() when combining middleware.
src/layouts/Layout.astro
---
import { WebForms } from '@astro-utils/forms/forms.js';
---
<WebForms>
<slot />
</WebForms>---
import { BButton, BInput, BOption, BSelect, BTextarea, Bind, BindForm, FormErrors } from '@astro-utils/forms/forms.js';
type ProfileForm = {
name: string;
age: number;
about?: string;
favoriteFood?: 'Pizza' | 'Salad' | 'Lasagna';
};
const form = Bind<ProfileForm>();
let result = '';
function submit() {
result = `${form.name} is ${form.age} years old.`;
}
---
<BindForm bind={form}>
<FormErrors title="Check the form" />
<label for="name">Name</label>
<BInput id="name" name="name" maxlength={20} required />
<label for="age">Age</label>
<BInput id="age" name="age" type="int" min={1} required />
<label for="about">About you</label>
<BTextarea id="about" name="about" maxlength={300} />
<label for="food">Favorite food</label>
<BSelect id="food" name="favoriteFood" required={false}>
<BOption disabled selected>Choose a food</BOption>
<BOption>Pizza</BOption>
<BOption>Salad</BOption>
<BOption>Lasagna</BOption>
</BSelect>
<BButton onClick={submit} whenFormOK>Submit</BButton>
{result && <p>{result}</p>}
</BindForm>whenFormOK prevents the callback from running when validation fails. FormErrors renders the server validation messages for the active form.
Generated identities distinguish controls rendered from the same source location. No index parameter is required:
<BindForm>
{items.map(item => (
<BButton onClick={removeItem} extra={item.id}>
Remove {item.name}
</BButton>
))}
</BindForm>- Binding and validation: typed values, validation, and state.
- Lists and reusable components: independent forms and actions in repeated UI.
- Large-file uploads: chunks, progress, and failure handling.
- Server helpers: redirects, response overrides, and parent refreshes.
View state and sessions serve different purposes. Sessions are signed, not encrypted; use application storage for durable data. Keep passwords out of persisted view state.
If Astro Utils helps you build, a star on GitHub is appreciated. Bug reports, useful examples, and pull requests help too. Thank you for supporting the project.