Skip to the content
OrielDocs
0.1.0-alpha.10GitHub

Models

Eloquent: mutators and casting

Accessors, mutators and casts change attribute values as your models read and write them: a JSON column comes back as an object, a date as a Date, and a name in the case you chose.

Introduction#

A cast turns a column's stored value into the type your code works with, and back when the model is saved. An accessor is a method that computes an attribute when you read it, and a mutator one that sets it when you write it.

Accessors and mutators#

Defining an accessor#

An accessor is a method named get, the attribute's name in StudlyCase, and Attribute. It receives the stored value, and returns what reading the attribute gives:

app/Models/User.ts
import { Model } from '@orieljs/framework'export class User extends Model {  /** Get the user's first name. */  getFirstNameAttribute(value: unknown): string {    const name = String(value ?? '')    return name.charAt(0).toUpperCase() + name.slice(1)  }}

user.getAttribute('first_name') then calls it, and so does reading user.first_name. model:types keeps the column's type for it; an accessor with no column, below, is typed from its return type.

An accessor can also make an attribute that has no column, computed from others:

export class User extends Model {  getFullNameAttribute(): string {    return `${String(this.getAttribute('first_name'))} ${String(this.getAttribute('last_name'))}`  }}

To have it in the model's toArray() and JSON, add it to appends (see Appending values to JSON).

Defining a mutator#

A mutator is a method named set, the attribute's name in StudlyCase, and Attribute. It receives the value being set, and stores what it decides in this.attributes:

app/Models/User.ts
import { Model } from '@orieljs/framework'export class User extends Model {  /** Interact with the user's first name. */  setFirstNameAttribute(value: string): void {    this.attributes.first_name = value.toLowerCase()  }}

Every write goes through it: user.first_name = 'Sally', fill(), create() and update().

Attribute casting#

casts maps attributes to the type they are read as:

app/Models/User.ts
import { Model, type CastDefinition } from '@orieljs/framework'export class User extends Model {  protected override casts: Readonly<Record<string, CastDefinition>> = {    is_admin: 'boolean',    options: 'json',    email_verified_at: 'datetime',  }}

A model with model:types types each attribute from its cast: is_admin as a boolean, email_verified_at as a Date | null.

CastRead asStored as
int, integerA numberThe number
float, double, realA numberThe number
stringA stringThe string
bool, booleantrue or false1 or 0, or a boolean on PostgreSQL
array, json, objectThe parsed JSONJSON text
collectionA collection of the parsed JSON listJSON text
dateA Date at midnight UTC'2026-03-04'
datetimeA Date'2026-01-02 03:04:05', in UTC
immutable_date, immutable_datetimeA Date, as date and datetimeThe same
timestampA Unix time in secondsThe date and time
decimal:2A string with that many decimals, '9.50'The number
hashedThe hashA hash of the value, made when the model is saved

A built-in cast of null stays null; a custom cast's get() receives it. The incrementing key is always cast as an integer, and created_at and updated_at (and deleted_at with soft deletes) as datetimes.

A cast string Oriel doesn't know, such as Laravel's datetime:Y-m-d with a format, throws an InvalidCastError when the attribute is read or written: "Call to undefined cast [datetime:Y-m-d] on column [launched_at] in model [Product].".

Array and JSON casting#

A json attribute is read as the parsed value. Changing it in place counts as a change, so save() writes it:

user.options.theme = 'dark'await user.save()

Date casting#

A date is stored in UTC, as '2026-01-02 03:04:05', and read as a Date. In toArray() and JSON, dates are written as Laravel writes them: '2026-01-02T03:04:05.000000Z'.

Enum casting#

A TypeScript enum casts an attribute to its values:

app/Models/Server.ts
import { Model, type CastDefinition } from '@orieljs/framework'export enum ServerStatus {  Provisioned = 'provisioned',  Ready = 'ready',}export class Server extends Model {  protected override casts: Readonly<Record<string, CastDefinition>> = { status: ServerStatus }}

Setting a value that isn't one of the enum's throws: ""nope" is not a valid backing value for the cast of [status] on [Server].".

Hashed casting#

The hashed cast hashes a value when the model is saved, with your hashing driver, so you can set a plain password:

export class User extends Model {  protected override casts: Readonly<Record<string, CastDefinition>> = { password: 'hashed' }}

Until save(), reading the attribute gives what you set. A value that is already a hash made with your current settings is kept as it is; one made with other settings is refused.

Query time casting#

withCasts() adds casts for one query, such as for a column a raw select adds:

const users = await User.query()  .select('users.*')  .selectRaw('(select max(created_at) from posts where posts.author_id = users.id) as last_posted_at')  .withCasts({ last_posted_at: 'datetime' })  .get()

Custom casts#

A custom cast is a class with a get() method, which turns the stored value into what the model gives, and a set() method, which turns a value back into what is stored. make:cast writes one in app/Casts:

Terminal
node oriel make:cast Money
app/Casts/Money.ts
import type { CastsAttributes } from '@orieljs/framework'/** A price kept in cents, read in units: 1234 is 12.34. */export class Money implements CastsAttributes<number, number> {  get(_model: object, _key: string, value: unknown): number {    return Number(value) / 100  }  set(_model: object, _key: string, value: number): unknown {    return Math.round(value * 100)  }}

Name the class in casts, or give an instance:

app/Models/Product.ts
import { Model, type CastDefinition } from '@orieljs/framework'import { Money } from '../Casts/Money'export class Product extends Model {  protected override casts: Readonly<Record<string, CastDefinition>> = { price: Money }}

Both methods also receive the model, the attribute's name and all its stored attributes.

Serialization#

Hiding attributes from JSON#

hidden lists the attributes left out of the model's toArray() and JSON, such as a password, and visible lists the only ones left in:

export class User extends Model {  protected override hidden = ['password', 'remember_token']}

makeVisible() and makeHidden() change the lists for one model: user.makeVisible('password').toArray().

Appending values to JSON#

An accessor with no column isn't in toArray() and JSON unless you add it to appends:

export class User extends Model {  protected override appends = ['is_admin']  getIsAdminAttribute(): boolean {    return this.getAttribute('role') === 'admin'  }}

append() adds one for one model: user.append('is_admin').toArray().

Privacy policy ·

esc