@contentrail/core
Framework-independent ContentRail schemas, validation, and inferred content types.
This package defines collections and validation only. It does not read files, talk to networks, store content, authenticate users, or publish sites.
Requirements
- Node.js ≥ 22 (
engines.node) - TypeScript 5.9+ recommended for consumers
- Zod
^4.6.7(dependency + peer; nativezodentry — notzod/v3)
Prefer validateEntry / validateCollection / assertValidEntry for untrusted
input. Calling schema.safeParse() directly bypasses ContentRail JSON bounds.
Upgrading from ContentRail 0.1.0 (Zod 3): see docs/MIGRATION_ZOD4.md.
Install
Monorepo (current)
pnpm install
pnpm --filter @contentrail/core build
From npm (0.2.6)
pnpm add @contentrail/[email protected] zod@^4.6.7
Define a collection
import { defineCollection } from "@contentrail/core";
import { z } from "zod";
const articles = defineCollection({
name: "articles",
schema: z.object({
title: z.string().min(1),
slug: z.string().min(1),
published: z.boolean(),
publishedAt: z.string().optional(),
}),
});
Collection names must match ^[a-z][a-z0-9_-]*$.
Unexpected fields
unknownKeys | Behaviour |
|---|---|
"strip" (default) | Unknown root-object keys are removed. |
"strict" | Unknown root-object keys fail validation. |
The policy is applied when the schema is a Zod object. Non-object schemas are left unchanged.
Validate an entry
import { validateEntry } from "@contentrail/core";
const result = validateEntry(articles, {
title: "Welcome",
slug: "welcome",
published: true,
});
if (!result.success) {
console.error(result.error.issues);
} else {
console.log(result.data.title);
}
validateEntry never throws for invalid content. Use assertValidEntry when you prefer exceptions.
Validate a collection (array of entries)
import { validateCollection } from "@contentrail/core";
const result = validateCollection(articles, [
{ title: "Welcome", slug: "welcome", published: true },
{ title: "Next", slug: "next", published: false },
]);
Failing issues are prefixed with the entry index, for example 1.title.
Type inference
import type { InferEntry } from "@contentrail/core";
type Article = InferEntry<typeof articles>;
// { title: string; slug: string; published: boolean; publishedAt?: string }
ContentSchema
ContentSchema is the public Zod 4 schema constraint used by
defineCollection / ContentCollection generics. It is an alias of
z.ZodType from the native zod entry:
import { defineCollection, type ContentSchema } from "@contentrail/core";
import { z } from "zod";
const schema: ContentSchema = z.object({
title: z.string().min(1),
});
const pages = defineCollection({
name: "pages",
schema,
});
Prefer defining schemas with z and letting defineCollection infer types;
use ContentSchema when you need an explicit annotation.
Errors
| Error | When |
|---|---|
ContentDefinitionError | Invalid defineCollection input (thrown). |
ContentValidationError | Invalid content (validate* result or assertValidEntry). |
ContentValidationError includes:
collection— collection nameissues—{ path, code, message, expected?, received? }[]toJSON()— stable serializable shape
Public exports
From @contentrail/core:
defineCollectionvalidateEntryvalidateCollectionassertValidEntryContentDefinitionErrorContentValidationError- Types:
ContentSchema,ContentCollection,DefineCollectionInput,InferEntry,UnknownKeysPolicy,ValidationResult,ValidationSuccess,ValidationFailure,ValidationIssue
There is a single browser-safe export map entry (.). No server-only subpath exists in this package.
Compatibility
- Runtime: any modern ESM environment (browsers, Node, Workers) that can run Zod
- Module format: ESM only
- Not coupled to Astro, Vite, Cloudflare, or React
Current limitations
- No content I/O, Git, R2, or publishing APIs
- No editor schema introspection helpers beyond Zod itself
- No JSON Schema export
- Requires Zod 4.6.7+; Zod 3 schemas are not supported
- See root README /
docs/PUBLISHING.mdfor0.2.6publication status