Skip to main content

Model Attributes

Ormed models support attribute metadata that affects:

Prerequisites

What You’ll Learn

  • How mass-assignment rules (fillable, guarded) are enforced
  • How visibility (hidden, visible) affects serialization
  • How attribute metadata connects to casting behavior

Step Outcome

By the end of this page, you should be able to:

  • Safely accept trusted/untrusted input with fill vs forceFill

  • Control serialized output fields for API/web responses

  • Use accessors/mutators without breaking tracked model behavior

  • Mass assignment (fill, fillIfAbsent, forceFill)

  • Serialization (toArray, toJson)

  • Casting (see Models → Casting)

Tracked models

These APIs are designed for tracked models (the generated $Model types) and instances returned by queries. Plain user-defined model instances are immutable and typically not used for mass assignment.

Define metadata

Attribute metadata can be declared at the model level, and optionally overridden per-field.

(
table: 'accounts',
fillable: ['email', 'name'],
guarded: ['is_admin'],
hidden: ['password_hash'],
visible: ['password_hash'],
appends: ['display_name'],
)
class Account extends Model<Account> {
const Account({
required this.id,
required this.email,
required this.passwordHash,
this.name,
this.isAdmin = false,
});

(isPrimaryKey: true, autoIncrement: true)
final int id;

final String email;

(columnName: 'password_hash')
final String passwordHash;

final String? name;

(columnName: 'is_admin', guarded: true)
final bool isAdmin;

Mass assignment

Mass assignment takes a Map<String, Object?> keyed by column names and applies fillable/guarded rules.

void massAssignmentExample() {
final account = $Account(id: 1, email: 'a@example.com', passwordHash: 'hash');

// Only fillable columns are applied; guarded columns are discarded.
account.fill({'email': 'new@example.com', 'is_admin': true});

// Enable strict mode to throw instead of silently discarding.
try {
account.fill({'is_admin': true}, strict: true);
} on MassAssignmentException {
// Handle mass assignment failures.
}

// Bypass guards for trusted code paths.
account.forceFill({'is_admin': true});
}

Notes:

  • fill(...) defaults to strict: false (guarded keys are discarded).
  • Set strict: true to throw a MassAssignmentException when a guarded key is present.
  • Use forceFill(...) only for trusted/internal flows.
  • fill / fillIfAbsent / forceFill accept tracked models, DTOs, and partials in addition to maps.
  • Map keys can be Dart field names or column names (they are normalized).

Default to fill(...) for request payloads, and reserve forceFill(...) for trusted internal flows.

Serialization (hidden / visible)

toArray() / toJson() honor hidden by default. To include hidden values you must set includeHidden: true, and the column must also be listed in visible.

Map<String, Object?> serializationExample() {
final account = $Account(id: 1, email: 'a@example.com', passwordHash: 'hash');

// Hidden columns are excluded.
final safe = account.toArray();

// Hidden columns can be included, but only when they're explicitly visible.
final internal = account.toArray(includeHidden: true);

return {'safe': safe, 'internal': internal};
}

Keep sensitive values (password, secrets, tokens) hidden by default, even for internal APIs.

Accessors and mutators

Accessors let you transform attribute reads, while mutators normalize values when they are assigned. Define them as static members on the model, similar to query scopes.

  (attribute: 'display_name')
static String displayName(Account model, Object? _) =>
model.name ?? model.email;

(attribute: 'email')
static String normalizeEmail(Account model, String? value) =>
value?.trim().toLowerCase() ?? model.email;
Static-only accessors

Accessors and mutators must be declared as static. The generator exposes them on tracked models via extensions, so you can read tracked.displayName and call tracked.normalizeEmail('User@Example.com'). Accessors apply when using getAttribute(...) and serialization (toArray, toJson). Mutators apply when using setAttribute(...), fill(...), or tracked setters. Use getRawAttribute(...) / setRawAttribute(...) to bypass accessors/mutators.

Appends (computed attributes)

Use appends to include computed attributes in toArray() / toJson() output.

Map<String, Object?> appendsExample() {
final account = $Account(
id: 2,
email: 'User@Example.com',
passwordHash: 'hash',
name: 'User Name',
);

return account.toArray();
}

Verify Attribute Rules

Add tests for:

  1. Guarded key rejection/ignore behavior.
  2. Hidden/visible serialization output.
  3. Accessor/mutator behavior on tracked instances.

Read This Next