Configuring breaking changes
Customize breaking changes in any way we want.Not every change that could be breaking is breaking for your own API.
openapi-changes lets us customize which changes are flagged as breaking, so we can tailor the rules
to match our API’s versioning strategy and consumer expectations.
This feature is available from
v0.0.91
We have dropped all support for Swagger moving forward. It’s a commercial tool that uses a different standard to OpenAPI.
This only works with OpenAPI Documents.
How it works
By default, openapi-changes uses libopenapi’s built-in breaking change rules.
These rules are sensible defaults that work for most APIs, but sometimes we need more control.
We can override any rule by creating a configuration file called changes-rules.yaml (this is the default config file name).
Using a config file
Explicit config file
Use the --config or -c flag to specify a config file:
Auto-detected config
If we don’t specify a config file, openapi-changes automatically looks for changes-rules.yaml in the following places:
- The current working directory (
./changes-rules.yaml) - Our home config directory (
~/.config/changes-rules.yaml)
openapi-changes uses the
default breaking rules from libopenapi.Configuration structure
Each rule in the config file has three options:
addedis adding this property a breaking change?modifiedis modifying this property a breaking change?removedis removing this property a breaking change?
Set any option to true (breaking) or false (not breaking).
Top level configuration
Every object in OpenAPI is configured at the root level of the configuration file.
For example, the Contact object in OpenAPI is a child of the Info object, like this
info:
title: an example
contact:
name: quobix
url: https://pb33f.io
However, to configure breaking changes for the Info and Contact objects, we need to configure them individually and
not configure them in a nested way.
# CORRECT WAY
# info is its own independent configuration, on the root of the configuration doc.
info:
title:
modified: true
# contact is its own independent configuration as well, off the root.
contact:
name:
modified: true
url:
modified: true
It is tempting to try and configure things as the OpenAPI document object flows.
# INCORRECT WAY
info:
title:
modified: true
contact: # <--- even though this is nested in OpenAPI, we need to configure objects independently on the root
name:
modified: true
url:
modified: true
But this will fail, the nested rules will simply be ignored.
What about arrays of objects?
Using Server as an example, there are multiple places in which a Server is used:
- Off the root of the document (via
servers) - In
Operationobjects (viaservers) - In
PathItemobjects (viaservers)
To configure if adding/removing a Server object throws a breaking change, we need to decide where we care:
# Root-level servers
servers:
added: false
removed: false
# Path-level servers
pathItem:
servers:
added: false
removed: false
# Operation-level servers
operation:
servers:
added: false
removed: false
And to configure the Server object on its own properties:
# Server object
server:
description:
modified: true
name:
modified: true
The same technique applies to Tag objects, configuring them off the root of the document and then the Tag its self.
# Root-level tags array (adding/removing tags)
tags:
added: true
removed: false
# Individual tag properties (modifying tag fields)
tag:
name:
modified: true
summary:
modified: false
parent:
modified: true
Real-world examples
Relaxed mode for deprecation workflows
When we’re deprecating endpoints, removing them isn’t really breaking — our consumers have been warned. This config makes operation and path removals non-breaking:
# changes-rules.yaml - Relaxed rules for deprecation workflows
pathItem:
get:
removed: false
post:
removed: false
put:
removed: false
delete:
removed: false
patch:
removed: false
options:
removed: false
head:
removed: false
trace:
removed: false
Flexible enum handling
Enum changes are often flagged as breaking, but in practice, adding new enum values is usually safe (consumers should handle unknown values), and removing values might be acceptable in some contexts:
# changes-rules.yaml - Flexible enum handling
schema:
enum:
added: false # Adding new enum values is safe
removed: false # Removing enum values is acceptable
modified: false # Reordering enum values is fine
Strict mode for new APIs
For brand new APIs in active development, we might want stricter rules that flag more changes as breaking to ensure stability:
# changes-rules.yaml - Strict mode for new APIs
schema:
description:
modified: true # Even description changes should be reviewed
example:
modified: true # Example changes could affect consumer tests
operation:
summary:
modified: true # Summary changes need review
description:
modified: true # Description changes need review
Ignoring optional parameter changes
If our API consumers are expected to handle optional parameters gracefully, we can make adding new optional parameters non-breaking:
# changes-rules.yaml - Flexible optional parameters
parameter:
required:
modified: false # Changing required status is acceptable
schema:
required:
modified: false # Schema required changes are acceptable
Full example with multiple overrides
Here’s a comprehensive example combining several common overrides:
# changes-rules.yaml - Production API rules
#
# These rules are tailored for a mature API with:
# - Deprecation workflows (removals are expected)
# - Flexible enum handling (consumers handle unknowns)
# - Strict parameter rules (parameters are contracted)
# Operation/endpoint removals are expected (deprecation workflow)
pathItem:
get:
removed: false
post:
removed: false
put:
removed: false
delete:
removed: false
patch:
removed: false
# Enum handling - consumers should handle unknown values
schema:
enum:
added: false
removed: false
# Security changes should always be flagged
securityRequirement:
schemes:
added: true
removed: true
scopes:
added: true
removed: true
Flexible response code handling
By default, removing response codes (like 404, 500) from an operation is considered breaking.
If we have a flexible API or are cleaning up unused response codes, we can make these non-breaking:
# changes-rules.yaml - Flexible response codes
responses:
codes:
removed: false # Removing response codes is acceptable
added: false # Adding new response codes is safe
responses.codes rule controls individual response codes (200, 404, 500, etc.).
Use operation.responses to control when the entire responses object is removed from an operation.Configurable objects
The following objects can be configured. Bold items are parent objects that contain the configurable child properties listed beneath them.
Document
- openapi
- jsonSchemaDialect
- servers
- tags
- schemas
- security
- info
- title
- summary
- description
- termsOfService
- version
- contact
- license
- contact
- url
- name
- license
- url
- name
- identifier
- externalDocs
- url
- description
Paths & Operations
- paths
- path
- pathItem
- description
- summary
- get
- put
- post
- delete
- options
- head
- patch
- trace
- query
- additionalOperations
- servers
- parameters
- operation
- tags
- summary
- description
- deprecated
- operationId
- externalDocs
- responses
- parameters
- security
- requestBody
- callbacks
- servers
Parameters & Request/Response
- parameter
- name
- in
- description
- required
- allowEmptyValue
- style
- allowReserved
- explode
- deprecated
- example
- schema
- items
- requestBody
- description
- required
- responses
- default
- codes
- response
- description
- summary
- schema
- examples
- header
- description
- style
- allowReserved
- allowEmptyValue
- explode
- example
- deprecated
- required
- schema
- items
- mediaType
- example
- schema
- itemSchema
- itemEncoding
- encoding
- contentType
- style
- explode
- allowReserved
Schema
- schema
- $ref
- $dynamicAnchor
- $dynamicRef
- type
- title
- description
- format
- maximum
- minimum
- exclusiveMaximum
- exclusiveMinimum
- maxLength
- minLength
- pattern
- maxItems
- minItems
- maxProperties
- minProperties
- uniqueItems
- multipleOf
- contentEncoding
- contentMediaType
- default
- const
- nullable
- readOnly
- writeOnly
- deprecated
- example
- examples
- required
- enum
- properties
- additionalProperties
- allOf
- anyOf
- oneOf
- prefixItems
- items
- discriminator
- externalDocs
- not
- if
- then
- else
- propertyNames
- contains
- unevaluatedItems
- unevaluatedProperties
- dependentRequired
- xml
- schemaDialect
- discriminator
- propertyName
- defaultMapping
- mapping
- xml
- name
- namespace
- prefix
- attribute
- nodeType
- wrapped
Servers
- server
- name
- url
- description
- serverVariable
- enum
- default
- description
Security
- securityScheme
- type
- description
- name
- in
- scheme
- bearerFormat
- openIdConnectUrl
- oauth2MetadataUrl
- flows
- scopes
- deprecated
- securityRequirement
- schemes
- scopes
- oAuthFlows
- implicit
- password
- clientCredentials
- authorizationCode
- device
- oAuthFlow
- authorizationUrl
- tokenUrl
- refreshUrl
- scopes
Other
- tag
- name
- summary
- description
- parent
- kind
- externalDocs
- callback
- expressions
- link
- operationRef
- operationId
- requestBody
- description
- server
- parameters
- example
- summary
- description
- value
- externalValue
- dataValue
- serializedValue
For default values, see the breaking rules reference.
What’s next?
Now that we know how to customize breaking change detection, check out:
- Command arguments for all available flags
- Summary command for CI/CD-friendly output
- HTML report for visual change exploration