Model Supertypes and Subtypes in OutSystems
Geplaatst op: 28 augustus 2026 • Erwin van Rijsewijk • Blog

How to Model Supertypes and Subtypes in OutSystems

Without Creating a Monster Entity

The other day I was looking at a data model with several types of customers.

All customers had a name, an email address and a registration date. Private customers also had a date of birth and an emergency contact. Business customers had a company name, a Chamber of Commerce number and several contact persons.

So, how do you model this in OutSystems?

The easiest solution is to create one large Customer entity with every possible attribute. Add a CustomerTypeId, make most attributes optional and you are done.

Simple, right?

It is. Until a few months later, when the entity contains thirty attributes, half of them are empty and nobody remembers whether an empty CompanyName is allowed or just forgotten.

Let’s look at the different options and see when a supertype-subtype model is a better choice.

The examples and screenshots in this article use OutSystems 11 and Service Studio. The same modeling principles can also be used in ODC, although some names and implementation details may be different.

What Are Supertypes and Subtypes?

A supertype contains the information that is shared by several related types.

A subtype contains the information that only belongs to one specific type.

For our example, we use a sports club application. The application has private and business customers.

The shared customer attributes are shown in the sketch below. The application also has two subtypes:

  1. PrivateCustomer
  2. BusinessCustomer

Press enter or click to view image in full size

Figure 1 — A conceptual sketch of Customer as a supertype containing the two subtype entities.

A private customer is a customer. A business customer is also a customer.

That “is a” sentence is a simple way to recognize a subtype.

A contact person is not a business customer. A business customer has contact persons. That is a relationship, not a subtype. We will come back to this later.

Option 1: One Customer Entity

Use one Customer entity with a CustomerTypeId when the customer types are mostly alike. Shared and type-specific attributes then stay together in one record.

How the Model Works

Press enter or click to view image in full size

Figure 2 — One Customer entity with shared and subtype-specific attributes.

The Customer entity contains attributes shared by all customers and attributes that apply to only one type. CustomerTypeId must be mandatory and reference a valid static entity record such as Private or Business, because it determines which rules apply.

This approach is often called single-table inheritance. OutSystems does not have real entity inheritance, but a type attribute provides a simple practical equivalent.

When It Works Well

This model is a strong fit when the differences between customer types are small and their processes are largely the same.

For example, if a business customer only needs a company name and a Chamber of Commerce number, creating several entities may add more complexity than value.

It also keeps Aggregates and integrations simple: every customer is in the same table, so a list of all customers does not need joins.

For simple situations in OutSystems, this is often the most practical model.

Trade-offs and Validation

The trade-off is that attributes that apply only to one customer type must remain optional.

A private customer does not have a Chamber of Commerce number, and a company normally does not have a date of birth. The application must therefore validate attributes based on CustomerTypeId.

For example, in pseudocode:

If Customer.CustomerTypeId = Entities.CustomerType.Business
 Customer.CompanyName is mandatory
 Customer.ChamberOfCommerceNumber is mandatory

These rules may be needed in screens, REST APIs, imports and Server Actions. In screens, validate early for helpful feedback, but do not make the screen the only safeguard.

Use custom create and update wrapper Server Actions as the server-side safeguard. Each wrapper validates the complete Customer input and then calls the generated Entity Action.

Every write path, including screens, REST APIs, imports and other Server Actions, must use these wrappers. Otherwise, screen validation can be bypassed and duplicate rules will eventually drift apart.

Fit Checklist

  • Few types. There are only a few customer types
  • Mostly shared data. Most attributes are shared
  • Limited type-specific data. Each type has only a few specific attributes
  • Mostly shared processes. The customer types use mostly the same processes

Option 2: Separate Subtype Entities

Use separate subtype entities when customer types share a common identity but differ substantially in attributes or processes.

How the Model Works

Keep the shared attributes in Customer and place the type-specific attributes in PrivateCustomer and BusinessCustomer. Each subtype is linked to the same Customer record.

