Extending Object Definitions With OpenAPI's allOf
Did you know you can define object inheritance using OpenAPI?
Your OpenAPI documents are getting out of control. You're repeating the same object definitions again and again, pasting the same properties into slightly different versions of users
, accounts
, items
, or whatever your API is built around. You know it's wrong, but you’re not sure how else to keep things consistent. Every small change becomes a risky operation, where one update can accidentally leave part of your API outdated or broken. And when someone else on your team touches the OpenAPI document, the situation even gets worse. There should be a proper way to handle object inheritance in OpenAPI documents, right? Keep reading to see what's possible.
This article is brought to you with the help of our supporter: Speakeasy.
Further expanding its best-in-class API tooling, Speakeasy now empowers teams with open standards. The platform simplifies OpenAPI Overlay adoption so you can focus on building. Click the button below to check out the playground.
Not too long ago, I was working on a project to build an online marketplace. The platform needed to support consumers and publishers, and while they had different behaviors in the system, they were both just users at their core. They both had a name
, email
, address
, and other common fields. So I started defining separate objects for Consumer
and Publisher
. At first, it was fine—I just copied the shared fields. But the moment I had to make a change to something as basic as the address
format, I realized I had already duplicated those fields. And I wasn’t even done with the full set of user roles yet. What began as a quick setup started to feel fragile and error-prone. The idea of changing one field in different places made me feel uncomfortable, especially as I imagined another developer coming in and forgetting one of those updates. That’s when I started looking for a cleaner solution.
There are a few ways to solve this. One is just to live with the duplication and hope nobody notices. Another is to manually define a shared object like UserBase
, then reference it mentally while still copying fields into every other object. That’s not much better. A much cleaner approach is to refactor the shared structure into its own definition and reuse it using OpenAPI's composition feature: allOf
. It’s straightforward. It combines every schema mentioned in a list into one single object. And that’s exactly what we need when we want to define a base user and then extend it with more fields depending on the user type.
To do that, I started by creating a UserBase
definition. This would act as the parent object, which is the common structure shared by both consumers and publishers. Think of it as the foundation or base class that all other user types will inherit from. Here’s how I defined it:
components:
UserBase:
type: object
properties:
name:
type: string
email:
type: string
format: email
Then, to define the Publisher
, I wrote:
Publisher:
allOf:
- $ref: '#/components/schemas/UserBase'
- type: object
properties:
storeName:
type: string
publishedItems:
type: array
items:
$ref: '#/components/schemas/Item'
Note that the publishedItems
property is there just as an example. To really make it work, you have to create the Item
schema. And now, for the Consumer
:
Consumer:
allOf:
- $ref: '#/components/schemas/UserBase'
- type: object
properties:
preferences:
type: object
paymentMethods:
type: array
items:
type: string
Now, both Publisher
and Consumer
include the base user fields and their own specific ones. Tools like Scalar merge everything nicely for display. Code generation tools like Speakeasy give you types or classes with all the combined fields. It makes the whole process smoother and easier to manage. If you need to define required fields, just include them in the relevant parts of the object. You can even go a step further and create smaller building blocks (like ContactInfo
or Address
) and compose those in the same way.
By using allOf
, you clean up your OpenAPI documents, reduce duplication, and make changes safer and more consistent. You stop treating your definitions as a bunch of unrelated pieces and start modeling them like the real-world things they represent: related, structured, and layered. The next time you need to add a field to every user type, you’ll do it in one place. That’s a lot less stressful. The original problem—managing shared fields across complex API objects—becomes much easier to deal with. If you’ve been frustrated by how messy your OpenAPI definitions have gotten, try using allOf
. It might be the simplest, most powerful change you make. And once you see how much cleaner things look, you’ll wonder why you didn’t use it earlier.