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 f4397819..5ce5d731 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/models/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/models/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](./data-and-the-database/models/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 3d62c9db..12ad5240 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 @@ -24,7 +24,7 @@ fields: employees: List ``` -Supported types are [bool](https://api.dart.dev/dart-core/bool-class.html), [int](https://api.dart.dev/dart-core/int-class.html), [double](https://api.dart.dev/dart-core/double-class.html), [String](https://api.dart.dev/dart-core/String-class.html), [Duration](https://api.dart.dev/dart-core/Duration-class.html), [DateTime](https://api.dart.dev/dart-core/DateTime-class.html), [ByteData](https://api.dart.dev/dart-typed_data/ByteData-class.html), [UuidValue](https://pub.dev/documentation/uuid/latest/uuid_value/UuidValue-class.html), [Uri](https://api.dart.dev/dart-core/Uri-class.html), [BigInt](https://api.dart.dev/dart-core/BigInt-class.html), [Vector](./models/vector-and-geography-fields#vector), [HalfVector](./models/vector-and-geography-fields#halfvector), [SparseVector](./models/vector-and-geography-fields#sparsevector), [Bit](./models/vector-and-geography-fields#bit), [GeographyPoint](./models/vector-and-geography-fields#geographypoint), [GeographyLineString](./models/vector-and-geography-fields#geographylinestring), [GeographyPolygon](./models/vector-and-geography-fields#geographypolygon), [GeographyGeometryCollection](./models/vector-and-geography-fields#geographygeometrycollection) and other serializable [classes](#class), [exceptions](#exception) and [enums](#enum). You can also use [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, just make sure to specify the types. All supported types can also be used inside [Record](https://api.dart.dev/dart-core/Record-class.html)s. Null safety is supported. Once your classes are generated, you can use them as parameters or return types to endpoint methods. +Supported types are [bool](https://api.dart.dev/dart-core/bool-class.html), [int](https://api.dart.dev/dart-core/int-class.html), [double](https://api.dart.dev/dart-core/double-class.html), [String](https://api.dart.dev/dart-core/String-class.html), [Duration](https://api.dart.dev/dart-core/Duration-class.html), [DateTime](https://api.dart.dev/dart-core/DateTime-class.html), [ByteData](https://api.dart.dev/dart-typed_data/ByteData-class.html), [UuidValue](https://pub.dev/documentation/uuid/latest/uuid_value/UuidValue-class.html), [Uri](https://api.dart.dev/dart-core/Uri-class.html), [BigInt](https://api.dart.dev/dart-core/BigInt-class.html), [dynamic](./models/dynamic-fields), [Vector](./models/vector-and-geography-fields#vector), [HalfVector](./models/vector-and-geography-fields#halfvector), [SparseVector](./models/vector-and-geography-fields#sparsevector), [Bit](./models/vector-and-geography-fields#bit), [GeographyPoint](./models/vector-and-geography-fields#geographypoint), [GeographyLineString](./models/vector-and-geography-fields#geographylinestring), [GeographyPolygon](./models/vector-and-geography-fields#geographypolygon), [GeographyGeometryCollection](./models/vector-and-geography-fields#geographygeometrycollection) and other serializable [classes](#class), [exceptions](#exception) and [enums](#enum). You can also use [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, just make sure to specify the types. All supported types can also be used inside [Record](https://api.dart.dev/dart-core/Record-class.html)s. Null safety is supported. Once your classes are generated, you can use them as parameters or return types to endpoint methods. ### Required fields 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..4df899e4 --- /dev/null +++ b/docs/06-concepts/03-data-and-the-database/01-models/03-dynamic-fields.md @@ -0,0 +1,126 @@ +--- +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 +``` + +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 invalid Dart syntax and will be rejected by the code generation. + +## 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 + payloadList: List + payloadMap: Map + payloadSet: Set + payloadMapWithDynamicKeys: Map +``` + +In Dart, assign values directly: + +```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. Endpoints also support using `dynamic` on collections, maps and sets. + +## 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 + +Generated `copyWith` methods distinguish between omitting a dynamic field and explicitly passing `null`. Omitting the argument preserves its current value: + +```dart +var updated = object.copyWith(id: 2); +``` + +Passing `null` 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-vector-and-geography-fields.md b/docs/06-concepts/03-data-and-the-database/01-models/04-vector-and-geography-fields.md similarity index 100% rename from docs/06-concepts/03-data-and-the-database/01-models/03-vector-and-geography-fields.md rename to docs/06-concepts/03-data-and-the-database/01-models/04-vector-and-geography-fields.md diff --git a/docs/06-concepts/03-data-and-the-database/01-models/04-custom-serialization.md b/docs/06-concepts/03-data-and-the-database/01-models/05-custom-serialization.md similarity index 100% rename from docs/06-concepts/03-data-and-the-database/01-models/04-custom-serialization.md rename to docs/06-concepts/03-data-and-the-database/01-models/05-custom-serialization.md diff --git a/docs/06-concepts/03-data-and-the-database/01-models/05-shared-packages.md b/docs/06-concepts/03-data-and-the-database/01-models/06-shared-packages.md similarity index 100% rename from docs/06-concepts/03-data-and-the-database/01-models/05-shared-packages.md rename to docs/06-concepts/03-data-and-the-database/01-models/06-shared-packages.md diff --git a/docs/06-concepts/03-data-and-the-database/01-models/06-backward-compatibility.md b/docs/06-concepts/03-data-and-the-database/01-models/07-backward-compatibility.md similarity index 100% rename from docs/06-concepts/03-data-and-the-database/01-models/06-backward-compatibility.md rename to docs/06-concepts/03-data-and-the-database/01-models/07-backward-compatibility.md diff --git a/docs/06-concepts/03-data-and-the-database/02-database/02-tables.md b/docs/06-concepts/03-data-and-the-database/02-database/02-tables.md index a359fb7d..42494636 100644 --- a/docs/06-concepts/03-data-and-the-database/02-database/02-tables.md +++ b/docs/06-concepts/03-data-and-the-database/02-database/02-tables.md @@ -54,7 +54,7 @@ All fields are persisted by default and have an implicit `persist` set on each f ## Data representation -Storing a field with a primitive / core dart type will be handled as its respective type. However, if you use a complex type, such as another model, a `List`, or a `Map`, these will be stored as a `json` column in the database by default. +Storing a field with a primitive / core dart type will be handled as its respective type. However, if you use a complex type, such as another model, a `List`, a `Map`, or a [dynamic](../models/dynamic-fields) value, these will be stored as a `json` column in the database by default. ```yaml class: Company @@ -81,7 +81,7 @@ For a complete guide on how to work with relations see the [relation section](re 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 8952268c..19d5758b 100644 --- a/docs/06-concepts/lookups/model-reference.md +++ b/docs/06-concepts/lookups/model-reference.md @@ -43,4 +43,5 @@ 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. - [Database tables](../data-and-the-database/database/tables): the database-specific keywords (`table`, `relation`, `persist`, and indexing).