diff --git a/docs/06-concepts/02-endpoints-and-apis/01-working-with-endpoints.md b/docs/06-concepts/02-endpoints-and-apis/01-working-with-endpoints.md index 613a602f..cd5421f2 100644 --- a/docs/06-concepts/02-endpoints-and-apis/01-working-with-endpoints.md +++ b/docs/06-concepts/02-endpoints-and-apis/01-working-with-endpoints.md @@ -103,7 +103,7 @@ For how users sign in and how scopes work, see [Authentication](./authentication ## Pass and return data -Parameters and return values can be of type `bool`, `int`, `double`, `String`, `UuidValue`, `Duration`, `DateTime`, `ByteData`, `Uri`, `BigInt`, `dynamic`, [vector and geography types](./data-and-the-database/database/vector-and-geography-fields), or generated serializable objects ([data models](./data-and-the-database/models)). You can also use `List`, `Map`, `Record`, and `Set`, strictly typed with the types above. Null safety is supported, and return types follow the same rules as parameters. A `DateTime` is always converted to UTC when passed. +Parameters and return values can be of type `bool`, `int`, `double`, `String`, `UuidValue`, `Duration`, `DateTime`, `ByteData`, `Uri`, `BigInt`, [`dynamic`](./data-and-the-database/models/dynamic-fields), [vector and geography types](./data-and-the-database/database/vector-and-geography-fields), or generated serializable objects ([data models](./data-and-the-database/models)). You can also use `List`, `Map`, `Record`, and `Set`, strictly typed with the types above. Null safety is supported, and return types follow the same rules as parameters. A `DateTime` is always converted to UTC when passed. Parameters are sent by name in the request between app and server, so renaming an endpoint method's parameters breaks older app builds. See [backward compatibility](./endpoints-and-apis/backward-compatibility) before changing a published API. diff --git a/docs/06-concepts/03-data-and-the-database/01-models/01-working-with-models.md b/docs/06-concepts/03-data-and-the-database/01-models/01-working-with-models.md index e2ad9da5..b4a8d304 100644 --- a/docs/06-concepts/03-data-and-the-database/01-models/01-working-with-models.md +++ b/docs/06-concepts/03-data-and-the-database/01-models/01-working-with-models.md @@ -37,7 +37,7 @@ The following types can be used as field types: - **Geography types**: [GeographyPoint](./database/vector-and-geography-fields#geographypoint), [GeographyLineString](./database/vector-and-geography-fields#geographylinestring), [GeographyPolygon](./database/vector-and-geography-fields#geographypolygon), and [GeographyGeometryCollection](./database/vector-and-geography-fields#geographygeometrycollection). - **Your own types**: other serializable [classes](#class), [exceptions](#exception), and [enums](#enum). - **Collections**: [List](https://api.dart.dev/dart-core/List-class.html)s, [Map](https://api.dart.dev/dart-core/Map-class.html)s, and [Set](https://api.dart.dev/dart-core/Set-class.html)s of the supported types, with the type arguments specified. All supported types can also be used inside [Record](https://api.dart.dev/dart-core/Record-class.html)s. -- **dynamic**: holds any serializable value when the type is not known at compile time. +- **[dynamic](./models/dynamic-fields)**: holds any serializable value when the type is not known at compile time. Null safety is supported: append `?` to any type to make the field nullable. Once your classes are generated, you can use them as parameters or return types to [endpoint methods](../endpoints-and-apis). diff --git a/docs/06-concepts/03-data-and-the-database/01-models/03-dynamic-fields.md b/docs/06-concepts/03-data-and-the-database/01-models/03-dynamic-fields.md new file mode 100644 index 00000000..bb5e0544 --- /dev/null +++ b/docs/06-concepts/03-data-and-the-database/01-models/03-dynamic-fields.md @@ -0,0 +1,120 @@ +--- +description: Store values of varying types in Serverpod models using dynamic fields, with serialization and database support for JSON and JSONB columns. +--- + +# Dynamic fields + +Use `dynamic` fields when a model property needs to hold different value types at runtime, for example, plugin configuration, user-defined form responses, or JSON payloads from external APIs. Serverpod serializes the value with its type information so it round-trips correctly between the client, server, and database. + +## Define a dynamic field + +Add `dynamic` as the field type in your model file: + +```yaml +class: FormResponse +table: form_response +fields: + formId: int + ### The submitted value may be a string, number, bool, list, or map. + value: dynamic +``` + +Save the file with `serverpod start` running (or run `serverpod generate`) to update the generated Dart classes. + +### Nullability + +The `dynamic` type is already nullable, so it is written without a `?`. Writing `dynamic?` or `List` is rejected by code generation, since the `?` mark is redundant. + +## Supported values + +A `dynamic` field can hold any value that Serverpod can serialize, including: + +- Primitives such as `bool`, `int`, `double`, and `String`. +- Other supported core types such as `DateTime`, `Duration`, and `UuidValue`. +- Generated serializable models from your project, modules, and shared packages. +- Collections (`List`, `Map`, and `Set`), including mixed-type contents. +- Nested combinations of these values. + +Below is an example with several dynamic and collection fields: + +```yaml +class: DynamicExample +table: dynamic_example +fields: + payload: dynamic + jsonbPayload: dynamic, serializationDataType=jsonb + payloadList: List + payloadMap: Map + payloadSet: Set + payloadMapWithDynamicKeys: Map +``` + +In Dart, assign values directly. In this example, `SimpleData` is a generated model: + +```dart +var object = DynamicExample( + payload: 42, + jsonbPayload: 10.23, + payloadList: [1, 'b', SimpleData(num: 7)], + payloadMap: {'a': 1, 'b': 2, 'c': SimpleData(num: 3)}, + payloadSet: {1, 2, 3, 'd'}, + payloadMapWithDynamicKeys: {'a': 1, 2: SimpleData(num: 1)}, +); +``` + +Values round-trip through `toJson` and `fromJson`, endpoint calls, and database operations. Serverpod includes runtime type metadata in the serialized value. Treat that representation as an implementation detail rather than constructing or editing it directly. + +Dynamic fields also work across project, module, and shared-package model boundaries. A dynamic field on a module or shared model can contain a generated model from the consuming project. Run `serverpod generate` after adding or changing the involved models so each generated protocol has the required type registrations. + +## Use dynamic values in endpoints + +Endpoint parameters and return values can use `dynamic` directly: + +```dart +class UtilityEndpoint extends Endpoint { + Future echo(Session session, dynamic value) async { + return value; + } +} +``` + +The generated client preserves the runtime type when it sends and receives supported values. Collections (`List`, `Map`, and `Set`) with `dynamic` type arguments are also supported. + +## Store dynamic fields in the database + +When the model has a `table` keyword, `dynamic` fields are stored in a `json` column by default. + +To store the value as `jsonb` instead, set `serializationDataType=jsonb` on the field. This format supports [GIN indexing](../database/indexing#gin-indexes): + +```yaml +class: Product +table: product +fields: + name: String + metadata: dynamic, serializationDataType=jsonb +``` + +See [Storing serializable fields as JSONB](../database/tables#storing-serializable-fields-as-jsonb) for class-level and project-level settings. + +After adding or changing `dynamic` fields on a table model, run `serverpod create-migration` and apply the migration. + +## Copy models with dynamic fields + +The generated [copyWith](../models#copywith) method keeps its standard behavior on dynamic fields: omitting the argument preserves the current value, and passing `null` explicitly clears the field: + +```dart +var cleared = object.copyWith(payload: null); +``` + +## Limits + +- **No compile-time type checks.** Cast values in Dart when you know the expected type (`value as String`, `value is SimpleData`). +- **No database schema validation.** The database stores the serialized JSON; invalid shapes are only caught at runtime in Dart. +- **Prefer typed models when the schema is stable.** `dynamic` trades type safety for flexibility. + +## Related + +- [Working with models](../models): model file syntax and supported types. +- [Working with endpoints](../../endpoints-and-apis): endpoint parameters and return values. +- [Database tables](../database/tables): how model fields map to columns, including JSONB storage. +- [Custom serialization](./custom-serialization): registering custom serializable classes. diff --git a/docs/06-concepts/03-data-and-the-database/01-models/03-custom-serialization.md b/docs/06-concepts/03-data-and-the-database/01-models/04-custom-serialization.md similarity index 100% rename from docs/06-concepts/03-data-and-the-database/01-models/03-custom-serialization.md rename to docs/06-concepts/03-data-and-the-database/01-models/04-custom-serialization.md diff --git a/docs/06-concepts/03-data-and-the-database/01-models/04-shared-packages.md b/docs/06-concepts/03-data-and-the-database/01-models/05-shared-packages.md similarity index 100% rename from docs/06-concepts/03-data-and-the-database/01-models/04-shared-packages.md rename to docs/06-concepts/03-data-and-the-database/01-models/05-shared-packages.md diff --git a/docs/06-concepts/03-data-and-the-database/02-database/01-tables.md b/docs/06-concepts/03-data-and-the-database/02-database/01-tables.md index 58d3115b..761b804f 100644 --- a/docs/06-concepts/03-data-and-the-database/02-database/01-tables.md +++ b/docs/06-concepts/03-data-and-the-database/02-database/01-tables.md @@ -58,7 +58,7 @@ All fields are persisted by default and have an implicit `persist` set on each f ## Data representation -A field with a primitive / core Dart type is stored as its respective column type. Complex types, such as another model, a `List`, or a `Map`, are stored as a `json` column in the database by default. Fields of type `dynamic` are stored the same way, and can always hold null. +A field with a primitive / core Dart type is stored as its respective column type. Complex types, such as another model, a `List`, or a `Map`, are stored as a `json` column in the database by default. Fields of type [`dynamic`](../models/dynamic-fields) are stored the same way, and can always hold null. ```yaml class: Company @@ -85,7 +85,7 @@ For a complete guide on how to work with relations, see the [Relations](relation By default, complex types are stored as `json` in the database. You can opt into `jsonb` storage instead using the `serializationDataType` keyword. JSONB is a binary format that supports efficient querying and [GIN indexing](indexing#gin-indexes) for PostgreSQL. :::info -The `serializationDataType` keyword is only valid on serializable field types (models, Lists, Maps). Primitive types like `String` and `int` have their own native database column types and are not affected by this setting. +The `serializationDataType` keyword is only valid on serializable field types (models, Lists, Maps, and `dynamic`). Primitive types like `String` and `int` have their own native database column types and are not affected by this setting. ::: You can set `serializationDataType` at three levels, each overriding the one above it: diff --git a/docs/06-concepts/lookups/model-reference.md b/docs/06-concepts/lookups/model-reference.md index 25c2d893..f5e7ab6a 100644 --- a/docs/06-concepts/lookups/model-reference.md +++ b/docs/06-concepts/lookups/model-reference.md @@ -48,5 +48,6 @@ Every keyword available in a Serverpod model file, and whether it applies to a ` ## Related - [Working with models](../data-and-the-database/models): how each keyword works, with examples. +- [Dynamic fields](../data-and-the-database/models/dynamic-fields): store and transfer values whose type varies at runtime. - [Serializable exceptions](../endpoints-and-apis/error-handling-and-exceptions#serializable-exceptions): define, inherit, throw, and catch application errors. - [Database tables](../data-and-the-database/database/tables): the database-specific keywords (`table`, `relation`, `persist`, and indexing).