Cerial
Inheritance

Private Fields

The !!private modifier for preventing field override in child types.

The !!private modifier prevents a field from being overridden in child types that use extends. It protects fields that should stay exactly as the parent defined them — the most common use case is locking down id and timestamp fields in abstract base models.

Syntax

Place !!private at the end of a field line, after all decorators:

abstract model BaseEntity {
  id Record @id !!private
  createdAt Date @createdAt !!private
  updatedAt Date @updatedAt
}

!!private takes no arguments. It's not a decorator (no @ prefix) — it's a field modifier that's processed during inheritance resolution and then stripped from the final output.

What Private Does

Private prevents one thing: redefining the field in a child's body. If a child tries to declare a field with the same name, Cerial raises a validation error:

abstract model Base {
  id Record @id !!private
  name String !!private
}

// ERROR: Cannot override private field 'name'
model Child extends Base {
  name String @default('override')  // not allowed
}

What Private Does Not Do

Private fields can still be:

  • Inherited normally by children — the field appears in the child's generated types
  • Omitted from pick lists (extends Base[!name]) — private doesn't prevent exclusion
  • Excluded via pick (extends Base[id] doesn't pick name) — only listed fields are kept

!!private controls override, not visibility. A child that doesn't need the field can simply omit it.

abstract model Base {
  id Record @id !!private
  name String !!private
  email Email
}

// OK: picks only email, omitting private fields
model Subset extends Base[email] {
  role String
}

// OK: omits email, keeps private fields
model WithPrivate extends Base[!email] {
  tag String
}

Where Allowed

LocationAllowed
Model fieldsYes
Object fieldsYes
Tuple elementsYes
Enum valuesNo
Literal variantsNo

Model Fields

The most common use case — lock down identity and timestamp fields in abstract bases:

abstract model BaseEntity {
  id Record @id !!private
  createdAt Date @createdAt !!private
  updatedAt Date @updatedAt
}

model User extends BaseEntity {
  email Email @unique
  name String
}

User inherits id and createdAt as-is. It could override updatedAt (not private) but can't touch the other two.

Object Fields

Protect specific sub-fields in shared object definitions:

object BaseAddress {
  street String !!private
  city String
  zip String !!private
  country String @default('US')
}

object DetailedAddress extends BaseAddress {
  // street and zip are inherited but cannot be redefined here
  country String @default('USA')   // OK: not private
  apartment String?
}

Tuple Elements

Protect tuple elements by position. Private elements can't be overridden by name:

tuple SecurePair { String !!private, Int !!private }

tuple Extended extends SecurePair {
  Bool  // appended as third element — OK
}

The first two elements are locked. New elements can still be appended.

Combining with Decorators

!!private goes after all decorators and works alongside any field-level decorator:

abstract model Base {
  id Record @id !!private
  email Email @unique !!private
  createdAt Date @createdAt !!private
  role String @default('user') !!private
  tags String[] @set !!private
}

The decorators apply as normal. !!private only adds the override-protection behavior on top.

Practical Pattern

A common pattern pairs abstract models with !!private on structural fields, leaving domain fields open for override or extension:

abstract model Auditable {
  id Record @id !!private
  createdAt Date @createdAt !!private
  updatedAt Date @updatedAt !!private
  createdBy String !!private
}

model Invoice extends Auditable {
  amount Float
  status String @default('draft')
}

model Receipt extends Auditable {
  invoiceId Record
  paidAt Date
}

Every child gets consistent audit fields that can't be accidentally changed. Domain-specific fields are added freely in each child.

!!private is most meaningful on abstract models since those are the types designed to be extended. While you can use !!private on any model or object, it only has an effect when the type is actually extended by another type.

On this page