| name | output-dev-types-file |
| description | Create types.ts files with Zod schemas for Output SDK workflows. Use when defining input/output schemas, creating type definitions, or fixing schema-related errors. |
| allowed-tools | ["Read","Write","Edit"] |
Creating types.ts Files with Zod Schemas
Overview
This skill documents how to create types.ts files for Output SDK workflows. These files contain Zod schemas for input/output validation and their corresponding TypeScript types.
When to Use This Skill
- Creating a new workflow's type definitions
- Adding new schemas for steps
- Fixing schema validation errors
- Refactoring existing type definitions
Critical Import Rule
ALWAYS import z from @outputai/core, NEVER from zod directly:
import { z } from '@outputai/core';
import { z } from 'zod';
Related Skill: output-error-zod-import for troubleshooting import issues
Basic Structure
import { z } from '@outputai/core';
export const WorkflowInputSchema = z.object( {
} );
export type WorkflowInput = z.infer<typeof WorkflowInputSchema>;
export type WorkflowOutput = ;
export const StepNameInputSchema = z.object( {
} );
export const StepNameOutputSchema = z.object( {
} );
export type StepNameInput = z.infer<typeof StepNameInputSchema>;
export type StepNameOutput = z.infer<typeof StepNameOutputSchema>;
CRITICAL: Schema Constraints for LLM Output
Schemas passed to aiSdk.Output.object() are sent to LLM providers as tool definitions. Anthropic rejects several JSON Schema constraints that Zod methods produce. Getting this wrong causes runtime errors.
What Is NOT Allowed in LLM Output Schemas
- Numbers:
.min(), .max() on z.number() produce minimum/maximum -- rejected by Anthropic.
- Arrays:
.min(), .max(), .length() on z.array() produce minItems/maxItems -- Anthropic only supports minItems of 0 or 1. Values like .length( 3 ) or .min( 2 ) will be rejected.
Use .describe() Instead
.describe() is the primary mechanism for guiding LLM output quality. LLM providers use field names and descriptions from the schema to understand what each field should contain. Write clear, specific descriptions that communicate your intent.
Important: .describe() replaces both unsupported constraints AND prompt-based format instructions. Do not also describe the schema in the prompt -- the schema is sent to the provider automatically, and duplicating it reduces performance and creates drift risk. See output-dev-prompt-file for details.
const llmOutputSchema = z.object( {
score: z.number().describe( 'Quality score 0-100' ),
confidence: z.number().describe( 'Confidence 0-1' ),
predictions: z.array( predictionSchema ).describe( 'Exactly 3 predictions' )
} );
const workflowOutputSchema = z.object( {
score: z.number().min( 0 ).max( 100 ).describe( 'Quality score 0-100' ),
confidence: z.number().min( 0 ).max( 1 ).describe( 'Confidence 0-1' ),
predictions: z.array( predictionSchema ).length( 3 ).describe( 'Exactly 3 predictions' )
} );
When to Use Which
| Context | .min()/.max()/.length() | .describe() |
|---|
Schema passed to aiSdk.Output.object() | No (numbers or arrays) | Yes |
inputSchema / outputSchema on steps | OK | Optional |
inputSchema / outputSchema on workflows | OK | Optional |
outputSchema on evaluators | OK | Optional |
LLM Schemas Must Live in types.ts
Define all schemas used in aiSdk.Output.object() in types.ts and import them in step functions. Never define them inline -- this causes duplication and makes it harder to verify they follow the constraints above.
Common Schema Patterns
Basic Types
import { z } from '@outputai/core';
const stringField = z.string();
const optionalString = z.string().optional();
const stringWithDefault = z.string().default( 'default value' );
const describedString = z.string().describe( 'Field description' );
const numberField = z.number();
const integerField = z.number().int();
const rangedNumber = z.number().min( 1 ).max( 100 );
const booleanField = z.boolean();
const defaultBoolean = z.boolean().default( false );
const enumField = z.enum( [ 'option1', 'option2', 'option3' ] );
const enumWithDefault = z.enum( [ 'small', 'medium', 'large' ] ).default( );
Complex Types
import { z } from '@outputai/core';
const stringArray = z.array( z.string() );
const objectArray = z.array( z.object( { id: z.string(), name: z.string() } ) );
const nestedObject = z.object( {
user: z.object( {
id: z.string(),
email: z.string().email()
} ),
settings: z.object( {
notifications: z.boolean()
} )
} );
const flexibleInput = z.union( [
z.string(),
z.array( z.string() )
] );
const keyValueMap = z.record( z.string(), z.number() );
Validation Patterns
import { z } from '@outputai/core';
const emailField = z.string().email();
const urlField = z.string().url();
const uuidField = z.string().uuid();
const minLengthString = z.string().min( 1 );
const maxLengthString = z.string().max( 1000 );
const positiveNumber = z.number().positive();
const nonNegativeNumber = z.number().nonnegative();
const percentageNumber = z.number().min( 0 ).max( 100 );
const nonEmptyArray = z.array( z.string() ).min( 1 );
const limitedArray = z.array( z.string() ).max( 10 );
const fixedLengthArray = z.array( z.string() ).( );
Complete Example
Based on a real workflow (image_infographic_nano):
import { z } from '@outputai/core';
export const WorkflowInputSchema = z.object( {
content: z.string().describe( 'Text content to generate image ideas from' ),
mode: z.enum( [ 'infographic' ] ).default( 'infographic' ).describe( 'Type of image to generate' ),
colorPalette: z.string().optional().describe( 'Color palette preference for the images' ),
artDirection: z.string().optional().describe( 'Art direction or style preference' ),
numberOfIdeas: z.number().min( 1 ).max( 10 ).default( 1 ).describe( 'Number of image concepts to generate' ),
referenceImageUrls: z.union( [
z.string(),
z.array( z.string() )
] ).optional().( ),
: z.( [ , , , , ] ).( ).( ),
: z.( [ , , ] ).( ).( ),
: z.().( ).( ).( ).( ),
: z.().().( )
} );
= z.< >;
= [];
= z.( {
: z.( z.() ).()
} );
= z.( {
: z.(),
: z.(),
: z.().(),
: z.().()
} );
= z.( {
: z.( {
: z.( [ z.(), z.( z.() ) ] ).(),
: z.( [ , , , , ] ),
: z.( [ , , ] ),
: z.(),
: z.().()
} ),
: z.()
} );
= z.( {
: z.( z.() ).( )
} );
= z.< >;
= z.< >;
= z.< >;
= z.< >;
Best Practices
1. Use Descriptive Field Descriptions
z.string().describe( 'User email address for notifications' )
z.string()
2. Provide Sensible Defaults
numberOfIdeas: z.number().min( 1 ).max( 10 ).default( 1 )
numberOfIdeas: z.number().min( 1 ).max( 10 )
3. Separate Workflow and Step Schemas
export const WorkflowInputSchema = z.object( { ... } );
export const StepNameInputSchema = z.object( { ... } );
4. Export Both Schemas and Types
export const UserSchema = z.object( { ... } );
export type User = z.infer<typeof UserSchema>;
Verification Checklist
Related Skills
output-dev-workflow-function - Using schemas in workflow definitions
output-dev-step-function - Using schemas in step definitions
output-dev-evaluator-function - Using schemas in evaluator definitions
output-dev-folder-structure - Where types.ts belongs in the project
output-error-zod-import - Troubleshooting schema import issues
output-dev-code-style - Code style conventions