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 pickname) — 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
| Location | Allowed |
|---|---|
| Model fields | Yes |
| Object fields | Yes |
| Tuple elements | Yes |
| Enum values | No |
| Literal variants | No |
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.