Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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).

Expand Down
Original file line number Diff line number Diff line change
@@ -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<dynamic?>` 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<dynamic>
payloadMap: Map<String, dynamic>
payloadSet: Set<dynamic>
payloadMapWithDynamicKeys: Map<dynamic, dynamic>
```

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<dynamic> 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.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:
Expand Down
1 change: 1 addition & 0 deletions docs/06-concepts/lookups/model-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Loading