Fix your OpenAPI spec with transforms
Use spec transforms to fix auto-generated specs or customize your OpenAPI without modifying the source
Spec transforms modify your OpenAPI spec during SDK generation without editing the source file. Define transforms in your stainless.yaml config file and Stainless applies them automatically every time it generates your SDKs.
Common uses for transforms:
- Fix incorrect types in auto-generated specs
- Add missing required fields while you fix the source spec
- Rename properties to avoid language conflicts
- Add Stainless metadata (x-stainless-naming, x-stainless-skip)
- Remove incorrect defaults or deprecated fields
Getting started
Section titled “Getting started”Suppose your API auto-generates a spec where account IDs are typed as number, but they’re actually strings. Add a transform to fix it:
openapi: transformations: - command: update reason: "Account IDs are strings, not numbers" args: target: '$.paths..parameters[?(@.name == "account_id")].schema.type' value: "string"The target uses JSONPath to find all parameters named account_id. The transform replaces their type with "string". The reason documents why this transform exists.
Fix incorrect types
Section titled “Fix incorrect types”openapi: transformations: # Fix specific property - command: update reason: "User age should be integer" args: target: "$.components.schemas.User.properties.age.type" value: "integer"
# Fix all instances across endpoints - command: update reason: "All limit parameters should be int32" args: target: '$.paths..parameters[?(@.name == "limit")].schema' value: type: "integer" format: "int32"Add missing fields
Section titled “Add missing fields”Use append for temporary fixes. Fails if the field exists, which alerts you when the spec is fixed.
openapi: transformations: - command: append reason: "Spec missing id field (TODO: fix in source)" args: target: "$.components.schemas.User.properties" value: id: type: "string" format: "uuid"
- command: append reason: "Mark id as required" args: target: "$.components.schemas.User.required" value: "id"When the spec includes the id field, your transform will fail with “Properties already exist”, prompting you to remove it.
Rename properties
Section titled “Rename properties”openapi: transformations: # Rename schema - command: move reason: "Normalize to PascalCase" args: from: "$.components.schemas.user_response" to: "$.components.schemas.UserResponse"
# Rename property - command: move reason: "Fix snake_case to camelCase" args: from: "$.components.schemas.User.properties.first_name" to: "$.components.schemas.User.properties.firstName"Add Stainless metadata
Section titled “Add Stainless metadata”Use merge for permanent additions like Stainless extensions:
openapi: transformations: # Language-specific naming - command: merge reason: "Avoid shadowing Python's timeout builtin" args: target: "$.components.schemas.Request.properties.timeout" value: x-stainless-naming: python: method_argument: "api_timeout"
# Skip parameters for specific SDKs - command: merge reason: "Terraform doesn't need pagination" args: target: '$.paths..parameters[?(@.name == "limit" || @.name == "offset")]' value: schema: x-stainless-skip: ["terraform"]Remove problematic fields
Section titled “Remove problematic fields”openapi: transformations: # Remove specific keys - command: remove reason: "Remove incorrect default" args: target: "$.components.schemas.User.properties.middleName" keys: ["default"]
# Remove entire nodes - command: remove reason: "Remove all deprecated parameters" args: target: '$.paths..parameters[?(@.deprecated == true)]'
# Remove examples - command: remove reason: "Remove auto-generated examples" args: target: - "$.paths.*.*.examples" - "$.components.schemas.*.example"Append text to strings
Section titled “Append text to strings”Use {{value}} templating to modify existing strings:
openapi: transformations: - command: update reason: "Mark deprecated endpoint" args: target: "$.paths./v1/users.get.summary" value: "{{value}}\n\n**DEPRECATED**: Use /v2/users" template: trueDeduplicate schemas
Section titled “Deduplicate schemas”Use matches_schema to find all schemas that match a template and replace them with a $ref.
openapi: transformations: - command: update reason: "Replace duplicate error schemas with $ref" args: target: - matches_schema: $.components.schemas.ErrorTemplate ignore: $.components.schemas.ErrorTemplate value: $ref: "#/components/schemas/Error"This finds all schemas that exactly match ErrorTemplate and replaces them with a reference. The ignore field prevents replacing the template itself.
Chain transforms
Section titled “Chain transforms”Transforms run in order, so later transforms see the effects of earlier ones.
Extract an inline schema to components.
openapi: transformations: # Copy inline schema to components - command: copy reason: "Extract error schema for reuse" args: from: "$.paths./users.get.responses.404.content.application/json.schema" to: "$.components.schemas.NotFoundError"
# Replace inline schema with $ref - command: update reason: "Use $ref instead of inline" args: target: "$.paths./users.get.responses.404.content.application/json.schema" value: $ref: "#/components/schemas/NotFoundError"JSONPath queries
Section titled “JSONPath queries”Transforms use JSONPath Plus for targeting. Common patterns:
# Exact pathtarget: "$.components.schemas.User.properties.id"
# Wildcard (all schemas)target: "$.components.schemas.*"
# Recursive (all parameters anywhere)target: "$..parameters"
# Filter expressionstarget: '$.paths..parameters[?(@.name == "account_id")]'target: '$.components.schemas[?(@.type == "object")]'
# Multiple targetstarget: - "$.components.schemas.User" - "$.components.schemas.Admin"- Use
appendfor temporary fixes (fails when the spec includes the field) - Use
mergefor permanent overrides such as x-stainless metadata - Document your reasons - future you will thank you
- Test one transform at a time
- Prefer explicit paths over broad wildcards when possible
- Keep transforms focused - one transform should do one thing
Troubleshooting
Section titled “Troubleshooting”“JSONPath did not match any nodes”
Your query didn’t find anything. Check for typos, verify the path exists, or simplify your query to test.
“Properties already exist”
You’re using append on a property that exists. Either remove the transform (if the spec was fixed) or switch to merge if you want to overwrite.
Transform does nothing
Check the order. If one transform renames a field and another targets the old name, the second won’t find anything. Adjust the order or update the target path.
Available commands
Section titled “Available commands”For complete documentation of all commands and options, see the Transforms Reference.