How to Use Generated Columns in Laravel Migrations

21 June 2026

How to Use Generated Columns in Laravel Migrations

Learn how to use virtual and stored generated columns in Laravel migrations with virtualAs() and storedAs(), including performance, indexing and database support.

Generated columns, sometimes called virtual columns or computed columns, allow you to define a database column whose value is calculated from other columns in the same row.

They can be a useful tool when building Laravel applications that need consistent derived values, cleaner queries, or better performance when filtering, sorting or indexing computed data.

Laravel supports generated columns in migrations through the virtualAs() and storedAs() column modifiers, making them straightforward to add to your database schema.

What are generated columns?

A generated column is a column whose value is calculated by the database from an expression.

Instead of calculating a value in your Laravel application every time you need it, you can ask the database to keep the calculation close to the data.

For example, an order line item might store:

  • unit_price

  • quantity

You could then create a generated column called total_price that is always calculated as:

unit_price * quantity

This helps keep the value consistent and avoids relying on application code to update it correctly.

Virtual vs stored generated columns

Virtual generated columns

A virtual generated column is not stored on disk with the rest of the row. Instead, the database calculates the value when the column is read.

Virtual columns can be useful when:

  • You want to avoid duplicating data

  • The expression is simple

  • You do not query the generated value heavily

  • You want to keep storage usage lower

The trade-off is that the value may need to be recalculated during reads, which can affect query performance depending on the database engine, expression and query pattern.

Stored generated columns

A stored generated column is calculated when the row is inserted or updated, then saved to disk like a regular column.

Stored columns can be useful when:

  • You need to filter, sort or join using the generated value

  • You want to index the computed value

  • The calculation is expensive to perform repeatedly at read time

  • Query performance matters more than storage size

The trade-off is that stored generated columns use more disk space and can add overhead when rows are inserted or updated.

When should you use generated columns?

Generated columns are a good fit when a value is derived from other columns and should always stay in sync with them.

Common examples include:

  • Calculating an order line total from unit price and quantity

  • Extracting a value from a JSON column for easier querying

  • Normalising a searchable value, such as a lowercased email address

  • Combining fields into a computed display or lookup value

  • Creating an indexable version of a calculated expression

They are less suitable when the value depends on application logic, external services, user-specific context, or non-deterministic values such as the current time.

Creating a generated column in a Laravel migration

Laravel migrations support generated columns using the virtualAs() and storedAs() column modifiers.

Here is an example using an order line item table:

Schema::create('order_line_items', function (Blueprint $table) {
    $table->id();
    $table->foreignIdFor(Order::class)->constrained();
    $table->foreignIdFor(Product::class)->constrained();
    $table->integer('unit_price');
    $table->integer('quantity');

    $table->integer('total_price')
        ->virtualAs('unit_price * quantity');

    $table->timestamps();
});

In this example, total_price is calculated by the database from unit_price and quantity.

Because it is virtual, the value is not stored with the row. It is calculated when the column is selected.

Using a stored generated column with an index

If you need to filter, sort or search by the generated value, a stored generated column is often a better option.

$table->integer('total_price')
    ->storedAs('unit_price * quantity')
    ->index();

This stores the calculated value and adds an index, making it more suitable for queries such as:

OrderLineItem::query()
    ->where('total_price', '>=', 5000)
    ->orderBy('total_price')
    ->get();

Index support varies between database engines, so check the behaviour for your database before relying on it in production. As a general rule, stored generated columns are often the safest choice when the computed value is important for query performance.

Using generated columns with Eloquent

Once the generated column exists in your database schema, Eloquent can read it like any other column returned from the query.

For example, you could cast unit_price and total_price using a custom Money cast:

class OrderLineItem extends Model
{
    protected $casts = [
        'unit_price' => Money::class,
        'total_price' => Money::class,
    ];
}

Then you can render the generated value in a Blade view:

<table>
    <thead>
        <tr>
            <th>Name</th>
            <th>Unit Price</th>
            <th>Quantity</th>
            <th>Total Price</th>
        </tr>
    </thead>

    <tbody>
        @foreach ($items as $item)
            <tr>
                <td>{{ $item->product->name }}</td>
                <td>{{ $item->unit_price }}</td>
                <td>{{ $item->quantity }}</td>
                <td>{{ $item->total_price }}</td>
            </tr>
        @endforeach
    </tbody>
</table>

You do not need to manually set total_price in your Laravel code. The database calculates it from the expression defined in the migration.

Generated columns vs Laravel accessors

Laravel accessors are useful when you only need a calculated value inside your application.

Generated columns are more useful when the database needs to understand the value too.

Use an accessor when:

  • The value is only used for display

  • The calculation depends on application logic

  • You do not need to query, sort or index the value

Use a generated column when:

  • The value should be calculated consistently by the database

  • You need to filter or sort using the computed value

  • You want to index the calculated result

  • Multiple applications or services read from the same database

Things to watch out for

Generated columns are powerful, but they come with a few important constraints.

The expression usually needs to be deterministic and based on values in the same row. Database engines may restrict subqueries, non-deterministic functions, stored functions, or references to data outside the current row.

Support also varies between MySQL, MariaDB, PostgreSQL and SQLite. Before adding a generated column to a production Laravel application, check that your database version supports the type of generated column and indexing strategy you want to use.

Conclusion

Generated columns are a useful way to move simple, repeatable calculations into the database while still working cleanly with Laravel migrations and Eloquent models.

Use virtual generated columns when you want a lightweight computed value that does not need to be stored. Use stored generated columns when you need better query performance, indexing, or a calculated value that will be read frequently.

For Laravel applications with complex queries, JSON fields, reporting screens or calculated order values, generated columns can be a clean way to improve consistency and performance without duplicating logic throughout your codebase.

If you're looking for an experienced developer for your next project, get in touch with Grizzly Pumpkin, we have years of experience working with Laravel, databases, Statamic and more.

Tags: Laravel Laravel Migrations Database Performance Generated Columns MySQL PostgreSQL Backend Development PHP

Ready to start your project?

Let's chat about your ideas and see how we can bring them to life.

Get in touch