When building advanced Laravel applications, you often need to go beyond the default features. Two powerful tools for improving model behavior and query flexibility in Laravel are:

  • Custom Eloquent Casts – for transforming how data is stored and retrieved on models

Query Macros – for extending the Eloquent query builder with reusable logic

What Are Custom Eloquent Casts?

They define how a model attribute should be transformed when accessed or saved. This is especially helpful for non-primitive types or custom logic.

Example: Custom Cast for JSON Settings

Let’s say you store user settings as a JSON column in the database but want to work with it as an object.

Step 1: Create a Custom Cast Class

bash

php artisan make:cast UserSettingsCast

Step 2: Implement the Cast Logic

PHP

namespace App\Casts;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
class UserSettingsCast implements CastsAttributes
{ public function get($model, string $key, $value, array $attributes) { return json_decode($value, true); } public function set($model, string $key, $value, array $attributes) { return json_encode($value); }
}

Step 3: Use the Cast in a Model

PHP

class User extends Model
{ protected $casts = [ 'settings' => \App\Casts\UserSettingsCast::class, ];
}

Now, $user->settings will return an array instead of a raw JSON string, and setting it works the same way.

PHP

$user->settings = ['dark_mode' => true, 'language' => 'en'];
$user->save();

What Are Query Macros?

Query Macros allow Laravel developers to extend Laravel’s query builder with custom methods, making your code DRY (Don’t Repeat Yourself) and easier to reuse across your app.

Example: Global Scope for Active Records

Let’s say you frequently filter records that have a status = active.

Step 1: Register a Macro

You can add this in a service provider, usually AppServiceProvider.

PHP

use Illuminate\Database\Query\Builder;
public function boot()
{ Builder::macro('active', function () { return $this->where('status', 'active'); });
}

If you’re using Eloquent Builder (Illuminate\Database\Eloquent\Builder), adjust accordingly.

Step 2: Use the Macro in Queries

PHP

$activeUsers = DB::table('users')->active()->get();

Or, if using Eloquent:

PHP

use Illuminate\Database\Eloquent\Builder;
Builder::macro('active', function () { return $this->where('status', 'active');
});

And then:

PHP

User::query()->active()->get();

When to Use Casts vs. Macros

Use CaseUse
Transform model attributesCustom Cast
Add reusable query logicQuery Macro
Reformat JSON, DateTime, objectsCustom Cast
Filter by status, scope, complex joinsQuery Macro

Best Practices

  • Keep custom casts simple and pure – avoid side effects.
  • Use descriptive macro names for readability (->active(), ->filterByYear()).
  • Place macros in dedicated service providers for organization.

Custom casts can also implement CastsInboundAttributes or Castable for even more flexibility.