Class Types
Every class has a type that determines its role in the data model. There are four types: BASIC, ABSTRACT, NONE, and EXTENSION.
The core rule
Every object in the system has exactly one BASIC class. This is the object's primary identity. On top of that, objects accumulate any number of ABSTRACT classes — through inheritance and through relations.
BASIC
A BASIC class is an instantiable type. When a user creates an object, they choose a BASIC class. The object then has that BASIC class as its primary type, plus all ABSTRACT classes inherited through the inheritance chain.
{
"class": "commons.person",
"type": "BASIC",
"inherits": "commons.entity"
}
A person object has the types: commons.person (BASIC), commons.entity (ABSTRACT), commons.item (ABSTRACT).
ABSTRACT
An ABSTRACT class cannot be the sole type of an object. Objects accumulate ABSTRACT classes in two ways:
1. Through inheritance — every class in the inheritance chain is added to the object's type set.
commons.person (BASIC) → commons.entity (ABSTRACT) → commons.item (ABSTRACT)
A commons.person object has all three types.
2. Through relations — when an object is linked as a relation, the type defined on that relation is added to the linked object.
See Relations for details.
NONE
NONE is used exclusively in index.json. It marks the entry point of a repository and cannot be used for objects.
{
"class": "my-project.index",
"type": "NONE",
"imports": [
"my-project.employee",
"my-project.company"
]
}
EXTENSION
An EXTENSION class adds value types, relations, or formulas to an existing commons class without replacing it. Use extends instead of inherits.
{
"class": "my-project.person",
"type": "EXTENSION",
"extends": "commons.person",
"valueTypes": [
{ "name": "employeeNumber" }
]
}
This adds an employeeNumber field to every commons.person object in the tenant, without creating a new class.
Supported properties per type
The table below shows which top-level properties are supported for each class type.
| Property | BASIC | ABSTRACT | EXTENSION | NONE |
|---|---|---|---|---|
valueTypes | ✓ | ✓ | ✓ | — |
relations | ✓ | ✓ | ✓ | — |
formulas | ✓ | ✓ | ✓ | — |
scripts | ✓ | ✓ | ✓ | GENERAL only |
lists | ✓ | ✓ | ✓ | ✓ |
views | ✓ | ✓ | ✓ | — |
layouts | ✓ | ✓ | ✓ | ✓ |
assets | ✓ | ✓ | ✓ | ✓ |
translations | ✓ | ✓ | ✓ | ✓ |
data | ✓ | ✓ | ✓ | ✓ |
migration | ✓ | ✓ | ✓ | ✓ |
Notes:
- Scripts on
NONEmust have scopeGENERAL—OBJECT-scoped scripts require a concrete object and are not supported. listscan be defined on any class type and referenced from any class in the same namespace. Define them where they are logically grouped, regardless of class type.dataandmigrationare most commonly placed on a dedicatedNONE-typed class, but are technically valid on any type.
Required properties per type
| Type | inherits | extends |
|---|---|---|
BASIC | required | not used |
ABSTRACT | required | not used |
NONE | not applicable | not used |
EXTENSION | not used | required |
BASIC and ABSTRACT classes must always declare inherits. The only class without inherits is commons.item itself — it is the root of the entire hierarchy and is provided by the platform.
Inheritance rules
- Inheritance chains must not contain loops
- A BASIC class must never inherit from another BASIC class, directly or indirectly. Every class in a BASIC class's inheritance chain must be ABSTRACT.
commons.itemis the root of all classes — every class eventually inherits from it- EXTENSION classes use
extends(notinherits) and reference a class they are extending
EXTENSION vs custom ABSTRACT: when to use which
Both can add fields to an existing commons class, but they behave very differently.
EXTENSION adds fields to every object of the target commons type in the entire tenant:
{
"class": "my-project.person",
"type": "EXTENSION",
"extends": "commons.person",
"valueTypes": [
{ "name": "employeeNumber" },
{ "name": "department" }
]
}
Every commons.person in the tenant now has employeeNumber and department, regardless of their role.
Custom ABSTRACT adds fields only to objects that take on a specific role via a relation:
{
"class": "my-project.agent",
"type": "ABSTRACT",
"inherits": "commons.person",
"valueTypes": [
{ "name": "agentCode" },
{ "name": "licenseNumber" }
]
}
agentCode and licenseNumber are only added to a person when they are linked as a my-project.agent relation. Other persons are unaffected.
Decision guide:
| Question | Use |
|---|---|
| Should every person / company in the tenant have this field? | EXTENSION |
| Should only persons / companies in a specific role have this field? | Custom ABSTRACT |
| Do you want to use this type as a relation target? | Custom ABSTRACT |
Example: EmploymentAgreement
This example shows how a single object can accumulate multiple types.
{
"class": "commons.employmentAgreement",
"type": "BASIC",
"inherits": "commons.agreement",
"relations": [
{ "type": "commons.company", "required": true, "multiple": false },
{ "type": "commons.employee", "required": true, "multiple": false }
]
}
{
"class": "commons.employee",
"type": "ABSTRACT",
"inherits": "commons.person"
}
When a commons.person object is linked to an employmentAgreement as the commons.employee relation:
- The person's existing types:
commons.person,commons.entity,commons.item - The relation adds:
commons.employee - The person now has all four types
The person's BASIC class remains commons.person. The commons.employee class adds its own value types (e.g., job title, employee number) to the person, but does not change what kind of object it fundamentally is.
Example: complete ABSTRACT class definition
An ABSTRACT class can carry the same properties as a BASIC class — valueTypes, relations, lists, translations, and more. Everything defined on the ABSTRACT class is inherited by any BASIC class that extends it.
This example defines my-project.serviceTicket as an ABSTRACT base for ticket types. Concrete ticket classes (e.g. my-project.bugReport, my-project.changeRequest) inherit from it.
{
"class": "my-project.serviceTicket",
"type": "ABSTRACT",
"inherits": "commons.dossier",
"valueTypes": [
{ "name": "subject", "required": true },
{ "name": "priority", "dataType": "LIST", "list": "my-project.ticketPriority" },
{ "name": "description", "dataType": "TEXTBLOCK" }
],
"formulas": [
{ "valueType": "name", "template": "{{subject}}" }
],
"relations": [
{ "type": "commons.person", "required": true, "multiple": false }
],
"lists": [
{
"name": "my-project.ticketPriority",
"items": [
{ "key": "low", "color": "#1a7f37", "backgroundColor": "#dafbe1" },
{ "key": "medium", "color": "#9a6700", "backgroundColor": "#fff8c5" },
{ "key": "high", "color": "#cf222e", "backgroundColor": "#ffebe9" }
]
}
],
"translations": [
{
"language": "en",
"singular": "Service Ticket",
"plural": "Service Tickets",
"valueTypeLabels": {
"subject": "Subject",
"priority": "Priority",
"description": "Description"
},
"listValueLabels": {
"my-project.ticketPriority": {
"low": "Low",
"medium": "Medium",
"high": "High"
}
}
}
]
}
The ticketPriority list uses both color and backgroundColor so each value renders as a colored badge — appropriate here because priority carries visual meaning. For lists where the values are neutral (e.g. categories, departments), omit the color properties entirely. See Lists for the full rendering rules.
A concrete subclass only needs to add what is specific to it:
{
"class": "my-project.bugReport",
"type": "BASIC",
"inherits": "my-project.serviceTicket",
"valueTypes": [
{ "name": "stepsToReproduce", "dataType": "TEXTBLOCK" }
],
"translations": [
{
"language": "en",
"singular": "Bug Report",
"plural": "Bug Reports",
"valueTypeLabels": {
"stepsToReproduce": "Steps to reproduce"
}
}
]
}
my-project.bugReport inherits subject, priority, description, the ticketPriority list, the commons.person relation, and all translations from my-project.serviceTicket. It only needs to define what is unique to a bug report.
Example: Shareholder (abstract relation type)
A shareholder relation needs to accept both persons and companies. Since you cannot add a BASIC class as a relation type (that would restrict it to only that type), you use an ABSTRACT class that sits above both.
{
"class": "my-project.shareholder",
"type": "ABSTRACT",
"inherits": "commons.entity"
}
commons.entity is inherited by both commons.person and commons.company. So when the platform needs to find objects that can be linked as a my-project.shareholder:
- It walks up the inheritance chain of
my-project.shareholder - It finds
commons.entityas the most specific type that BASIC classes inherit from - It searches for objects of type
commons.entity(both persons and companies)
When creating a new shareholder, the user is presented with a choice: create a commons.person or a commons.company. The newly created object gets the my-project.shareholder class added automatically.