Skip to main content

Inheritance

Krios supports single inheritance with a maximum depth of 2. A child type extends a parent type and inherits its fields; the child can add fields but can't redefine inherited ones.

Setting a parent

Set via parentApiName (CLI / seed) or parentId (REST):

{
"apiName": "articlePage",
"parentApiName": "basePage",
"fields": [
{ "apiName": "body", "fieldType": "richtext", "isLocalizable": true }
]
}

articlePage inherits every field declared on basePage. The validator that runs on field create rejects an apiName that collides with an inherited field — inherited fields are managed on the parent only.

Max depth 2

A single inheritance hop is enough for most cases:

BasePage (title, slug, SEO fields)
└── ArticlePage (body, excerpt, publishedAt)
ComposablePage (extends BasePage; adds blocks)
└── LandingPage (no new fields; blocks is the primary content)

Three-deep is rejected by the validator. The recommendation for complex shapes is composition via reference / blocks — see Blocks and Composition.

Field origin metadata

resolveAllFields(db, contentTypeId) returns every field in the chain plus origin info:

{
apiName: "title",
...,
origin: {
contentTypeId: "ckl_basePage",
contentTypeApiName: "basePage",
inherited: true
}
}

The entry editor groups inherited fields under their origin within each tab, so authors see at a glance which fields come from which parent.

What's inherited

PropertyInherited?
Field definitions✅ — every field on the parent shows up on the child.
Required-field rules✅ — required fields stay required on the child.
Tab assignment✅ — inherited fields keep their parent's tab.
allowedChildTypes❌ — set per type; the child has its own list.
availableSiteIds❌ — set per type.
isRoutable / isPublishable / isSingleton❌ — set per type. The child can be routable while the parent isn't.

Modifying inherited fields

You can't redefine an inherited field on the child. You can:

  • Add new fields with the same tab as inherited ones (they appear together in the editor).
  • Remove a field on the parent (it disappears from the child too). The in-use guard checks only the declaring type's own entries for values — it does not count child-type entries holding the inherited field, so the orphan check covers the declaring type's own entries only.
  • Change the parent's field's flags (e.g. flip isRequired) — the child reflects the change immediately.

Worked example

// basePage
{
"apiName": "basePage",
"fields": [
{ "apiName": "title", "fieldType": "text", "isRequired": true, "isLocalizable": true, "uiConfig": { "tab": "Content" } },
{ "apiName": "slug", "fieldType": "slug", "isRequired": true, "uiConfig": { "tab": "Content" } },
{ "apiName": "metaTitle", "fieldType": "text", "isLocalizable": true, "uiConfig": { "tab": "SEO" } },
{ "apiName": "metaDescription", "fieldType": "text", "isLocalizable": true, "uiConfig": { "tab": "SEO" } }
]
}

// articlePage extends basePage
{
"apiName": "articlePage",
"parentApiName": "basePage",
"fields": [
{ "apiName": "body", "fieldType": "richtext", "isRequired": true, "isLocalizable": true, "uiConfig": { "tab": "Content" } }
]
}

articlePage has five fields effectively: title, slug, metaTitle, metaDescription (inherited from basePage), plus body (own). The entry editor renders the Content tab with the title, slug, and body grouped together, with a small "from basePage" label on the inherited rows.