> ## Documentation Index
> Fetch the complete documentation index at: https://docs.core-ai.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Structured Outputs

> Generate type-safe JSON with generateObject() and streamObject() using Zod schemas

core-ai provides strongly-typed structured outputs using Zod schemas, ensuring your responses match expected formats with full TypeScript type inference.

## Generate Structured Objects

Use `generateObject()` to get validated JSON responses:

```typescript theme={null}
import { generateObject } from '@core-ai/core-ai';
import { createOpenAI } from '@core-ai/openai';
import { z } from 'zod';

const openai = createOpenAI({ apiKey: process.env.OPENAI_API_KEY });
const model = openai.chatModel('gpt-5-mini');

const weatherSchema = z.object({
  city: z.string(),
  temperatureC: z.number(),
  summary: z.string(),
});

const result = await generateObject({
  model,
  messages: [
    {
      role: 'user',
      content: 'Return a weather report for Berlin as structured JSON.',
    },
  ],
  schema: weatherSchema,
  schemaName: 'weather_report',
  schemaDescription: 'A structured weather report object.',
});

// result.object is fully typed!
console.log('City:', result.object.city);
console.log('Temperature:', result.object.temperatureC);
console.log('Summary:', result.object.summary);
```

## Stream Structured Objects

Stream JSON as it's being generated:

```typescript theme={null}
import { streamObject } from '@core-ai/core-ai';
import { z } from 'zod';

const extractSchema = z.object({
  headline: z.string(),
  sentiment: z.enum(['positive', 'neutral', 'negative']),
  tags: z.array(z.string()),
});

const result = await streamObject({
  model,
  messages: [
    {
      role: 'user',
      content: 'Analyze this sentence and return JSON only: "core-ai makes provider integration easier."',
    },
  ],
  schema: extractSchema,
  schemaName: 'text_analysis',
  schemaDescription: 'Structured text analysis output.',
});

for await (const event of result) {
  if (event.type === 'object-delta') {
    process.stdout.write(event.text);
    continue;
  }

  if (event.type === 'object') {
    console.log('\n\nValidated object:', event.object);
  }
}

const response = await result.result;
console.log('\nFinal object:', response.object);
console.log('Finish reason:', response.finishReason);
```

## Object stream events

The `streamObject()` function returns an `ObjectStream` -- an async iterable with `.result` and `.events`. It emits these event types:

```typescript theme={null}
type ObjectStreamEvent<TSchema extends z.ZodType> =
  | { type: 'object-delta'; text: string }      // Raw JSON text chunks
  | { type: 'object'; object: z.infer<TSchema> } // Final validated object
  | { type: 'finish'; finishReason: FinishReason; usage: ChatUsage };
```

<Note>
  The `object` event is emitted once, when the full object payload has been validated. Use `object-delta` events for progressive UI updates while JSON is still streaming.
</Note>

## Complex Schema Examples

<Tabs>
  <Tab title="Nested Objects">
    ```typescript theme={null}
    import { generateObject } from '@core-ai/core-ai';
    import { z } from 'zod';

    const userProfileSchema = z.object({
      name: z.string(),
      age: z.number(),
      address: z.object({
        street: z.string(),
        city: z.string(),
        country: z.string(),
      }),
      skills: z.array(z.string()),
      metadata: z.record(z.string(), z.unknown()),
    });

    const result = await generateObject({
      model,
      messages: [{
        role: 'user',
        content: 'Create a sample user profile for a software engineer in Berlin.',
      }],
      schema: userProfileSchema,
    });

    console.log(result.object.name);
    console.log(result.object.address.city);
    console.log(result.object.skills);
    ```
  </Tab>

  <Tab title="Arrays">
    ```typescript theme={null}
    const taskListSchema = z.object({
      tasks: z.array(
        z.object({
          id: z.number(),
          title: z.string(),
          priority: z.enum(['low', 'medium', 'high']),
          completed: z.boolean(),
          tags: z.array(z.string()),
        })
      ),
      totalCount: z.number(),
    });

    const result = await generateObject({
      model,
      messages: [{
        role: 'user',
        content: 'Generate 5 sample tasks for a project management app.',
      }],
      schema: taskListSchema,
    });

    result.object.tasks.forEach((task) => {
      console.log(`${task.id}. ${task.title} [${task.priority}]`);
    });
    ```
  </Tab>

  <Tab title="Unions and Discriminated Types">
    ```typescript theme={null}
    const notificationSchema = z.discriminatedUnion('type', [
      z.object({
        type: z.literal('email'),
        to: z.string().email(),
        subject: z.string(),
        body: z.string(),
      }),
      z.object({
        type: z.literal('sms'),
        phoneNumber: z.string(),
        message: z.string(),
      }),
      z.object({
        type: z.literal('push'),
        deviceId: z.string(),
        title: z.string(),
        body: z.string(),
      }),
    ]);

    const result = await generateObject({
      model,
      messages: [{
        role: 'user',
        content: 'Create an email notification about a password reset.',
      }],
      schema: notificationSchema,
    });

    if (result.object.type === 'email') {
      console.log('Email to:', result.object.to);
      console.log('Subject:', result.object.subject);
    }
    ```
  </Tab>

  <Tab title="Optional and Default Values">
    ```typescript theme={null}
    const configSchema = z.object({
      name: z.string(),
      enabled: z.boolean().default(true),
      timeout: z.number().optional(),
      settings: z.object({
        theme: z.enum(['light', 'dark']).default('light'),
        language: z.string().default('en'),
      }).optional(),
    });

    const result = await generateObject({
      model,
      messages: [{
        role: 'user',
        content: 'Create a configuration for a web application.',
      }],
      schema: configSchema,
    });

    console.log(result.object);
    ```
  </Tab>
