# TypeBox Validation Patterns at Instatic System Boundaries

> Explore TypeBox validation patterns at Instatic system boundaries. Discover how Instatic enforces data integrity with canonical helpers, ensuring trusted data throughout your application.

- Repository: [CoreBunch/Instatic](https://github.com/CoreBunch/Instatic)
- Tags: deep-dive
- Published: 2026-07-27

---

**Instatic uses TypeBox schemas at every I/O boundary, enforcing a "validate once, trust everywhere" philosophy through canonical helpers like `readValidatedBody`, `apiRequest`, and `safeParseJson` that prevent malformed data from reaching business logic.**

Instatic, an open-source content management system maintained by CoreBunch, implements strict TypeBox validation patterns to guard all system boundaries. The repository CoreBunch/Instatic centralizes data integrity checks in reusable utilities, ensuring that every HTTP request, local storage entry, and plugin manifest conforms to expected schemas before any business logic executes.

## Server-Side HTTP TypeBox Validation Patterns

### The readValidatedBody Pattern

According to the Instatic source code, every incoming HTTP request from the browser passes through `readValidatedBody` in [`server/http.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/http.ts) (lines 47‑58). This generic function accepts a TypeBox schema and validates the request body before any handler logic processes the data.

```typescript
// server/http.ts
import { type TSchema, type Static } from '@sinclair/typebox';
import { Value } from '@sinclair/typebox/value';

export async function readValidatedBody<T extends TSchema>(
  req: Request, 
  schema: T
): Promise<Static<T>> {
  const body = await req.json();
  
  if (!Value.Check(schema, body)) {
    throw new Error('Request body validation failed');
  }
  
  return body;
}

```

All CMS handlers—including those in [`server/cms/users.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/cms/users.ts) and [`server/cms/siteDocument.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/cms/siteDocument.ts)—use this pattern to guarantee that route inputs match their TypeBox schemas.

## Client-Side HTTP Validation

### Response Validation via apiRequest

The client-side HTTP wrapper `apiRequest` in [`src/core/http/apiClient.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/http/apiClient.ts) (lines 150‑166) validates success responses against TypeBox schemas before returning data to calling components. This function throws a unified `ApiError` when validation fails.

```typescript
// src/core/http/apiClient.ts
import { type TSchema, type Static } from '@sinclair/typebox';
import { Value } from '@sinclair/typebox/value';

export class ApiError extends Error {
  constructor(message: string, public status: number) {
    super(message);
  }
}

export async function apiRequest<S extends TSchema>(
  path: string, 
  options: RequestInit & { schema: S }
): Promise<Static<S>> {
  const res = await fetch(path, options);
  const data = await res.json();
  
  if (!res.ok) throw new ApiError('Request failed', res.status);
  if (!Value.Check(options.schema, data)) {
    throw new ApiError('Response validation failed', 500);
  }
  
  return data;
}

```

### Envelope Validation with readEnvelope

When handling `{ data, error }` envelope responses from the CMS persistence layer, `readEnvelope` in [`src/core/persistence/cmsTransfer.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/persistence/cmsTransfer.ts) (lines 11‑22) validates the JSON payload against a supplied TypeBox schema while checking the `res.ok` status.

```typescript
// src/core/persistence/cmsTransfer.ts
import { type TSchema, type Static } from '@sinclair/typebox';
import { Value } from '@sinclair/typebox/value';

export async function readEnvelope<T extends TSchema>(
  res: Response,
  schema: T,
  fallbackMsg: string
): Promise<Static<T>> {
  if (!res.ok) throw new Error(fallbackMsg);
  
  const json = await res.json();
  if (!Value.Check(schema, json.data)) {
    throw new Error('Envelope data validation failed');
  }
  
  return json.data;
}

```

## Low-Level JSON TypeBox Validation Utilities

### parseJsonResponse and jsonValidate

The foundational `parseJsonResponse` and `jsonValidate` utilities in [`src/core/utils/jsonValidate.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/utils/jsonValidate.ts) (lines 2‑30) serve as building blocks for higher-level validators. These functions parse response bodies and execute TypeBox validators directly.

```typescript
// src/core/utils/jsonValidate.ts
import { Value } from '@sinclair/typebox/value';
import { type TSchema, type Static } from '@sinclair/typebox';

export async function parseJsonResponse<T extends TSchema>(
  res: Response, 
  schema: T
): Promise<Static<T>> {
  const data = await res.json();
  
  if (!Value.Check(schema, data)) {
    throw new Error('JSON validation failed against TypeBox schema');
  }
  
  return data;
}

```

### Safe Local Storage Parsing

For client-side persistence boundaries including `localStorage` and IndexedDB, `safeParseJson` and `parseJsonWithFallback` in [`src/core/utils/jsonValidate.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/utils/jsonValidate.ts) (lines 21‑35) provide corruption-resistant validation with fallback defaults. These functions guard clipboard payloads and spotlight history against corrupted storage data.

```typescript
// src/core/utils/jsonValidate.ts
import { Value } from '@sinclair/typebox/value';

export function safeParseJson<T extends TSchema>(
  raw: string | null, 
  schema: T
): Static<T> | null {
  if (!raw) return null;
  
  try {
    const parsed = JSON.parse(raw);
    return Value.Check(schema, parsed) ? parsed : null;
  } catch {
    return null;
  }
}

export function parseJsonWithFallback<T extends TSchema>(
  raw: string | null,
  schema: T,
  defaultValue: Static<T>
): Static<T> {
  return safeParseJson(raw, schema) ?? defaultValue;
}

```

## Domain-Specific TypeBox Validation Patterns

### Plugin Manifest Parsing

When loading plugin bundles, `parsePluginManifest` in [`src/core/plugins/manifest.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/plugins/manifest.ts) (lines 70‑90) validates the [`plugin.json`](https://github.com/CoreBunch/Instatic/blob/main/plugin.json) file against a strict TypeBox schema defined in the same file. This ensures the manifest conforms to the expected shape before the system loads the plugin.

```typescript
// src/core/plugins/manifest.ts
import { Type, type Static } from '@sinclair/typebox';
import { Value } from '@sinclair/typebox/value';

const PluginManifestSchema = Type.Object({
  name: Type.String(),
  version: Type.String(),
  entry: Type.String()
});

export function parsePluginManifest(raw: unknown): Static<typeof PluginManifestSchema> {
  if (!Value.Check(PluginManifestSchema, raw)) {
    throw new Error('Invalid plugin manifest format');
  }
  return raw;
}

```

### Site Document Validation

The site-runtime loader uses `validateSite` in [`src/core/persistence/validate.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/persistence/validate.ts) (lines 7‑12) to validate entire site JSON documents against TypeBox schemas before editors or publishers interact with the data.

```typescript
// src/core/persistence/validate.ts
import { Type, type Static } from '@sinclair/typebox';
import { Value } from '@sinclair/typebox/value';

const SiteDocumentSchema = Type.Object({
  id: Type.String(),
  components: Type.Array(Type.Any())
});

export function validateSite(document: unknown): Static<typeof SiteDocumentSchema> {
  if (!Value.Check(SiteDocumentSchema, document)) {
    throw new Error('Site document validation failed');
  }
  return document;
}

```

## Schema Definitions and Error Handling

### Centralized Schema Definitions

As implemented in CoreBunch/Instatic, every module that persists data ships its own TypeBox schema as the single source of truth. For example, [`src/core/visualComponents/schemas.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/visualComponents/schemas.ts) (lines 2‑10) exports schemas for visual components, layouts, fonts, and data tables.

```typescript
// src/core/visualComponents/schemas.ts
import { Type } from '@sinclair/typebox';

export const VisualComponentSchema = Type.Object({
  id: Type.String(),
  type: Type.String(),
  props: Type.Record(Type.String(), Type.Any())
});

```

### Error Message Extraction

The `getErrorMessage` utility in [`src/core/utils/errorMessage.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/utils/errorMessage.ts) (lines 1‑12) wraps `ApiError` instances and TypeBox validation failures into user-friendly strings for the global toast notification system.

```typescript
// src/core/utils/errorMessage.ts
export function getErrorMessage(err: unknown, fallback: string): string {
  if (err instanceof Error) return err.message;
  if (typeof err === 'string') return err;
  return fallback;
}

```

## Summary

- **Single-responsibility validators**: Every I/O boundary uses dedicated helpers like `readValidatedBody`, `apiRequest`, and `safeParseJson` that own TypeBox validation exclusively, as defined in [`server/http.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/http.ts) and [`src/core/utils/jsonValidate.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/utils/jsonValidate.ts).
- **Schema-driven architecture**: All validation accepts `TSchema` arguments from TypeBox, eliminating ad-hoc type casting and manual property checks throughout the CoreBunch/Instatic codebase.
- **Centralized error handling**: Validation failures propagate as `ApiError` on the client or typed server errors, then render via global toast notifications using `getErrorMessage`.
- **Consistent naming conventions**: Helpers follow clear patterns (`read*`, `parse*`, `validate*`) making validation boundaries obvious in [`src/core/persistence/cmsTransfer.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/persistence/cmsTransfer.ts) and related modules.
- **Defense in depth**: From HTTP requests in [`server/http.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/http.ts) to local storage entries and plugin manifests in [`src/core/plugins/manifest.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/plugins/manifest.ts), every external input validates exactly once before entering the trusted system core.

## Frequently Asked Questions

### What validation library does Instatic use at system boundaries?

Instatic uses **TypeBox** (`@sinclair/typebox`) as its primary validation library. Every system boundary—from HTTP requests in [`server/http.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/http.ts) to local storage in [`src/core/utils/jsonValidate.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/utils/jsonValidate.ts)—uses TypeBox schemas as the single source of truth for data integrity.

### How does Instatic handle validation errors?

Validation errors are converted into unified `ApiError` instances on the client side or thrown as typed errors on the server. The `getErrorMessage` utility in [`src/core/utils/errorMessage.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/utils/errorMessage.ts) extracts human-readable messages from these errors for display in the global toast notification system.

### Why does Instatic validate data at every system boundary?

The codebase follows a **"validate once, trust everywhere"** philosophy. By enforcing TypeBox schemas at every I/O boundary—whether browser-to-server HTTP, plugin manifests in [`src/core/plugins/manifest.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/plugins/manifest.ts), or local storage—Instatic guarantees that malformed or corrupted data never reaches business logic, preventing runtime errors and data corruption.

### Where are TypeBox schemas defined in the Instatic repository?

TypeBox schemas are defined adjacent to the data they describe. For example, visual component schemas live in [`src/core/visualComponents/schemas.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/visualComponents/schemas.ts), site document schemas in [`src/core/persistence/validate.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/persistence/validate.ts), and plugin manifest schemas in [`src/core/plugins/manifest.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/plugins/manifest.ts). This co-location ensures schemas remain synchronized with their usage.