Press enter or click to view image in full size

Figure 3 — Customer as a supertype with optional one-to-one PrivateCustomer and BusinessCustomer subtype extensions.

Enforcing At Most One Record per Subtype

Customer 1 ─── 0..1 PrivateCustomer
Customer 1 ─── 0..1 BusinessCustomer

This is the relationship we want, but it is not automatically enforced just by adding a normal CustomerId reference.

If the subtype has its own identifier, a normal CustomerId reference creates a one-to-many relationship. OutSystems will not stop you from creating two PrivateCustomer records for the same customer.

To enforce at most one record per customer within each subtype entity, you can:

  1. Use CustomerId as the identifier of the subtype.
  2. Give the subtype its own identifier and create a unique index on CustomerId.

I prefer the first solution when the subtype cannot exist without the customer. The subtype then uses the same identifier as its parent, which prevents duplicate rows in that subtype entity but does not require a subtype record to exist.

Choose the Delete Rule on the subtype’s Customer reference deliberately. Use Delete when the subtype should be removed automatically with its Customer, or use Protect when a lifecycle action must handle the dependent record before the Customer can be deleted. Avoid Ignore, because it can leave an orphaned subtype record.

I also made an Umbrella Talks video about one-to-one relationships in OutSystems. If you want some more background on how this works, you can watch it here: Umbrella Talks — One-to-One Relationships.

Press enter or click to view image in full size

Figure 4 — A normal reference allows multiple PrivateCustomer rows per customer; using CustomerId as the identifier allows at most one.

Make this decision before you publish the entity for the first time. Changing the identifier later is not easy and may require creating a new entity and migrating the existing data. That is a lot of work for something that originally took one right-click.

Validation and Lifecycle Actions

Suppose customer 42 is a private customer. We do not want this:

Customer 42
 ├── PrivateCustomer 107
 └── PrivateCustomer 284

Using CustomerId as the subtype identifier prevents this. An identifier is unique, so there can only be one PrivateCustomer record for customer 42.

But there is another rule. In our sports club, a customer is either private or business. A customer cannot be both.

The identifier prevents two records in the same subtype entity. It does not prevent the same customer from having a record in both PrivateCustomer and BusinessCustomer.

The database does not compare both subtype entities, so we need application logic for this.

I would create Server Actions such as:

  1. CreatePrivateCustomer
  2. CreateBusinessCustomer
  3. ChangeCustomerType

CreatePrivateCustomer and CreateBusinessCustomer create or update the Customer and the matching subtype together. Before creating a subtype, they check whether the other subtype already exists.

ChangeCustomerType removes or archives the old subtype, creates the new subtype and updates CustomerTypeId in the same transaction. If any step fails, the transaction must roll back.

Screens, imports and integrations should all use these actions. Do not let each screen create the records in its own way. That usually works well until the second screen is added.

Keeping CustomerTypeId in Sync

When using subtype entities, the customer type can be found by looking at the subtype record.

If a customer exists in PrivateCustomer, it is private. If it exists in BusinessCustomer, it is business.

This means that CustomerTypeId is not always needed.

But storing it can make Aggregates, reports and integrations easier. You can filter by customer type without joining both subtype entities.

Both options are valid:

1. Determine the type from the subtype record.
2. Store CustomerTypeId and update it atomically with the subtype in the central lifecycle actions.

I normally store the type when it is used a lot in filters or integrations. But then all updates must use the same logic. Otherwise, CustomerTypeId may say Private while only a BusinessCustomer record exists.

The database will accept this. The developer investigating the problem on Friday afternoon probably will not.

Contacts: Subtype or Related Entity?

Now it gets a little more interesting.

A private customer can have one emergency contact. A business customer can have several contact persons, such as a manager, administrator and financial contact.

You may be tempted to model these contacts as more subtypes. But a business customer is not a contact person. A business customer has contact persons.

