--- title: Make your OpenAPI spec appear differently to Stainless with transforms | Stainless description: Use spec transforms to modify auto-generated specs or customize your OpenAPI without modifying the source --- Stainless transforms make your OpenAPI appear differently to Stainless without requiring you to modify the spec itself. You define transforms in your `stainless.yaml` config file, and Stainless applies your transforms automatically every time we generate your code, docs, and other targets. 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 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: stainless.yaml ``` openapi: transforms: - 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 ``` openapi: transforms: # 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 Use `append` for temporary fixes. Fails if the field exists, which alerts you when the spec is fixed. ``` openapi: transforms: - 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 ``` openapi: transforms: # 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 Use `merge` for permanent additions like Stainless extensions: ``` openapi: transforms: # 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 ``` openapi: transforms: # 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 Use `{{value}}` templating to modify existing strings: ``` openapi: transforms: - command: update reason: "Mark deprecated endpoint" args: target: "$.paths./v1/users.get.summary" value: "{{value}}\n\n**DEPRECATED**: Use /v2/users" template: true ``` ## Deduplicate schemas Use `matches_schema` to find all schemas that match a template and replace them with a `$ref`. ``` openapi: transforms: - 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 Transforms run in order, so later transforms see the effects of earlier ones. Extract an inline schema to components. ``` openapi: transforms: # 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 Transforms use [JSONPath Plus](https://www.npmjs.com/package/jsonpath-plus) for targeting. Common patterns: ``` # Exact path target: "$.components.schemas.User.properties.id" # Wildcard (all schemas) target: "$.components.schemas.*" # Recursive (all parameters anywhere) target: "$..parameters" # Filter expressions target: '$.paths..parameters[?(@.name == "account_id")]' target: '$.components.schemas[?(@.type == "object")]' # Multiple targets target: - "$.components.schemas.User" - "$.components.schemas.Admin" ``` ## Tips - Use `append` for temporary fixes (fails when the spec includes the field) - Use `merge` for 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 **“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 For complete documentation of all commands and options, see the [OpenAPI transforms reference](/docs/openapi/transforms/reference/index.md).