Schema validation
Validate and transform stored values with a synchronous Standard Schema.
Persisted data can outlive the code that created it, and users or other scripts can change it. A TypeScript type alone cannot guarantee that a stored value still has the shape your application expects.
Schema validation checks values when you read them, helping you handle outdated or invalid data before using it.
import { createStorage } from 'ultrastorage';
export const appStorage = createStorage({ prefix: 'acme' });
Validate with any libraries that support Standard Schema, as long as validation is synchronous.
import { z } from 'zod';
import { appStorage } from './lib/app-storage';
const UserSchema = z.object({ name: z.string(), age: z.number() });
// Returns typed value if valid, null if validation fails
const user = appStorage.getItem('user', { schema: UserSchema });
Read semantics
Validation happens on read, not on write. Invalid data reported as schema issues returns null and
remains in storage.
Successful transformations return the schema output. Async schemas throw a TypeError, and exceptions thrown by a schema propagate.
A TypeScript generic alone does not validate the persisted value. Browser storage can change outside your application, so use a schema when runtime validation matters.
For inferred React values and defaults, see React schemas and types.
appStorage.setItem('user', { name: 'Alice', age: 'unknown' });
appStorage.has('user'); // true
appStorage.getItem('user', { schema: UserSchema }); // null
Schemas are supplied per read, not registered against a key. getOrInit() and updateItem() read
without a schema; their type arguments do not validate existing values. Validate explicitly when
using these helpers with data that might have the wrong shape.
const CountSchema = z.string().transform(Number);
appStorage.setItem('count', '2');
appStorage.getItem('count', { schema: CountSchema }); // 2
appStorage.getItem('count'); // '2': storage still contains the original string
Persisting a transformation is a separate write. Before doing so, ensure the schema accepts that
output on subsequent reads: CountSchema above rejects a stored number because it requires a
string input. For schema changes, explicitly migrate entries into a format the new schema accepts.
This also matters for React setters, which write values using the schema’s output type.