Chapter 04 of 10
OpenAPI Schemas
Objects, arrays and primitives, the difference between allOf, oneOf and anyOf, enums that carry meaning, formats, nullability in both dialects, and examples that stay true.
Most of the effort in an OpenAPI document goes into schemas, and most of the disappointment with generated output traces back to them. A schema that says type: object and stops produces documentation that says nothing and a mock that returns nothing.
The four shapes a schema takes
Every schema is one of four things, whatever the tooling calls them.
An object has named properties and a list of which ones are required. A primitive is a string, a number, an integer or a boolean, optionally narrowed by an enum or by bounds. An array is an ordered list with an item schema. A composition is built from other schemas with allOf, oneOf or anyOf.
The one rule worth memorising is that an array must define its items. A schema with type: array and nothing else is valid in the loosest sense and useless to everything downstream.
Objects and required
Properties and required are separate lists, which surprises people who expect a flag on each property.
Order:
type: object
required: [id, status, total]
properties:
id:
type: string
format: uuid
status:
type: string
enum: [PENDING, PAID, SHIPPED, CANCELLED]
total:
type: number
format: double
note:
type: string
description: Free-text note the customer added at checkout.Required means the property must be present, not that it must be non-empty and not that it is always useful. A property that is absent from required is optional for a request and, more importantly, optional for a response, which means every client has to handle it being missing. Marking response fields required is how you promise they will always be there.
Composition without the confusion
The three combining keywords differ only in how many members must match, and picking the wrong one produces types nobody can use.
allOf means every member matches. It is how you express shared structure, so a PaidOrder can be an Order plus a payment block. Most code generators turn it into inheritance or into a merged type.
oneOf means exactly one member matches. It is the right choice for a value that is genuinely one thing or another, such as a card payment or a bank transfer. Generators turn it into a union or a discriminated type.
anyOf means at least one member matches. It is rarely what you mean. If two members can both match, no generator can decide which type to produce, so the result tends to be an untyped blob.
For oneOf and anyOf a discriminator names the property that says which member applies, which is what turns a union into something a generator can switch on.
Payment:
oneOf:
- $ref: "#/components/schemas/CardPayment"
- $ref: "#/components/schemas/BankTransfer"
discriminator:
propertyName: method
mapping:
card: "#/components/schemas/CardPayment"
transfer: "#/components/schemas/BankTransfer"Enums that mean something
An enum lists the allowed values and says nothing about what they mean. A reader who sees PARTIALLY_FULFILLED learns the spelling and nothing else.
Put the meaning next to the values, because the alternative is that every consumer guesses. A short description that names each value and its consequence is enough, and it is the difference between a status field a client can act on and one they have to ask about.
Two extension fields are widely understood for this. x-enumNames carries a readable label per value and x-enumDescriptions carries an explanation, and generators that know them produce named constants instead of bare strings.
Nullability depends on the dialect
This is the one schema question where the specification version changes the answer.
In OpenAPI 3.0 you set nullable to true, and it only takes effect when type is explicitly set in the same schema. In OpenAPI 3.1 the keyword does not exist, and you give the property two types instead.
# OpenAPI 3.0
cancelledAt:
type: string
format: date-time
nullable: true# OpenAPI 3.1
cancelledAt:
type: [string, "null"]
format: date-timeQuoting "null" in YAML matters, because an unquoted null is the null value rather than the string naming the type. The version chapter covers the rest of the differences.
Formats and real constraints
Format is a hint, and whether it is enforced depends on the validator. Treat format: uuid as documentation for the reader and the generator, and add a pattern or explicit bounds where the value genuinely has to match.
The formats worth using are the ones tooling recognises, such as date, date-time, uuid, email, uri, int32, int64 and double. Inventing your own is allowed and achieves nothing unless you also control the tool that reads it.
Examples that stay true
An example is the part of a schema a reader trusts most and the part most likely to be wrong, because nothing forces it to match the schema it sits under.
Two habits keep them honest. Generate the example from the schema rather than typing it, so a structural change is reflected automatically. And validate examples as part of linting, since an example that no longer matches its schema is a defect a machine can find. Both are covered in validating and linting.
In Routebase
Schemas are edited as structures rather than as YAML, with the kind selector deciding whether you get a property table, a primitive editor, an item type or a composition operator.

Enum values carry an optional label and description that export as x-enumNames and x-enumDescriptions, so the meaning travels with the file rather than living only in the workspace. The example panel generates a sample payload from the structure and can be regenerated after a change, and a usage count on every schema tells you what a change would touch before you make it. The Schemas guide covers all four kinds in detail.
Frequently asked questions
What is a schema in OpenAPI?
A schema describes the shape of a piece of data, meaning its type, its properties, which of them are required and what values they may take. Schemas are used for request bodies, for responses and for parameter values. In OpenAPI 3.1 a schema is JSON Schema 2020-12, and in 3.0 it is a modified subset of an older draft.
What is the difference between allOf, oneOf and anyOf?
All three combine schemas and they differ in how many members have to match. With allOf the data must satisfy every member, which is how composition and inheritance are expressed. With oneOf it must satisfy exactly one, which is how you describe a value that is either this or that. With anyOf it must satisfy at least one, which is the loosest and the least useful for generated code.
How do you document what an enum value means?
The enum keyword only carries the allowed values, so the meaning has to go somewhere else. The usual place is the schema description, written as a short list of the values and what each one implies. Some tooling also reads the extension fields x-enumNames and x-enumDescriptions, which keep the label and the explanation attached to the value itself.
What does format do in OpenAPI?
Format refines a type with a hint such as uuid, date-time, email or int32. Validators may or may not enforce it, so it is best understood as a signal to generators and readers rather than a constraint you can rely on. Where a value really must match a shape, add a pattern or explicit bounds next to the format.
Last reviewed by The Routebase Team.