That makes ContactPerson a related entity.

The relationship is:

BusinessCustomer 1 ─── 0..* ContactPerson

Press enter or click to view image in full size

Figure 5 — Recommended model: two optional one-to-one subtype extensions and a one-to-many ContactPerson relationship.

A business customer still has one BusinessCustomer subtype record. That subtype can have several contact persons.

The emergency contact can remain directly on PrivateCustomer, because only one is allowed.

Customer
 ├── PrivateCustomer
 │      └── One emergency contact
 │
 └── BusinessCustomer
 └── Multiple ContactPerson records

The important difference is:

A subtype describes what something is. A related entity describes what something has.

If something can occur several times for the same parent, it is probably not a subtype.

What If Both Are Just Contacts?

You could also create one ContactPerson entity with a reference to Customer.

Customer 1 ─── 0..* ContactPerson

A business customer may have several contacts, while a private customer may only have one.

In that model, the custom contact wrapper Server Action must enforce a maximum of one contact for private customers. For example, in pseudocode:

If Customer.CustomerTypeId = Entities.CustomerType.Private
 and a ContactPerson already exists
 Do not create another contact

This is a good solution when both contact types have the same attributes and behavior.

If an emergency contact and a business contact have a different meaning or lifecycle, I would keep them separate. Having a name and a phone number does not automatically make them the same business concept.

Fit Checklist

  • Shared identity. The types share one Customer identity and are managed as parts of the same customer lifecycle.
  • Substantial differences. Each type has enough type-specific attributes or processes to justify a separate subtype entity.
  • Dependent existence. A subtype cannot exist without its Customer.
  • Single occurrence. Each subtype can occur at most once per Customer; repeating information belongs in a related entity.

Option 3: Separate Customer Entities

Use separate customer entities only when the customer types do not share the same business identity or lifecycle.

How the Model Works

PrivateCustomer and BusinessCustomer are independent entities. Each contains its own copy of Name, EmailAddress, PhoneNumber, RegistrationDate and IsActive.

Press enter or click to view image in full size

Figure 6 — Separate customer entities duplicate their shared attributes.

When It Works Well

This can work when both customer types are almost completely independent, for example when they are used in different applications and never appear in the same processes. For our sports club, it is not a good fit.

Trade-offs

Common attributes and validations are duplicated. Searching all customers requires combining data from two entities, integrations must support two customer structures, and every new shared attribute must be added to both entities.

Fit Checklist

• Independent identity. The customer types do not share the same business identity or lifecycle.
• Acceptable duplication. Duplicating shared data and validation is acceptable.

Which Solution Should You Choose?

Use the decision tree below for the detailed choice.

Press enter or click to view image in full size

Figure 7 — A practical decision tree for choosing the right model.

The table below provides the same choice as a quick reference.

For our sports club, I would use:

• Model. Use Customer as the supertype and PrivateCustomer and BusinessCustomer as optional one-to-one subtype extensions, each keyed by CustomerId.

• Lifecycle. Manage the complete customer through central Server Actions.

• Contacts. Store the single emergency contact on PrivateCustomer and use ContactPerson for the business customer’s repeatable contacts.

Wrapping Up

Start with identity and lifecycle, then compare the type-specific data and behavior. If information can occur more than once for the same parent, model it as a related entity rather than a subtype.

Choose the simplest model that represents those rules clearly and that another developer can understand by looking at it.

Preferably without first having to read twenty pages of documentation.

 

Delen:

Nieuws

APEX Developer inhuren

29 juli 2026 • Transfer Solutions

Aftermovie KlantEvent 2026

17 juni 2026 • Mariette van Pinxteren

Meer nieuws

Blog

Model Supertypes and Subtypes in OutSystems

28 augustus 2026 • Erwin van Rijsewijk

Working with Mentor in ODC

19 augustus 2026 • Marlies Quaadgras

Meer blogitems

Training & Events

Meer training & events