mrkeyoor.com_
Sat 08 Aug 20:58 UTC
npmUtilsupdated 08 Aug 2026

superstruct

Superstruct is a runtime data-validation library for JavaScript and TypeScript. You compose small struct objects that describe values, arrays, objects, unions, records, and custom refinements, then assert, test, validate, coerce, or mask unknown input. Successful checks narrow TypeScript types, while failures expose a StructError with the failing value, key, path, branch, refinement, and an iterator over all failures. It works in browsers and Node and has no runtime dependencies.

Verdict

Superstruct remains a clean, small validator with unusually understandable composition and error inspection. Its inactive release cadence is the reason not to choose it for a new long-lived TypeScript platform unless the API is a distinctly better fit than Zod or Valibot.

API stability4/5Version 2's core factories and assert, is, validate, create, and mask operations have stayed unchanged since 2.0.2 in 2024. The API is small and explicit, and the package has no dependencies that can shift behavior underneath it. That calm also reflects repository inactivity, so stability is strong for pinned existing schemas but not evidence of future platform adaptation.
Docs4/5The official site separates validation, coercion, refinement, error, and TypeScript guides from references for core operations, types, coercions, utilities, and StructError. It documents subtle facts such as strict object keys, type retaining extras, strictNullChecks for optional fields, invalid dates, map traversal, and create being required for defaults. Some examples and surrounding site tooling feel dated.
Maintenance2/5The latest npm version, 2.0.2, was published in July 2024 and the GitHub repository's last push was October 2024. The project is not archived or deprecated, and its fixed API can continue working, but 103 open issues and pull requests remain against a repository with more than 7,000 stars. New platform compatibility and bug fixes should not be assumed.
Ecosystem3/5Superstruct works in browser and server code, has built-in TypeScript narrowing, no runtime dependencies, detailed errors, and enough primitives to integrate with any framework manually. It sees millions of weekly downloads and has a JSR badge, but the surrounding adapters, code generators, form resolvers, OpenAPI bridges, and current community material are much thinner than Zod's ecosystem.

Use it if

  • You want small composable validators with detailed paths and custom refinements rather than a large batteries-included schema package
  • You need both throwing assertions, Boolean type guards, and tuple-style validation from the same schema
  • You want to strip unknown object properties with mask or deliberately permit them with type
  • You value a dependency-free package with ESM, CommonJS, browser, and TypeScript support
Skip it if

Setup reality

npm install superstruct adds no runtime dependencies, no peer dependencies, and ships TypeScript declarations plus ESM and CommonJS builds. The current 2.0.2 package declares Node 14 or newer, although browser bundlers can consume its module build. There is no compiler, plugin, provider, or config file. The work is designing schemas and choosing the correct entry operation. object is strict about extra properties and will fail when an API adds an unexpected key; type allows additional properties, while mask returns a copy with unknown keys removed only when used with strict object-style schemas. assert throws and narrows, is returns a Boolean type guard, and validate returns an error/value tuple. Coercions and defaulted values do not run through assert or is. Call create, mask, or validate with { coerce: true } when you actually want transformation. That distinction is an easy production bug because a schema can look correct while a default never applies. StructError.failures() provides every failure, but converting paths and messages into a public API error format is your job. number accepts Infinity, date rejects invalid Date instances, and regexp validates a RegExp object rather than testing a string against it; use refinements such as pattern for content constraints. optional permits undefined, nullable permits null, and they are not interchangeable. TypeScript users should enable strict or at least strictNullChecks. Inference works well for ordinary structs, but recursive lazy structures need an explicit type. The repository has been quiet since 2024, so pin the version, cover schema behavior with tests, and compare the migration cost to Zod or Valibot before making it foundational in a new codebase.

Patterns

Assert an unknown objectassert-object-shape

import { assert, object, array, number, string } from 'superstruct'

const Article = object({
  id: number(),
  title: string(),
  tags: array(string()),
})

assert(payload, Article)
console.log(payload.title)

object rejects unknown properties. After assert succeeds, TypeScript narrows payload to the inferred shape.

Check data without throwingtest-with-type-guard

import { is, object, number, string } from 'superstruct'

const User = object({ id: number(), name: string() })
if (is(input, User)) {
  console.log(input.name)
}

is validates but does not run coercions or apply default values.

Return every validation failurecollect-validation-failures

import { validate } from 'superstruct'

const [error, value] = validate(input, User)
if (error) {
  const problems = [...error.failures()].map(({ path, message }) => ({
    path: path.join('.'),
    message,
  }))
  return { ok: false, problems }
}
return { ok: true, value }

The tuple contains an error or a typed value; do not read value on the error branch.

Create a value with defaultsapply-default-values

import { create, defaulted, number, object, string } from 'superstruct'

const Page = object({
  query: string(),
  limit: defaulted(number(), 20),
})
const page = create({ query: 'printers' }, Page)

Defaults run through create. Calling assert or is on the same input will not fill limit.

Mask keys outside a strict schemastrip-unknown-properties

import { mask, object, number, string } from 'superstruct'

const PublicUser = object({ id: number(), name: string() })
const clean = mask({ id: 1, name: 'Ada', admin: true }, PublicUser)
// { id: 1, name: 'Ada' }

mask with type retains extra properties; use object when stripping is intended.

Distinguish missing and null fieldsallow-optional-nullable

import { nullable, object, optional, string } from 'superstruct'

const ProfilePatch = object({
  displayName: optional(string()),
  biography: optional(nullable(string())),
})

optional accepts undefined and nullable accepts null. Enable strictNullChecks for correct TypeScript inference.

Define an application-specific stringdefine-custom-type

import { define, object, string } from 'superstruct'

const OrderId = define('OrderId', (value) =>
  typeof value === 'string' && /^ord_[a-z0-9]+$/.test(value),
)
const Order = object({ id: OrderId, status: string() })

define returns a validation failure when the predicate is false; choose a stable name for useful error messages.

Add a readable refinementrefine-string-value

import { refine, string } from 'superstruct'

const NonBlank = refine(string(), 'NonBlank', (value) =>
  value.trim().length > 0 || 'Expected a non-blank string',
)

A refinement can return a message instead of only false, which improves StructError output.

Validate a string-keyed recordvalidate-record-values

import { integer, record, string } from 'superstruct'

const Counters = record(string(), integer())
assert({ queued: 3, running: 1 }, Counters)

record validates arbitrary keys and values; object is for a fixed set of named properties.

Infer a TypeScript type from a structinfer-typescript-type

import { Infer, object, optional, string } from 'superstruct'

const Account = object({
  id: string(),
  nickname: optional(string()),
})
type Account = Infer<typeof Account>

Recursive lazy structs cannot always be inferred and need an explicit TypeScript type.

Alternatives

PackageRegistryPick it when
zodnpmYou want the largest TypeScript-first validation ecosystem, frequent maintenance, and broad framework support
valibotnpmYou want a modular tree-shakeable schema library with a current TypeScript-focused API
yupnpmYou need familiar object and form validation with extensive coercion and conditional schema behavior
ajvnpmYour source of truth is JSON Schema and schemas must work across languages and tooling