[Feature Request] Surface Strapi schema constraints in generated types and validation artifacts
Summary
The generator correctly captures enumerations as TypeScript union types, but silently drops all
other constraint metadata present in the Strapi schema: required, min/max, default,
unique, etc.
This forces consumers to either re-declare these constraints manually or ship without them
entirely — defeating the purpose of a typed, schema-driven client.
There are four distinct gaps, ordered by impact and implementation effort.
Gap 1 — Required fields are not reflected in Input types
Type: Bug / Type correctness
Strapi schemas declare required: true on individual fields. The generator currently emits
every input field as optional and nullable regardless of that flag.
Schema example (reservation.json)
"dateFrom": { "type": "date", "required": true },
"dateTo": { "type": "date", "required": true },
"note": { "type": "text" }
Currently generated
export interface ReservationInput {
dateFrom?: string | null; // ❌ required in schema — missing field won't be caught by TS
dateTo?: string | null; // ❌ required in schema — missing field won't be caught by TS
note?: string | null; // ✅ correctly optional
}
Expected output
export interface ReservationInput {
dateFrom: string; // ✅ non-optional, non-nullable — compile error if omitted
dateTo: string; // ✅ non-optional, non-nullable — compile error if omitted
note?: string | null; // ✅ optional as before
}
Why this matters
The generator already does this correctly for the read type — Reservation.dateFrom is
string, not string | null, because the schema marks it required. The Input type should
mirror this symmetry. Currently a developer can write:
await strapi.reservations.create({ data: { note: "hello" } }); // ✅ TypeScript is happy
// ❌ Strapi returns HTTP 400 — dateFrom and dateTo are required
This is a pure TypeScript change — no runtime artifacts needed. The required flag maps
directly to removing ? and | null from the property in the Input interface.
⚠️ Breaking change — this narrows previously-nullable fields in Input types.
Suggest releasing under a minor or major version bump with a migration note.
Gap 2 — Numeric min/max and default values are silently dropped
Type: Enhancement — documentation / tooling
Strapi integer, decimal, and float fields support min, max, and default. These cannot
be expressed in TypeScript's type system natively, but they can be surfaced as
JSDoc annotations at zero runtime cost.
Schema example
"adult": { "type": "integer", "min": 0, "default": 0 },
"beforeDays": { "type": "integer", "min": 0, "default": 0 },
"pageSize": { "type": "integer", "min": 1, "max": 100, "default": 25 },
"commission": { "type": "float", "min": 0, "default": 0 }
Currently generated
adult?: number | null;
beforeDays?: number | null;
pageSize?: number | null;
commission?: number | null;
Expected output (with JSDoc)
/** @minimum 0 @default 0 */
adult?: number | null;
/** @minimum 0 @default 0 */
beforeDays?: number | null;
/** @minimum 1 @maximum 100 @default 25 */
pageSize?: number | null;
/** @minimum 0 @default 0 */
commission?: number | null;
Why this matters
JSDoc tags like @minimum, @maximum, and @default are understood by:
- IDEs (VS Code, WebStorm) — shown on hover
ts-to-zod — automatically generates .min() / .max() Zod refinements from JSDoc
typescript-json-schema — produces JSON Schema with numeric constraints
- TypeDoc — renders constraints in generated documentation
This surfaces constraints to the developer at the call site rather than requiring them to
cross-reference the Strapi admin panel or raw schema files.
Gap 3 — Default values not exported as runtime constants
Type: Enhancement — additive export
Strapi schema default values are useful for initialising form state and UI components
client-side. Currently they are not accessible without reading the raw schema JSON.
Proposed addition
A generated *Defaults constant per collection, exported alongside the existing types:
// ✅ Entirely static — derived from schema, zero runtime cost
export const ReservationDefaults = {
adult: 0,
beforeDays: 0,
afterDays: 0,
parking: 1,
crib: 0,
otaExpanse: 0,
tax: 0,
commission: 0,
advance: 0,
timeFrom: "14:00:00.000",
timeTo: "10:00:00.000",
state: "new",
} as const satisfies Partial<ReservationInput>;
The satisfies Partial<ReservationInput> constraint ensures the defaults object stays
in sync with the Input type — a rename or removal will produce a compile error.
Developer experience
// ✅ Before: hard-coded defaults scattered across the codebase
const newReservation: ReservationInput = {
dateFrom: today,
dateTo: tomorrow,
adult: 0, // where does this 0 come from?
state: "new", // magic string
};
// ✅ After: single source of truth from the schema
const newReservation: ReservationInput = {
...ReservationDefaults,
dateFrom: today,
dateTo: tomorrow,
};
Non-breaking — purely additive new export. Opt-in via a generator flag if bundle size
is a concern.
Gap 4 — No runtime validation schema (Zod / JSON Schema) generated
Type: Enhancement — optional output artifact
JSDoc covers documentation but not runtime validation. Consumers working with forms,
API gateways, or server actions currently have to write Zod schemas by hand that duplicate
what the Strapi schema already defines.
Proposed addition
An optional --validation zod CLI flag (or a separate generated file) that emits a Zod
schema per collection derived directly from the Strapi schema:
// generated/validation.ts (opt-in, not emitted by default)
import { z } from "zod";
export const ReservationInputSchema = z.object({
// required: true → non-optional
dateFrom: z.string(),
dateTo: z.string(),
// optional fields with constraints
slug: z.string().optional().nullable(),
adult: z.number().int().min(0).default(0).optional().nullable(),
beforeDays: z.number().int().min(0).default(0).optional().nullable(),
parking: z.number().int().min(0).default(1).optional().nullable(),
commission: z.number().min(0).default(0).optional().nullable(),
securityDeposit: z.number().int().optional().nullable(),
// enumerations
state: z.enum(["inquiry", "new", "modified", "cancelled"]).default("new").optional().nullable(),
// relations — accept StrapiID or RelationOperations
units: z.union([z.string(), z.number(), z.array(z.union([z.string(), z.number()])), z.object({ connect: z.array(z.any()).optional(), disconnect: z.array(z.any()).optional(), set: z.array(z.any()).optional() })]).optional().nullable(),
});
// Type alias derived from the schema — ReservationInput stays the single source of truth
export type ReservationInput = z.infer<typeof ReservationInputSchema>;
Strapi schema → Zod mapping table
| Strapi attribute |
Zod output |
required: true |
.nonoptional() (no ?) |
type: "integer" |
z.number().int() |
type: "float" / "decimal" |
z.number() |
type: "string" / "text" / "uid" |
z.string() |
type: "boolean" |
z.boolean() |
type: "date" / "datetime" |
z.string() (ISO 8601) |
type: "enumeration" |
z.enum([...values]) |
min: N |
.min(N) |
max: N |
.max(N) |
default: value |
.default(value) |
type: "relation" |
RelationInputSchema (shared helper) |
type: "media" (single) |
z.union([z.string(), z.number()]) |
type: "media" (multiple) |
z.array(z.union([z.string(), z.number()])) |
Why this matters
- Forms — pass directly to
react-hook-form resolver (zodResolver(ReservationInputSchema))
- Server actions — validate incoming payloads before hitting Strapi
- API gateways — validate at the edge without importing Strapi itself
- Single source of truth — constraints live in the Strapi schema, not duplicated in
application code
Non-breaking — purely additive, opt-in via CLI flag. Requires zod as a peer dependency
only when the flag is used.
Implementation priority
| # |
Gap |
Breaking |
Effort |
Value |
| 1 |
Required fields in Input types |
⚠️ Yes |
Low |
🔴 High — prevents silent HTTP 400s |
| 2 |
JSDoc @minimum / @maximum / @default |
No |
Low |
🟡 Medium — improves tooling |
| 3 |
*Defaults constants |
No |
Low |
🟡 Medium — improves DX |
| 4 |
Zod schema generation (--validation zod) |
No |
Medium |
🟠 High for form-heavy consumers |
Gap 1 is the highest-value fix: it catches missing required fields at compile time
rather than at runtime when Strapi returns a 400 Bad Request. Gaps 2–4 are fully
additive and can ship independently.
Additional context
This gap was discovered while using the generated client with a React Native / Expo app
and a Next.js portal that both use react-hook-form + Zod for form validation. Every
content-type had to maintain a parallel hand-written Zod schema to enforce the same
constraints that the Strapi schema already defines — creating two sources of truth that
drift over time whenever the schema changes.
The attached types file (types.ts) and schema (reservation.json) are from a real
production Strapi v5 instance and were used as the basis for the examples above.
[Feature Request] Surface Strapi schema constraints in generated types and validation artifacts
Summary
The generator correctly captures enumerations as TypeScript union types, but silently drops all
other constraint metadata present in the Strapi schema:
required,min/max,default,unique, etc.This forces consumers to either re-declare these constraints manually or ship without them
entirely — defeating the purpose of a typed, schema-driven client.
There are four distinct gaps, ordered by impact and implementation effort.
Gap 1 — Required fields are not reflected in Input types
Type: Bug / Type correctness
Strapi schemas declare
required: trueon individual fields. The generator currently emitsevery input field as optional and nullable regardless of that flag.
Schema example (
reservation.json)Currently generated
Expected output
Why this matters
The generator already does this correctly for the read type —
Reservation.dateFromisstring, notstring | null, because the schema marks it required. The Input type shouldmirror this symmetry. Currently a developer can write:
This is a pure TypeScript change — no runtime artifacts needed. The
requiredflag mapsdirectly to removing
?and| nullfrom the property in the Input interface.Gap 2 — Numeric
min/maxanddefaultvalues are silently droppedType: Enhancement — documentation / tooling
Strapi integer, decimal, and float fields support
min,max, anddefault. These cannotbe expressed in TypeScript's type system natively, but they can be surfaced as
JSDoc annotations at zero runtime cost.
Schema example
Currently generated
Expected output (with JSDoc)
Why this matters
JSDoc tags like
@minimum,@maximum, and@defaultare understood by:ts-to-zod— automatically generates.min()/.max()Zod refinements from JSDoctypescript-json-schema— produces JSON Schema with numeric constraintsThis surfaces constraints to the developer at the call site rather than requiring them to
cross-reference the Strapi admin panel or raw schema files.
Gap 3 — Default values not exported as runtime constants
Type: Enhancement — additive export
Strapi schema
defaultvalues are useful for initialising form state and UI componentsclient-side. Currently they are not accessible without reading the raw schema JSON.
Proposed addition
A generated
*Defaultsconstant per collection, exported alongside the existing types:The
satisfies Partial<ReservationInput>constraint ensures the defaults object staysin sync with the Input type — a rename or removal will produce a compile error.
Developer experience
Gap 4 — No runtime validation schema (Zod / JSON Schema) generated
Type: Enhancement — optional output artifact
JSDoc covers documentation but not runtime validation. Consumers working with forms,
API gateways, or server actions currently have to write Zod schemas by hand that duplicate
what the Strapi schema already defines.
Proposed addition
An optional
--validation zodCLI flag (or a separate generated file) that emits a Zodschema per collection derived directly from the Strapi schema:
Strapi schema → Zod mapping table
required: true.nonoptional()(no?)type: "integer"z.number().int()type: "float"/"decimal"z.number()type: "string"/"text"/"uid"z.string()type: "boolean"z.boolean()type: "date"/"datetime"z.string()(ISO 8601)type: "enumeration"z.enum([...values])min: N.min(N)max: N.max(N)default: value.default(value)type: "relation"RelationInputSchema(shared helper)type: "media"(single)z.union([z.string(), z.number()])type: "media"(multiple)z.array(z.union([z.string(), z.number()]))Why this matters
react-hook-formresolver (zodResolver(ReservationInputSchema))application code
Implementation priority
@minimum/@maximum/@default*Defaultsconstants--validation zod)Gap 1 is the highest-value fix: it catches missing required fields at compile time
rather than at runtime when Strapi returns a
400 Bad Request. Gaps 2–4 are fullyadditive and can ship independently.
Additional context
This gap was discovered while using the generated client with a React Native / Expo app
and a Next.js portal that both use
react-hook-form+ Zod for form validation. Everycontent-type had to maintain a parallel hand-written Zod schema to enforce the same
constraints that the Strapi schema already defines — creating two sources of truth that
drift over time whenever the schema changes.
The attached types file (
types.ts) and schema (reservation.json) are from a realproduction Strapi v5 instance and were used as the basis for the examples above.