Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Calculated Field Laravel Package

codebykyle/calculated-field

A Laravel package for defining calculated fields on Eloquent models. Declare attributes that derive their values from other model properties, and the package keeps them updated automatically when the model is saved—no manual recalculation or custom observers required.

View on GitHub
Deep Wiki
Context7

Getting Started

This package adds a Nova field whose value is computed on the server from the underlying model rather than read from a database column. It is a thin base class: you subclass it, implement calculate(), and drop it into a resource's fields().

1. Install (mind the version constraint). The package has been archived since 2019 and targets Nova 2. If you're on a newer Nova, you'll need to fork or accept that it likely won't resolve:

composer require codebykyle/calculated-field

2. Look here first. Open src/CalculatedField.php in the vendor directory. The whole package is one or two files — the base field overrides Nova's resolve() to call your calculate() method instead of reading $model->{$attribute}. Understanding that one override is the entire mental model.

3. Create a field class. The contract is:

namespace App\Nova\Fields;

use Codebykyle\CalculatedField\CalculatedField;
use Laravel\Nova\Http\Requests\NovaRequest;

class FullName extends CalculatedField
{
    public function calculate(NovaRequest $request, $model)
    {
        return trim("{$model->first_name} {$model->last_name}");
    }
}

4. Register it. The second argument to make() is only a label anchor — there is no real column, so pass anything meaningful for the UI:

use App\Nova\Fields\FullName;

public function fields(NovaRequest $request)
{
    return [
        ID::make()->sortable(),
        Text::make('First name'),
        Text::make('Last name'),
        FullName::make('Full Name')->onlyOnIndex()->onlyOnDetail(),
    ];
}

First use case: a full-name column derived from first_name + last_name. It's the lowest-risk way to validate the install end-to-end — no relations, no formatting, no auth — before you start using it for aggregates or status badges.

Implementation Patterns

Display-only derivation (the common case). Concatenation, formatting, arithmetic, and status derivation all belong here. Prefer returning primitives and letting Nova's display layer format them:

class OrderTotal extends CalculatedField
{
    public function calculate(NovaRequest $request, $model)
    {
        return number_format($model->lineItems->sum('total'), 2);
    }
}

Always constrain placement. Because there is no backing column, a CalculatedField on a create/update form is a lie — Nova has nothing to fill and nothing to persist. Use ->onlyOnIndex() and/or ->onlyOnDetail() consistently. Treat writing to it as a bug, not a feature.

Badges and conditional HTML. Return an HTML string and opt into rendering, which is often cleaner than composing a Badge field:

public function calculate(NovaRequest $request, $model)
{
    return $model->isOverdue()
        ? '<span class="text-red-500 font-bold">Overdue</span>'
        : '<span class="text-green-500">On time</span>';
}
OverdueStatus::make('Status')->asHtml()->onlyOnIndex(),

Reusable fields with parameters. Push variation into constructor args or fluent setters so one class serves many resources:

class RelationSum extends CalculatedField
{
    protected $relation;
    protected $column;

    public function relation(string $relation, string $column = 'amount'): static
    {
        $this->relation = $relation;
        $this->column = $column;
        return $this;
    }

    public function calculate(NovaRequest $request, $model)
    {
        return $model->{$this->relation}->sum($this->column);
    }
}

Then RelationSum::make('Revenue')->relation('orders', 'total').

Request-aware visibility. Use the injected NovaRequest to gate values by role rather than hiding the whole field:

public function calculate(NovaRequest $request, $model)
{
    return $request->user()->isAdmin() ? $model->margin() : '—';
}

Testing. You don't need an HTTP round-trip. Instantiate the field, build a model (unsaved is usually fine), and call calculate() with a mocked NovaRequest. Anything you can't test that way is a sign the logic belongs on the model, not in the field.

Integration tip: keep the calculation itself on the model ($model->margin(), $model->fullName()) and let the field be a two-line adapter. Nova's own CSV exports and API responses can then reuse the same logic, and you get testability for free.

Gotchas and Tips

  • Archived and pinned to Nova 2. Last release October 2019. Nova 3 and later changed field resolution internals (resolve/resolveUsing ordering, request types). Before adopting, copy the base class into app/Nova/Fields/ and maintain it yourself — it's a handful of lines and removes a dead dependency.
  • No sorting, no filtering. ->sortable() and ->filterable() will attempt to reference a nonexistent column and fail at the SQL layer. If users need to sort by the computed value, store it (accessor + generated column) and use a normal Text/Number field.
  • N+1 queries are the default failure mode. calculate() runs once per row on index views. Any $model->relation access lazy-loads a query per row. Either eager-load in the resource's indexQuery(), or do a single aggregate query inside calculate() using $model->relation()->sum(...) — still one query per row, but cheaper than hydrating models.
  • resolveUsing() is shadowed. The base class takes over resolve(), so chaining ->resolveUsing(...) on the field may be ignored or double-applied depending on Nova version. Put all logic in calculate().
  • Raw values, not formatted values. Nova's Number, Currency, and DateTime fields apply presentation; a CalculatedField returns whatever you hand it. Carbon instances often render as 2024-01-01T00:00:00.000000Z unless you format. Return strings for anything non-trivial.
  • Silent failures look like empty columns. An exception inside calculate() surfaces as a blank cell with little signal. Temporarily dd() or logger() the return value; on index pages, check storage/logs/laravel.log rather than the browser.
  • No form persistence, no validation. A CalculatedField placed on a form will silently ignore submitted data and, if you attach rules, may produce confusing validation results. Use onlyOnIndex/onlyOnDetail.
  • Memoize expensive calculations. Set a transient attribute on the model ($model->setAttribute('_cf_total', $v)) or guard with a static/instance cache so repeated resolution of the same field within a request doesn't recompute.
  • Escape hatch for newer Nova. If you're on Nova 3+, you likely don't need this package at all: a closure as the attribute name (Text::make('Full Name', fn () => $this->first_name.' '.$this->last_name)) or ->resolveUsing() on a plain Text field gives the same behavior with a supported dependency. Reserve the package for legacy Nova 2 codebases where rewriting isn't warranted.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource