API Reference
Complete API documentation for Formit.
Table of Contents
createForm(init?)
Creates a new form instance.
Parameters
init?: FormInit– Optional configuration object
type FormInit = {
fields?: Record<string, any>; // Plain values, nested objects, or FieldConfig
validate?: FormValidator; // Form-level validator
};
type FieldConfig = {
value?: any;
validators?: FieldValidator | FieldValidator[];
};
type FieldValidator = (value: FormDataEntryValue) => string | undefined | null | Promise<string | undefined | null>;
type FormValidator = (
formData: FormData,
) => Map<string, string> | undefined | null | Promise<Map<string, string> | undefined | null>;Returns
A form instance with the following methods:
Form Instance Methods
get(name)
Get a field value. Returns array if multiple values exist for the same name.
get(name: string): anyExamples:
form.get('email'); // string | File | null
form.get('tags'); // string[] (if multiple values)
form.get('user.profile.name'); // nested accessset(name, value, options?)
Set a single field value.
set(name: string, value: any, options?: {
markDirty?: boolean; // default: true
markTouched?: boolean; // default: false
}): voidExamples:
form.set('email', 'user@example.com');
form.set('age', 25, { markDirty: false });
form.set('tags', ['js', 'ts']);
form.set('avatar', fileInput.files[0]);set(entries, options?)
Set multiple fields at once.
set(entries: Record<string, any> | FormData, options?: {
replace?: boolean; // default: false
markDirty?: boolean; // default: false
}): voidExamples:
// Merge values
form.set({ email: 'user@example.com', name: 'Alice' });
// Replace all values
form.set({ email: 'new@example.com' }, { replace: true });
// Copy from another FormData
form.set(otherFormData);values()
Get all form values as a plain object.
values(): Record<string, any>Example:
const allValues = form.values();
// { email: 'user@example.com', name: 'Alice', ... }data()
Get the native FormData instance.
data(): FormDataExample:
const formData = form.data();
await fetch('/api/submit', { method: 'POST', body: formData });clone()
Clone the FormData for safe external mutation.
clone(): FormDataerror(name?)
Get error(s).
error(): Map<string, string> // Get all errors
error(name: string): string | undefined // Get specific errorExamples:
const emailError = form.error('email');
const allErrors = form.error();
// Iterate over all errors
for (const [field, message] of allErrors) {
console.log(`${field}: ${message}`);
}error(name, message)
Set or clear a single error.
error(name: string, message: string): voidExamples:
// Set error
form.error('email', 'This email is already taken');
// Clear error
form.error('email', '');errors(nextErrors)
Set multiple errors at once.
errors(nextErrors: Map<string, string> | Record<string, string>): voidExamples:
// Set from object
form.errors({ email: 'Invalid', password: 'Too short' });
// Set from Map
form.errors(new Map([['email', 'Invalid']]));
// Clear all errors
form.errors({});
form.errors(new Map());dirty(name)
Check if a field has been modified from its initial value.
dirty(name: string): booleanExample:
if (form.dirty('email')) {
console.log('Email has been changed');
}touch(name)
Check if a field has been touched (user interacted with it).
touch(name: string): booleanExample:
if (form.touch('email')) {
console.log('User has focused on email field');
}touch(name, mark)
Mark a field as touched.
touch(name: string, mark: true): voidExample:
form.touch('email', true);validate(name)
Validate a specific field.
validate(name: string): Promise<string | undefined>Example:
const error = await form.validate('email');
if (error) {
console.log('Email validation failed:', error);
}validate(options?)
Validate multiple fields or the entire form.
validate(options?: {
signal?: AbortSignal;
onlyTouched?: boolean;
fields?: string[];
}): Promise<Map<string, string>>Examples:
// Validate all fields
const errors = await form.validate();
// Validate only touched fields
const errors = await form.validate({ onlyTouched: true });
// Validate specific fields
const errors = await form.validate({ fields: ['email', 'password'] });
// With abort signal
const controller = new AbortController();
const errors = await form.validate({ signal: controller.signal });submit(onSubmit, options?)
Submit the form with automatic validation.
submit(
onSubmit: (formData: FormData) => any | Promise<any>,
options?: {
signal?: AbortSignal;
validate?: boolean; // default: true
}
): Promise<any>Behavior:
- Validates form (unless
validate: false) - If validation fails, throws
ValidationError - If validation passes, calls
onSubmitwith FormData - Returns the result of
onSubmit
Examples:
// Basic submission
const result = await form.submit(async (formData) => {
const response = await fetch('/api/submit', {
method: 'POST',
body: formData,
});
return response.json();
});
// Skip validation
await form.submit(onSubmit, { validate: false });
// With abort signal
const controller = new AbortController();
await form.submit(onSubmit, { signal: controller.signal });
// Handle validation errors
try {
await form.submit(onSubmit);
} catch (error) {
if (error instanceof ValidationError) {
console.log('Validation failed:', error.errors);
}
}reset(newFormData?)
Reset the form to initial values or new values.
reset(newFormData?: FormData | Record<string, any>): voidExamples:
// Reset to initial values
form.reset();
// Reset to new values
form.reset({ email: '', password: '' });
// Reset from FormData
form.reset(otherFormData);subscribe(listener)
Subscribe to form state changes.
subscribe(listener: (state: FormState) => void): () => voidReturns: Unsubscribe function
Example:
const unsubscribe = form.subscribe((state) => {
console.log('Form state:', state);
console.log('Errors:', state.errors);
console.log('Dirty fields:', state.dirty);
console.log('Touched fields:', state.touched);
console.log('Is submitting:', state.isSubmitting);
console.log('Is validating:', state.isValidating);
console.log('Submit count:', state.submitCount);
});
// Later: cleanup
unsubscribe();subscribeField(name, listener)
Subscribe to specific field changes.
subscribeField(
name: string,
listener: (payload: {
value: any;
error?: string;
touched: boolean;
dirty: boolean;
}) => void
): () => voidReturns: Unsubscribe function
Example:
const unsubscribe = form.subscribeField('email', ({ value, error, touched, dirty }) => {
console.log('Email:', value);
console.log('Error:', error);
console.log('Touched:', touched);
console.log('Dirty:', dirty);
});
// Later: cleanup
unsubscribe();bind(name, config?)
Create a binding object for input elements.
bind(name: string, config?: {
valueExtractor?: (event: any) => any;
markTouchedOnBlur?: boolean; // default: true
}): {
name: string;
value: any;
onChange: (event: any) => void;
onBlur: () => void;
set: (value: any | ((prev: any) => any)) => void;
}Examples:
// Simple input binding
<input {...form.bind('email')} />
// Custom value extractor
const binding = form.bind('category', {
valueExtractor: (e) => e.selectedOption
});
// Disable touch on blur
<input {...form.bind('name', { markTouchedOnBlur: false })} />
// Programmatic updates
const emailBinding = form.bind('email');
emailBinding.set('new@example.com');
emailBinding.set((prev) => prev + '@domain.com');snapshot()
Get an immutable snapshot of the current form state.
snapshot(): FormStateReturns:
type FormState = {
errors: Map<string, string>;
touched: Set<string>;
dirty: Set<string>;
isValidating: boolean;
isSubmitting: boolean;
submitCount: number;
};Example:
const state = form.snapshot();
console.log(state.errors.get('email'));
console.log(state.dirty.has('email'));
console.log(state.touched.has('email'));Types
FormInit
type FormInit = {
fields?: Record<string, any>;
validate?: FormValidator;
};FieldConfig
type FieldConfig = {
value?: any;
validators?: FieldValidator | FieldValidator[];
};FieldValidator
type FieldValidator = (value: FormDataEntryValue) => string | undefined | null | Promise<string | undefined | null>;FormValidator
type FormValidator = (
formData: FormData,
) => Map<string, string> | undefined | null | Promise<Map<string, string> | undefined | null>;FormState
type FormState = {
errors: Map<string, string>;
touched: Set<string>;
dirty: Set<string>;
isValidating: boolean;
isSubmitting: boolean;
submitCount: number;
};ValidationError
class ValidationError extends Error {
readonly type: 'validation';
readonly errors: Map<string, string>;
}Example:
try {
await form.submit(onSubmit);
} catch (error) {
if (error instanceof ValidationError) {
for (const [field, message] of error.errors) {
console.log(`${field}: ${message}`);
}
}
}Complete Example
import { createForm, ValidationError } from '@vielzeug/formit';
const form = createForm({
fields: {
email: {
value: '',
validators: [(v) => !v && 'Email is required', (v) => v && !String(v).includes('@') && 'Invalid email'],
},
password: {
value: '',
validators: (v) => String(v).length < 8 && 'Min 8 characters',
},
confirmPassword: '',
},
validate: (formData) => {
const errors = new Map();
const password = formData.get('password');
const confirm = formData.get('confirmPassword');
if (password !== confirm) {
errors.set('confirmPassword', 'Passwords must match');
}
return errors;
},
});
// Subscribe to changes
const unsubscribe = form.subscribe((state) => {
updateUI(state);
});
// Handle submission
try {
const result = await form.submit(async (formData) => {
const response = await fetch('/api/register', {
method: 'POST',
body: formData,
});
return response.json();
});
console.log('Success:', result);
} catch (error) {
if (error instanceof ValidationError) {
console.log('Validation errors:', error.errors);
} else {
console.error('Submit error:', error);
}
}
// Cleanup
unsubscribe();