Skip to content
ultrastorageultrastorage
Esc
↑↓navigate↵open⌘Jpreview
On this page

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.

Was this page helpful?