Claude-skill-registry create-schema

Create Zod schemas for new entities/resources in the backend. Use when adding a new resource, creating data models, defining DTOs (CreateType, UpdateType), or setting up validation schemas. Triggers on "new schema", "add entity", "create model", "define type".

install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/create-schema" ~/.claude/skills/majiayu000-claude-skill-registry-create-schema && rm -rf "$T"
manifest: skills/data/create-schema/SKILL.md
source content

Create Schema

Creates Zod schemas with TypeScript type inference for new entities in this backend template.

Quick Reference

Location:

src/schemas/{entity-name}.schema.ts
Naming: Singular, kebab-case (e.g.,
note.schema.ts
,
course-registration.schema.ts
)

Instructions

Step 1: Create the Schema File

Create a new file at

src/schemas/{entity-name}.schema.ts

Step 2: Define the Entity ID Schema

Always start with a dedicated ID schema:

import { z } from "zod";

export const {entity}IdSchema = z.string();
export type {Entity}IdType = z.infer<typeof {entity}IdSchema>;

Step 3: Define the Base Entity Schema

export const {entity}Schema = z.object({
  id: {entity}IdSchema,                    // Primary key
  // ... entity-specific fields ...
  createdBy: userIdSchema,                 // If entity is user-owned (import from user.schemas)
  createdAt: z.date().optional(),          // Set by DB/service
  updatedAt: z.date().optional(),          // Set by DB/service
});

export type {Entity}Type = z.infer<typeof {entity}Schema>;

Step 4: Define Create DTO Schema

Omit system-managed fields and add validation:

export const create{Entity}Schema = {entity}Schema
  .omit({
    id: true,           // Generated by service/system
    createdBy: true,    // Set from authenticated user context
    createdAt: true,    // Set by service/database
    updatedAt: true,    // Set by service/database
  })
  .extend({
    // Add stricter validation for required fields
    fieldName: z.string().min(1, "{Entity} field is required for creation."),
  });
export type Create{Entity}Type = z.infer<typeof create{Entity}Schema>;

Step 5: Define Update DTO Schema

Make all mutable fields optional:

export const update{Entity}Schema = {entity}Schema
  .omit({
    id: true,           // Part of URL, not body
    createdBy: true,    // Immutable
    createdAt: true,    // Immutable
    updatedAt: true,    // Set by service/database
  })
  .partial();           // All fields optional for updates
export type Update{Entity}Type = z.infer<typeof update{Entity}Schema>;

Step 6: Define Query Parameters Schema (if needed)

Extend the base query params for filtering:

import { queryParamsSchema } from "./shared.schema";

export const {entity}QueryParamsSchema = queryParamsSchema.extend({
  // Entity-specific filters
  createdBy: userIdSchema.optional(),
  status: z.enum(["active", "inactive"]).optional(),
});
export type {Entity}QueryParamsType = z.infer<typeof {entity}QueryParamsSchema>;

Patterns & Rules

Naming Conventions

  • File name:
    {entity-name}.schema.ts
    (singular, kebab-case)
  • Schema variables:
    {entity}Schema
    ,
    create{Entity}Schema
    (camelCase)
  • Type names:
    {Entity}Type
    ,
    Create{Entity}Type
    (PascalCase)

Import Rules

  • Always use path aliases:
    import { x } from "@/schemas/..."
  • Import shared schemas from
    ./shared.schema
  • Import user-related schemas from
    ./user.schemas
    when needed

Schema Design Rules

  1. Always export both schema and type for each definition
  2. ID schemas are separate - allows reuse and type narrowing
  3. Timestamps are optional on the base schema (not present on creation)
  4. Create schemas omit system-managed fields (id, createdBy, timestamps)
  5. Update schemas are partial - all fields optional
  6. Extend base queryParamsSchema for entity-specific filters

Validation Guidelines

  • Add
    .min(1)
    for required string fields in create schemas
  • Use
    .optional()
    for truly optional fields
  • Use
    z.coerce.number()
    for numeric query params (they come as strings)
  • Add descriptive error messages:
    z.string().min(1, "Field is required")

Cross-Reference Pattern

When referencing other entities:

import { userIdSchema } from "./user.schemas";
import { otherEntityIdSchema } from "./other-entity.schema";

export const myEntitySchema = z.object({
  id: myEntityIdSchema,
  ownerId: userIdSchema, // Reference to user
  relatedId: otherEntityIdSchema, // Reference to another entity
});

Complete Example

See

src/schemas/note.schema.ts
for a complete reference implementation.

import { z } from "zod";
import { queryParamsSchema } from "./shared.schema";
import { userIdSchema } from "./user.schemas";

// ID Schema
export const noteIdSchema = z.string();
export type NoteIdType = z.infer<typeof noteIdSchema>;

// Base Entity Schema
export const noteSchema = z.object({
  id: noteIdSchema,
  content: z.string(),
  createdBy: userIdSchema,
  createdAt: z.date().optional(),
  updatedAt: z.date().optional(),
});
export type NoteType = z.infer<typeof noteSchema>;

// Create DTO
export const createNoteSchema = noteSchema
  .omit({
    id: true,
    createdBy: true,
    createdAt: true,
    updatedAt: true,
  })
  .extend({
    content: z.string().min(1, "Note content is required for creation."),
  });
export type CreateNoteType = z.infer<typeof createNoteSchema>;

// Update DTO
export const updateNoteSchema = noteSchema
  .omit({
    id: true,
    createdBy: true,
    createdAt: true,
    updatedAt: true,
  })
  .partial();
export type UpdateNoteType = z.infer<typeof updateNoteSchema>;

// Query Parameters
export const noteQueryParamsSchema = queryParamsSchema.extend({
  createdBy: userIdSchema.optional(),
});
export type NoteQueryParamsType = z.infer<typeof noteQueryParamsSchema>;

Shared Schemas Reference

The following are available from

@/schemas/shared.schema
:

  • queryParamsSchema
    - Base pagination/sorting params (search, sortBy, sortOrder, page, limit)
  • paginatedResultsSchema(dataSchema)
    - Generic paginated response wrapper
  • entityIdParamSchema(paramName)
    - URL path parameter validation
  • DEFAULT_PAGE
    ,
    DEFAULT_LIMIT
    - Pagination defaults

What NOT to Do

  • Do NOT use
    process.env
    - environment config belongs in
    src/env.ts
  • Do NOT create barrel exports (index.ts) - use explicit imports
  • Do NOT use plural names (
    notes.schema.ts
    ) - use singular (
    note.schema.ts
    )
  • Do NOT define business logic in schemas - schemas are for structure/validation only
  • Do NOT skip type exports - always export both schema and inferred type