# How Instatic Validates Data at Untyped Boundaries Using TypeBox

> Instatic validates all incoming untyped data using TypeBox schemas before processing. Discover how Instatic enforces a strict validate then trust contract for robust data integrity.

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

---

**Instatic enforces a strict "validate‑then‑trust" contract where all data entering from HTTP, `JSON.parse`, plugin manifests, persisted files, or inter‑process communication is validated against a TypeBox schema before downstream code ever sees it.**

Instatic's architecture treats every external data source as an untyped boundary. Rather than casting or assuming shapes, the codebase routes all ingress through specialized utilities that validate data using TypeBox schemas. This article examines the five validation entry points, the compiled validator pattern, and how Instatic prevents "as‑cast" shortcuts at every boundary.

## The Core Validation Philosophy

Instatic's design documentation explicitly defines five boundary rules covering HTTP traffic, JSON parsing, raw fetch calls, raw `req.json()` usage, and field casting. According to the [TypeBox patterns documentation](https://github.com/CoreBunch/Instatic/blob/main/docs/reference/typebox-patterns.md), no data may cross into the trusted interior without schema validation. This policy resides in [`docs/reference/typebox-patterns.md`](https://github.com/CoreBunch/Instatic/blob/main/docs/reference/typebox-patterns.md) and guides all implementation decisions.

The practical result: downstream handlers, React components, and plugin runtimes never receive unchecked data. Every validation failure produces either a typed error or a safe fallback, never an ambiguous shape.

## HTTP Boundary: Browser to Server

### Client-Side Validation with `apiRequest`

Every browser‑to‑server call routes through [`src/core/http/apiClient.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/http/apiClient.ts). The `apiRequest` function automatically validates success payloads against a supplied TypeBox schema and throws a typed `ApiError` on failure.

```typescript
import { apiRequest } from '../core/http/apiClient';
import { UsersResponseSchema } from '../schemas/api';

// Validation happens automatically before the promise resolves
const users = await apiRequest('/admin/api/cms/users', {
  schema: UsersResponseSchema
});

```

The `apiRequest` utility in [`src/core/http/apiClient.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/http/apiClient.ts) guarantees that `UsersResponseSchema` is enforced before React state receives the data. Invalid payloads never reach component code.

### Server-Side Validation with `readValidatedBody`

On the server, request bodies are parsed exclusively via `readValidatedBody` in [`server/http.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/http.ts). This helper parses the raw request, validates with TypeBox, and passes only confirmed‑valid data to route handlers.

```typescript
import { readValidatedBody } from '../server/http';
import { CreateUserSchema } from '../schemas/api';

export async function createUserRoute(req: Request) {
  // Throws early if body fails schema validation
  const body = await readValidatedBody(req, CreateUserSchema);
  
  // From here, `body` is trusted to match CreateUserSchema
  return createUserInDatabase(body);
}

```

The [`server/http.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/http.ts) implementation ensures downstream handlers never see unchecked data. This creates symmetric validation: client and server both enforce schemas at the HTTP boundary.

## JSON Parsing Boundaries

### Core Validation Helpers in [`jsonValidate.ts`](https://github.com/CoreBunch/Instatic/blob/main/jsonValidate.ts)

The [`src/core/utils/jsonValidate.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/utils/jsonValidate.ts) module provides three primitives for JSON validation at untyped boundaries:

- **`safeParseJson`** – parses a string and validates it, returning a discriminated union for hard‑error versus soft‑fallback handling
- **`parseJsonWithFallback`** – "best‑effort" parsing for local storage or optional config, returning a default on any failure
- **`parseJsonResponse`** – parses a `Response` and validates it, throwing on schema mismatch

```typescript
import { safeParseJson, parseJsonWithFallback } from '../core/utils/jsonValidate';
import { SettingsSchema, SiteSchema } from '../schemas/persistence';

// Hard validation: success or error
const result = safeParseJson(rawSiteJson, SiteSchema);
if (result.success) {
  // result.data is guaranteed to match SiteSchema
} else {
  // Handle validation error explicitly
}

// Soft validation with safe default
const settings = parseJsonWithFallback(
  localStorage.getItem('settings'),
  SettingsSchema,
  /* fallback */ { theme: 'system', fontSize: 16 }
);
// Corrupted JSON silently falls back, never crashes the UI

```

`parseJsonWithFallback` is particularly valuable for local storage and optional configuration where corrupted data should degrade gracefully rather than throw.

### Persistence Layer Integration

For database JSON columns, [`src/core/persistence/validate.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/persistence/validate.ts) uses `safeParseJson` to guarantee stored documents conform to canonical schemas before loading into the editor:

```typescript
import { safeParseJson } from '../core/utils/jsonValidate';
import { SiteSchema } from '../schemas/site';

// Loading from DB JSON column
const site = safeParseJson(rawSiteJson, SiteSchema);
// site.data is trusted; site.error indicates migration or corruption

```

## Server-to-Server and Internal Fetch

Internal service calls use `readEnvelope` in server code, enforcing the same schema check without a second‑level wrapper:

```typescript
import { readEnvelope } from '../server/http';

const res = await fetch('https://internal.service/api/data');
const data = await readEnvelope(res, InternalDataSchema);
// Throws on schema mismatch; otherwise data is trusted

```

