Skip to main content

Custom Rules

Custom rules enforce conventions not covered by built-in rules. Define them under top-level rules, then enable them from a profile with include.

Define and enable a rule

specmatic-linter.yaml
rules:
company/parameter-description:
type: parameters
severity: error
message: Every parameter must have a description.
reference: https://example.com/api-guidelines#parameters
fix:
- Add a useful description.
on:
type: Parameter
must:
required:
- description

profiles:
default:
rules:
extends:
- recommended
include:
- company/parameter-description

A custom rule has four core parts:

  • on: nodes to inspect;
  • must: assertions those nodes must satisfy;
  • message: violation text;
  • severity: error, warn, or off.

type, reference, fix, and maturity add classification and remediation context to reports.

Top-level rules is only an inventory. A rule runs only when a selected profile names it in rules.include. This lets several profiles reuse one definition.

Build a rule in four steps

  1. Name policy: choose a stable ID such as company/operation-id.
  2. Select nodes: use on.type, then optionally property and key filters.
  3. State requirement: add one or more assertions under must.
  4. Enable rule: add its ID to a profile's rules.include.

Start with severity: warn. Run against representative specifications, refine targeting, then promote it to error when findings are accurate.

Target nodes

on:
type: Operation
property: operationId

type selects an OpenAPI node type. property selects one property or a list of properties within each matching node. Omit property to assert against the node itself.

For example, this targets every operation's operationId value:

on:
type: Operation
property: operationId

This targets each complete operation object instead:

on:
type: Operation

Narrow matches by key:

on:
type: Operation
filterInParentKeys:
- get
- post

Available filters:

  • filterInParentKeys: include matching current or parent keys;
  • filterOutParentKeys: exclude matching current or parent keys;
  • matchParentKeys: include keys matching a regular expression.

Filters inspect the matched node's key and immediate parent key. Use when when a condition depends on a more distant ancestor, such as a response belonging to a GET operation.

Map node types—such as SchemaProperties, Responses, and MediaTypesMap—have useful key behavior. Assertions such as pattern, enum, and casing run against each map key when property is omitted. Object assertions such as required inspect the complete object.

See Configuration Reference for supported node types.

Add assertions

Multiple assertions under must must all pass:

rules:
company/operation-summary:
on:
type: Operation
property: summary
must:
nonEmpty: true
minLength: 10
maxLength: 80
message: Operation summary must contain 10 to 80 characters.
AssertionChecks
definedValue exists or does not exist
requiredObject contains every named property
requireAnyObject contains at least one named property
disallowedValue/property is absent
nonEmptyString/list contains a value
pattern, notPatternValue matches/avoids a regular expression
enum, constValue belongs to a list/equals one value
containsList contains required values
minLength, maxLengthString/list size is within limit
casingValue follows a named casing style
sortOrderList uses ascending or descending order
mutuallyExclusiveAt most one named property exists
mutuallyRequiredNamed properties appear together or not at all
refNode uses, avoids, or matches a $ref

Supported casing values: camelCase, kebab-case, snake_case, PascalCase, MACRO_CASE, COBOL-CASE, and flatcase.

Missing values

Most value assertions—such as pattern, casing, minLength, and maxLength—skip a missing value. Combine them with defined: true when the property must exist:

must:
defined: true
casing: camelCase

Use required when inspecting the parent object:

on:
type: Operation
must:
required:
- operationId
- responses

Regular expressions

Both plain strings and slash-form patterns work:

must:
pattern: /^[a-z][a-zA-Z0-9]+$/

Slash-form supports i, m, and s behavior flags. Quote patterns when YAML punctuation could change parsing.

Apply a rule conditionally

Use when to require matching ancestor context. This example checks response descriptions only for GET operations and 200 responses:

rules:
company/success-description:
on:
type: Response
property: description
filterInParentKeys:
- "200"
when:
- on:
type: Operation
filterInParentKeys:
- get
must:
defined: true
must:
nonEmpty: true
message: GET 200 responses must have a description.

Multiple when clauses match ancestor context in declared order.

Each when clause has the same on and must structure as a rule. It does not report a separate problem; it decides whether the main rule applies.

Add useful messages

Messages support these placeholders:

  • {{problems}}: assertion failure details;
  • {{assertionName}}: rule ID;
  • {{nodeType}}: selected node type;
  • {{key}}: matched node key;
  • {{property}}: selected property;
  • {{pointer}}: JSON Pointer;
  • {{file}}: source filename.

Use {{problems}} when one rule contains several assertions:

message: "Operation {{key}} is invalid: {{problems}}"

Without message, Specmatic creates one from rule ID, target, and assertion failures. Prefer a short custom message explaining policy intent. Put remediation steps in fix and longer guidance in reference.

More examples

Require camelCase operation IDs

rules:
company/operation-id:
type: operations
severity: error
on:
type: Operation
property: operationId
must:
defined: true
casing: camelCase
message: "Operation ID at {{pointer}} must exist and use camelCase: {{problems}}"
fix:
- Add an operationId such as listOrders.

profiles:
default:
rules:
extends:
- recommended
include:
- company/operation-id

This reports both missing IDs and values such as list-orders.

Require camelCase schema property names

SchemaProperties is a map, so casing checks each property key:

rules:
company/schema-property-name:
type: schema
severity: warn
on:
type: SchemaProperties
must:
casing: camelCase
message: "Schema property names must use camelCase: {{problems}}"

profiles:
default:
rules:
include:
- company/schema-property-name

This accepts createdAt and reports created_at.

Require JSON on successful GET responses

This rule combines map-key filtering with ancestor context:

rules:
company/get-success-json:
type: operations
severity: error
on:
type: MediaTypesMap
when:
- on:
type: Operation
filterInParentKeys:
- get
must:
defined: true
- on:
type: Response
filterInParentKeys:
- "200"
- "201"
must:
defined: true
must:
contains:
- application/json
message: Successful GET responses must provide application/json.

profiles:
default:
rules:
include:
- company/get-success-json

Ancestor order matters: Operation appears before Response on the path to MediaTypesMap.

Test a new rule

  1. Add rule with severity: warn.
  2. Include it in one test profile.
  3. Create one specification that should pass and one that should fail.
  4. Run:
docker run --rm \
-v "$(pwd):/usr/src/app" \
-w /usr/src/app \
specmatic/enterprise \
lint pass.yaml fail.yaml --profile test-rules --format=html
  1. Confirm report points to intended file, JSON Pointer, line, and column.
  2. Test excluded operations or response codes to catch overly broad targeting.
  3. Promote rule to error after results match policy.

If loading fails with unknown node type or Unsupported assertion, check custom-rule assertions and node types.