</Tabs>

## Schema Descriptions

Add descriptions to help the model understand your schema:

```typescript theme={null}
const productSchema = z.object({
  name: z.string().describe('The product name'),
  price: z.number().describe('Price in USD'),
  category: z.enum(['electronics', 'clothing', 'food'])
    .describe('Product category'),
  inStock: z.boolean().describe('Whether the product is currently available'),
  tags: z.array(z.string()).describe('Relevant tags for searching'),
});

const result = await generateObject({
  model,
  messages: [{
    role: 'user',
    content: 'Create a product listing for a wireless keyboard.',
  }],
  schema: productSchema,
  schemaName: 'product',
  schemaDescription: 'A product listing with name, price, and metadata.',
});
```

## Type Inference

TypeScript automatically infers types from Zod schemas:

```typescript theme={null}
const userSchema = z.object({
  id: z.number(),
  name: z.string(),
  email: z.string().email(),
  role: z.enum(['admin', 'user', 'guest']),
});

// Type is automatically inferred!
type User = z.infer<typeof userSchema>;
// { id: number; name: string; email: string; role: 'admin' | 'user' | 'guest' }

const result = await generateObject({
  model,
  messages: [{ role: 'user', content: 'Create a user' }],
  schema: userSchema,
});

// result.object is typed as User
const user: User = result.object;
```

## Error Handling

Handle validation and parsing errors:

```typescript theme={null}
import {
  StructuredOutputError,
  StructuredOutputValidationError,
  StructuredOutputParseError,
} from '@core-ai/core-ai';

try {
  const result = await generateObject({
    model,
    messages,
    schema: mySchema,
  });

  console.log(result.object);
} catch (error) {
  if (error instanceof StructuredOutputValidationError) {
    console.error('Validation failed:', error.message);
    console.error('Validation errors:', error.issues);
  } else if (error instanceof StructuredOutputParseError) {
    console.error('Failed to parse JSON:', error.message);
    console.error('Raw output:', error.rawOutput);
  } else if (error instanceof StructuredOutputError) {
    console.error('Structured output error:', error.message);
  }
}
```

## Streaming with UI Updates

Update your UI as the object is built:

```typescript theme={null}
import { streamObject } from '@core-ai/core-ai';
import { z } from 'zod';

const recipeSchema = z.object({
  title: z.string(),
  ingredients: z.array(z.string()),
  instructions: z.array(z.string()),
  prepTime: z.number(),
  cookTime: z.number(),
});

const result = await streamObject({
  model,
  messages: [{ role: 'user', content: 'Give me a pasta recipe' }],
  schema: recipeSchema,
});

for await (const event of result) {
  if (event.type === 'object-delta') {
    updateRecipePreview(event.text);
    continue;
  }

  if (event.type === 'object') {
    // event.object contains the final validated object
    updateRecipeUI(event.object);
    console.log('Title:', event.object.title);
    console.log('Ingredients:', event.object.ingredients.length);
  }
}

const response = await result.result;
console.log('Complete recipe:', response.object);
```

## Configuration Options

Customize structured output generation:

```typescript theme={null}
const result = await generateObject({
  model,
  messages,
  schema: mySchema,
  schemaName: 'my_object',
  schemaDescription: 'A description',
  temperature: 0.3,
  maxTokens: 2000,
});
```

## Best Practices

<AccordionGroup>
  <Accordion title="Use descriptive schema names and descriptions">
    Help the model understand what you want:

    ```typescript theme={null}
    const result = await generateObject({
      model,
      messages,
      schema: z.object({
        name: z.string().describe('Full name of the person'),
        age: z.number().describe('Age in years'),
      }),
      schemaName: 'person',
      schemaDescription: 'A person with their basic information',
    });
    ```
  </Accordion>

  <Accordion title="Use lower temperature for consistent structure">
    Reduce temperature for more predictable JSON structure:

    ```typescript theme={null}
    const result = await generateObject({
      model,
      messages,
      schema: mySchema,
      temperature: 0.3,
    });
    ```
  </Accordion>

  <Accordion title="Use `object-delta` for progressive updates">
    Build previews from raw JSON chunks, then switch to the validated object when it arrives:

    ```typescript theme={null}
    for await (const event of result) {
      if (event.type === 'object-delta') {
        appendJsonPreview(event.text);
      }

      if (event.type === 'object') {
        renderFinalObject(event.object);
      }
    }
    ```
  </Accordion>

  <Accordion title="Validate complex constraints">
    Add custom validation after generation:

    ```typescript theme={null}
    const result = await generateObject({
      model,
      messages,
      schema: mySchema,
    });

    // Additional business logic validation
    if (result.object.startDate > result.object.endDate) {
      throw new Error('Start date must be before end date');
    }
    ```
  </Accordion>
</AccordionGroup>

## Next Steps

<CardGroup cols={2}>
  <Card title="Chat Completion" icon="message" href="/guides/chat-completion">
    Generate text responses without structure
  </Card>

  <Card title="Tool Calling" icon="wrench" href="/guides/tool-calling">
    Combine structured outputs with tool calls
  </Card>

  <Card title="Streaming" icon="water" href="/guides/streaming">
    Stream regular text responses
  </Card>

  <Card title="Zod Documentation" icon="link" href="https://zod.dev">
    Learn more about Zod schemas
  </Card>
</CardGroup>