This maintains validation consistency even when Instatic services communicate with each other.

## Plugin Manifest Validation

Plugin manifests undergo validation before runtime instantiation via `parsePluginManifest` in the plugin SDK:

```typescript
import { parsePluginManifest } from '../plugin-sdk/manifest';

const manifest = parsePluginManifest(rawJson);
// manifest is guaranteed to match PluginManifestSchema
// Plugin runtime is only instantiated after validation passes

```

This prevents malformed or malicious plugin configurations from entering the system.

## Compiled Validators for Performance

Instatic avoids recompiling TypeBox schemas on every validation call. The [`src/core/utils/typeboxCompiler.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/utils/typeboxCompiler.ts) module compiles each schema once and reuses the generated validator:

```typescript
// In src/core/utils/typeboxCompiler.ts
import { TypeCompiler } from '@sinclair/typebox/compiler';

const compileCache = new Map<string, ReturnType<typeof TypeCompiler.Compile>>();

export function getCompiledValidator<T extends TSchema>(schema: T) {
  const key = JSON.stringify(schema); // Simplified; actual implementation uses stable hashing
  if (!compileCache.has(key)) {
    compileCache.set(key, TypeCompiler.Compile(schema));
  }
  return compileCache.get(key)!;
}

```

This compile‑once pattern ensures fast runtime checks even under high throughput, keeping validation overhead minimal at every boundary.

## Boundary Validation Summary

| Boundary | Entry Point | Source File | Failure Behavior |
|----------|-------------|-------------|----------------|
| Browser → Server | `apiRequest` | [`src/core/http/apiClient.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/http/apiClient.ts) | Throws `ApiError` |
| Server request body | `readValidatedBody` | [`server/http.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/http.ts) | Throws validation error |
| Server → Server fetch | `readEnvelope` | [`server/http.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/http.ts) | Throws on mismatch |
| Local storage / config | `parseJsonWithFallback` | [`src/core/utils/jsonValidate.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/utils/jsonValidate.ts) | Returns safe default |
| Persistence (DB JSON) | `safeParseJson` | [`src/core/persistence/validate.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/persistence/validate.ts) | Returns discriminated union |
| Plugin manifests | `parsePluginManifest` | Plugin SDK | Prevents instantiation |

All paths converge on the compiled validators from [`src/core/utils/typeboxCompiler.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/utils/typeboxCompiler.ts), ensuring consistent performance characteristics across the codebase.

## Integration with Type System

TypeBox schemas serve double duty: they produce both runtime validators and static TypeScript types. Instatic leverages this to eliminate drift between validation logic and type annotations. When a schema changes, the compiler enforces updates at all usage sites.

This is implemented according to the [TypeBox patterns documentation](https://github.com/CoreBunch/Instatic/blob/main/docs/reference/typebox-patterns.md), which mandates that no boundary may use raw `as` casting or unvalidated `JSON.parse` calls.

## Summary

- **Five boundary rules** documented in [`docs/reference/typebox-patterns.md`](https://github.com/CoreBunch/Instatic/blob/main/docs/reference/typebox-patterns.md) govern all untyped data entry
- **`apiRequest`** and **`readValidatedBody`** enforce symmetric HTTP validation between client and server
- **[`jsonValidate.ts`](https://github.com/CoreBunch/Instatic/blob/main/jsonValidate.ts)** primitives (`safeParseJson`, `parseJsonWithFallback`, `parseJsonResponse`) handle JSON parsing boundaries with appropriate failure modes
- **[`typeboxCompiler.ts`](https://github.com/CoreBunch/Instatic/blob/main/typeboxCompiler.ts)** compiles schemas once for performant repeated validation
- Plugin manifests, persistence layers, and internal service calls all route through the same validation pipeline
- **No "as‑cast" shortcuts** exist in the codebase; every boundary validates before trusting

## Frequently Asked Questions

### What happens when TypeBox validation fails at an HTTP boundary?

The `apiRequest` client utility throws a typed `ApiError` containing validation details, while `readValidatedBody` on the server throws a validation error before the route handler executes. Both prevent unchecked data from reaching application logic. As implemented in CoreBunch/Instatic, these errors include the specific schema violations for debugging.

### Why does Instatic use TypeBox instead of Zod or Yup?

TypeBox provides both runtime validation and static TypeScript type inference from a single schema definition. The [`src/core/utils/typeboxCompiler.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/utils/typeboxCompiler.ts) module additionally leverages TypeBox's compilation API for optimized validators. This dual purpose reduces maintenance overhead and prevents type‑validation drift that can occur with separate systems.

### How does Instatic handle corrupted data in localStorage?

The `parseJsonWithFallback` helper in [`src/core/utils/jsonValidate.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/utils/jsonValidate.ts) returns a configured default value when JSON is malformed or fails schema validation. This "best‑effort" pattern ensures UI stability—corrupted settings silently reset to safe defaults rather than crashing components.

### Is there performance overhead from validating every boundary?

Minimal. The [`typeboxCompiler.ts`](https://github.com/CoreBunch/Instatic/blob/main/typeboxCompiler.ts) utility compiles each TypeBox schema once and caches the result, making subsequent validations fast native code checks. According to the Instatic source, this compile‑once pattern keeps validation overhead negligible even for high‑frequency operations like real‑time sync.