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:

openapi-changes summary -c my-rules.yaml old.yaml new.yaml openapi-changes html-report -c ~/configs/strict-rules.yaml ./ specs/openapi.yaml

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)
If no config file is found in any default location, openapi-changes uses the default breaking rules from libopenapi.

Configuration structure

Each rule in the config file has three options:

  • added is adding this property a breaking change?
  • modified is modifying this property a breaking change?
  • removed is removing this property a breaking change?

Set any option to true (breaking) or false (not breaking).

We only need to specify the rules we want to override. Any rules not specified will use the default values.

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 Operation objects (via servers)
  • In PathItem objects (via servers)

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
openapi-changes summary -c deprecation-rules.yaml old.yaml new.yaml

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
The 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
    • email
  • 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: