From cfdb7c047520f503718155a3f82b43c8c1011f70 Mon Sep 17 00:00:00 2001 From: Marcelo Soares Date: Thu, 16 Jul 2026 00:27:19 -0300 Subject: [PATCH 1/2] docs: Document inherited field tail ordering --- .../02-inheritance-and-polymorphism.md | 45 +++++++++++++++++++ docs/06-concepts/lookups/model-reference.md | 1 + 2 files changed, 46 insertions(+) diff --git a/docs/06-concepts/03-data-and-the-database/01-models/02-inheritance-and-polymorphism.md b/docs/06-concepts/03-data-and-the-database/01-models/02-inheritance-and-polymorphism.md index bdc5e254..5a5e14bc 100644 --- a/docs/06-concepts/03-data-and-the-database/01-models/02-inheritance-and-polymorphism.md +++ b/docs/06-concepts/03-data-and-the-database/01-models/02-inheritance-and-polymorphism.md @@ -72,6 +72,51 @@ indexes: Indexes can be defined on inherited fields in a child class with a table, and relations work normally with inherited table classes. To use a base model that is shared between server and client and extend it on the server with a table, see [Shared packages](./shared-packages). +### Place inherited fields at the end + +By default, inherited fields are ordered from the root class down to the child class. Add the `tail` option to a field when it should appear after the hierarchy's normal fields. This is useful for common metadata such as creation and update timestamps that should remain at the end of generated constructors and serialized output. + +Add `tail` after the field type and any other field options: + +```yaml +class: Entity +fields: + tenantId: UuidValue + createdAt: DateTime, default=now, tail + updatedAt: DateTime, default=now, tail +``` + +Tail ordering applies across multiple inheritance levels. Normal fields keep their root-to-child order. Tail fields are then added from the most specific child back toward the root parent: + +```yaml +class: ArchivedEntity +extends: Entity +fields: + archiveReason: String + archivedAt: DateTime, tail +``` + +```yaml +class: Article +extends: ArchivedEntity +fields: + title: String + publishedAt: DateTime?, tail +``` + +The generated `Article` field order is: + +1. `tenantId`, the normal root field. +2. `archiveReason`, the normal parent field. +3. `title`, the normal child field. +4. `publishedAt`, the child tail field. +5. `archivedAt`, the parent tail field. +6. `createdAt` and `updatedAt`, the root tail fields in their declared order. + +This order is used by generated constructor parameters and serialization code. For table models, it is also used in generated table definitions, with the primary `id` first. The `tail` option is not allowed on `id`. + +Changing `tail` only changes generated field ordering. It does not rename a database column or create a migration solely to reorder columns in an existing table. + ### Sealed classes In addition to the `extends` keyword, you can also use the `sealed` keyword to create sealed class hierarchies, enabling exhaustive type checking. With sealed classes, the compiler knows all subclasses, ensuring that every possible case is handled when working with the model. diff --git a/docs/06-concepts/lookups/model-reference.md b/docs/06-concepts/lookups/model-reference.md index 8952268c..2c73b045 100644 --- a/docs/06-concepts/lookups/model-reference.md +++ b/docs/06-concepts/lookups/model-reference.md @@ -18,6 +18,7 @@ Every keyword available in a Serverpod model file, and whether it applies to a ` | [**fields**](../data-and-the-database/models#class) | All fields in the generated class should be listed here. | ✅ | ✅ | | | [**type (fields)**](../data-and-the-database/models#class) | Denotes the data type for a field. | ✅ | ✅ | | | [**required**](../data-and-the-database/models#required-fields) | Makes the field as required. This keyword can only be used for **nullable** fields. | ✅ | ✅ | | +| [**tail (fields)**](../data-and-the-database/models/inheritance-and-polymorphism#place-inherited-fields-at-the-end) | Places the field after normal fields in generated inheritance order. Child tail fields precede parent tail fields. | ✅ | ✅ | | | [**scope**](../data-and-the-database/models#limiting-visibility-of-a-generated-class) | Denotes the scope for a field. | ✅ | | | | [**jsonKey**](../data-and-the-database/models#json-key-aliasing) | Sets a custom key name for JSON serialization and deserialization. | ✅ | | | | [**persist**](../data-and-the-database/database/tables) | A boolean flag if the data should be stored in the database or not can be negated with `!persist` | ✅ | | | From 6f59983d9dd83d2fa9cdf64993968198cd789cf5 Mon Sep 17 00:00:00 2001 From: Marcelo Soares Date: Tue, 21 Jul 2026 15:32:31 -0300 Subject: [PATCH 2/2] docs: Polish the tail field ordering section Use a noun-shaped heading consistent with the neighbouring sections, give the example hierarchy names that read naturally, and state the motivation for the keyword before the syntax. --- .../02-inheritance-and-polymorphism.md | 38 +++++++++---------- docs/06-concepts/lookups/model-reference.md | 2 +- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/docs/06-concepts/03-data-and-the-database/01-models/02-inheritance-and-polymorphism.md b/docs/06-concepts/03-data-and-the-database/01-models/02-inheritance-and-polymorphism.md index 5a5e14bc..2ad5c44b 100644 --- a/docs/06-concepts/03-data-and-the-database/01-models/02-inheritance-and-polymorphism.md +++ b/docs/06-concepts/03-data-and-the-database/01-models/02-inheritance-and-polymorphism.md @@ -72,50 +72,50 @@ indexes: Indexes can be defined on inherited fields in a child class with a table, and relations work normally with inherited table classes. To use a base model that is shared between server and client and extend it on the server with a table, see [Shared packages](./shared-packages). -### Place inherited fields at the end +### Ordering inherited fields -By default, inherited fields are ordered from the root class down to the child class. Add the `tail` option to a field when it should appear after the hierarchy's normal fields. This is useful for common metadata such as creation and update timestamps that should remain at the end of generated constructors and serialized output. +Inherited fields are ordered from the root class down to the child class. Since a base class is declared first, its audit fields end up in front of the fields that describe the child. Mark a field with the `tail` option to push it behind every normal field in the hierarchy instead. Add `tail` after the field type and any other field options: ```yaml -class: Entity +class: BaseEntity fields: - tenantId: UuidValue + ownerId: UuidValue createdAt: DateTime, default=now, tail updatedAt: DateTime, default=now, tail ``` -Tail ordering applies across multiple inheritance levels. Normal fields keep their root-to-child order. Tail fields are then added from the most specific child back toward the root parent: +Tail ordering applies across the whole hierarchy. Normal fields keep their root-to-child order, and the tail fields follow, starting from the most specific child and working back toward the root parent: ```yaml -class: ArchivedEntity -extends: Entity +class: Document +extends: BaseEntity fields: - archiveReason: String - archivedAt: DateTime, tail + authorName: String + archivedAt: DateTime?, tail ``` ```yaml class: Article -extends: ArchivedEntity +extends: Document fields: title: String publishedAt: DateTime?, tail ``` -The generated `Article` field order is: +The generated `Article` class orders its fields like this: -1. `tenantId`, the normal root field. -2. `archiveReason`, the normal parent field. -3. `title`, the normal child field. -4. `publishedAt`, the child tail field. -5. `archivedAt`, the parent tail field. -6. `createdAt` and `updatedAt`, the root tail fields in their declared order. +1. `ownerId`, a normal field from the root class. +2. `authorName`, a normal field from the parent class. +3. `title`, a normal field from the child class. +4. `publishedAt`, a tail field from the child class. +5. `archivedAt`, a tail field from the parent class. +6. `createdAt` and `updatedAt`, tail fields from the root class, in the order they are declared. -This order is used by generated constructor parameters and serialization code. For table models, it is also used in generated table definitions, with the primary `id` first. The `tail` option is not allowed on `id`. +The same order applies to the generated constructor parameters and the serialized output. For table models it also determines the generated table definition, where the primary `id` column always comes first. The `tail` option cannot be used on `id`. -Changing `tail` only changes generated field ordering. It does not rename a database column or create a migration solely to reorder columns in an existing table. +Adding or removing `tail` only affects generated code. It does not rename a database column, and it does not produce a migration that reorders the columns of an existing table. ### Sealed classes diff --git a/docs/06-concepts/lookups/model-reference.md b/docs/06-concepts/lookups/model-reference.md index 2c73b045..949ab4d4 100644 --- a/docs/06-concepts/lookups/model-reference.md +++ b/docs/06-concepts/lookups/model-reference.md @@ -18,7 +18,7 @@ Every keyword available in a Serverpod model file, and whether it applies to a ` | [**fields**](../data-and-the-database/models#class) | All fields in the generated class should be listed here. | ✅ | ✅ | | | [**type (fields)**](../data-and-the-database/models#class) | Denotes the data type for a field. | ✅ | ✅ | | | [**required**](../data-and-the-database/models#required-fields) | Makes the field as required. This keyword can only be used for **nullable** fields. | ✅ | ✅ | | -| [**tail (fields)**](../data-and-the-database/models/inheritance-and-polymorphism#place-inherited-fields-at-the-end) | Places the field after normal fields in generated inheritance order. Child tail fields precede parent tail fields. | ✅ | ✅ | | +| [**tail (fields)**](../data-and-the-database/models/inheritance-and-polymorphism#ordering-inherited-fields) | Places the field after all normal fields of an inheritance hierarchy. Child tail fields come before parent tail fields. | ✅ | ✅ | | | [**scope**](../data-and-the-database/models#limiting-visibility-of-a-generated-class) | Denotes the scope for a field. | ✅ | | | | [**jsonKey**](../data-and-the-database/models#json-key-aliasing) | Sets a custom key name for JSON serialization and deserialization. | ✅ | | | | [**persist**](../data-and-the-database/database/tables) | A boolean flag if the data should be stored in the database or not can be negated with `!persist` | ✅ | | |