skills/davila7/claude-code-templates/openapi-to-typescript

# openapi-to-typescript

SKILL.md

# OpenAPI to TypeScript

Converts OpenAPI 3.0 specifications to TypeScript interfaces and type guards.

Input: OpenAPI file (JSON or YAML) Output: TypeScript file with interfaces and type guards

## When to Use

- "generate types from openapi"
- "convert openapi to typescript"
- "create API interfaces"
- "generate types from spec"

## Workflow

1. Request the OpenAPI file path (if not provided)
2. Read and validate the file (must be OpenAPI 3.0.x)
3. Extract schemas from `components/schemas`
4. Extract endpoints from `paths` (request/response types)
5. Generate TypeScript (interfaces + type guards)
6. Ask where to save (default: `types/api.ts` in current directory)
7. Write the file

## OpenAPI Validation

Check before processing:

    - Field "openapi" must exist and start with "3.0"
    - Field "paths" must exist
    - Field "components.schemas" must exist (if there are types)

If invalid, report the error and stop.

## Type Mapping

### Primitives

OpenAPI | TypeScript
--- | ---
`string` | `string`
`number` | `number`
`integer` | `number`
`boolean` | `boolean`
`null` | `null`

### Format Modifiers

Format | TypeScript
--- | ---
`uuid` | `string` (comment UUID)
`date` | `string` (comment date)
`date-time` | `string` (comment ISO)
`email` | `string` (comment email)
`uri` | `string` (comment URI)

### Complex Types

Object:

    // OpenAPI: type: object, properties: {id, name}, required: [id]
    interface Example {
      id: string;      // required: no ?
      name?: string;   // optional: with ?
    }

Array:

    // OpenAPI: type: array, items: {type: string}
    type Names = string[];

Enum:

    // OpenAPI: type: string, enum: [active, draft]
    type Status = "active" | "draft";

oneOf (Union):

    // OpenAPI: oneOf: [{$ref: Cat}, {$ref: Dog}]
    type Pet = Cat | Dog;

allOf (Intersection/Extends):

    // OpenAPI: allOf: [{$ref: Base}, {type: object, properties: ...}]
    interface Extended extends Base {
      extraField: string;
    }

## Code Generation

### File Header

    /**
     * Auto-generated from: {source_file}
     * Generated at: {timestamp}
     *
     * DO NOT EDIT MANUALLY - Regenerate from OpenAPI schema
     */

### Interfaces (from components/schemas)

For each schema in `components/schemas`:

    export interface Product {
      /** Product unique identifier */
      id: string;

      /** Product title */
      title: string;

      /** Product price */
      price: number;

      /** Created timestamp */
      created_at?: string;
    }

- Use OpenAPI description as JSDoc
- Fields in `required[]` have no `?`
- Fields outside `required[]` have `?`

### Request/Response Types (from paths)

For each endpoint in `paths`:

    // GET /products - query params
    export interface GetProductsRequest {
      page?: number;
      limit?: number;
    }

    // GET /products - response 200
    export type GetProductsResponse = ProductList;

    // POST /products - request body
    export interface CreateProductRequest {
      title: string;
      price: number;
    }

    // POST /products - response 201
    export type CreateProductResponse = Product;

Naming convention:

- `{Method}{Path}Request` for params/body
- `{Method}{Path}Response` for response

### Type Guards

For each main interface, generate a type guard:

    export function isProduct(value: unknown): value is Product {
      return (
        typeof value === 'object' &&
        value !== null &&
        'id' in value &&
        typeof (value as any).id === 'string' &&
        'title' in value &&
        typeof (value as any).title === 'string' &&
        'price' in value &&
        typeof (value as any).price === 'number'
      );
    }

Type guard rules:

- Check `typeof value === 'object' && value !== null`
- For each required field: check `'field' in value`
- For primitive fields: check `typeof`
- For arrays: check `Array.isArray()`
- For enums: check `.includes()`

### Error Type (always include)

    export interface ApiError {
      status: number;
      error: string;
      detail?: string;
    }

    export function isApiError(value: unknown): value is ApiError {
      return (
        typeof value === 'object' &&
        value !== null &&
        'status' in value &&
        typeof (value as any).status === 'number' &&
        'error' in value &&
        typeof (value as any).error === 'string'
      );
    }

## $ref Resolution

When encountering `{"$ref": "#/components/schemas/Product"}`:

1. Extract the schema name (`Product`)
2. Use the type directly (don't resolve inline)

    // OpenAPI: items: {$ref: "#/components/schemas/Product"}
    // TypeScript:
    items: Product[]  // reference, not inline

## Complete Example

Input (OpenAPI):

    {
      "openapi": "3.0.0",
      "components": {
        "schemas": {
          "User": {
            "type": "object",
            "properties": {
              "id": {"type": "string", "format": "uuid"},
              "email": {"type": "string", "format": "email"},
              "role": {"type": "string", "enum": ["admin", "user"]}
            },
            "required": ["id", "email", "role"]
          }
        }
      },
      "paths": {
        "/users/{id}": {
          "get": {
            "parameters": [{"name": "id", "in": "path", "required": true}],
            "responses": {
              "200": {
                "content": {
                  "application/json": {
                    "schema": {"$ref": "#/components/schemas/User"}
                  }
                }
              }
            }
          }
        }
      }
    }

Output (TypeScript):

    /**
     * Auto-generated from: api.openapi.json
     * Generated at: 2025-01-15T10:30:00Z
     *
     * DO NOT EDIT MANUALLY - Regenerate from OpenAPI schema
     */

    // ============================================================================
    // Types
    // ============================================================================

    export type UserRole = "admin" | "user";

    export interface User {
      /** UUID */
      id: string;

      /** Email */
      email: string;

      role: UserRole;
    }

    // ============================================================================
    // Request/Response Types
    // ============================================================================

    export interface GetUserByIdRequest {
      id: string;
    }

    export type GetUserByIdResponse = User;

    // ============================================================================
    // Type Guards
    // ============================================================================

    export function isUser(value: unknown): value is User {
      return (
        typeof value === 'object' &&
        value !== null &&
        'id' in value &&
        typeof (value as any).id === 'string' &&
        'email' in value &&
        typeof (value as any).email === 'string' &&
        'role' in value &&
        ['admin', 'user'].includes((value as any).role)
      );
    }

    // ============================================================================
    // Error Types
    // ============================================================================

    export interface ApiError {
      status: number;
      error: string;
      detail?: string;
    }

    export function isApiError(value: unknown): value is ApiError {
      return (
        typeof value === 'object' &&
        value !== null &&
        'status' in value &&
        typeof (value as any).status === 'number' &&
        'error' in value &&
        typeof (value as any).error === 'string'
      );
    }

## Common Errors

Error | Action
--- | ---
OpenAPI version != 3.0.x | Report that only 3.0 is supported
$ref not found | List missing refs
Unknown type | Use `unknown` and warn
Circular reference | Use type alias with lazy reference

Weekly Installs
79
Repository
davila7/claude-code-templates
First Seen
Jan 25, 2026
Security Audits
Gen Agent Trust Hub Pass
Socket Pass
Snyk Pass
Installed on
claude-code 63
opencode 55
cursor 53
antigravity 52
gemini-cli 50
codex 40