rimu/formatted-number
A PHP package from rimu for formatting numbers into readable, presentation-ready strings, with support for custom separators, decimals, and locale-style output. Useful for displaying prices, counts, and other numeric values consistently in Laravel or plain PHP applications.
Verify the package resolves before you write any resource code. Despite the description, rimu/formatted-number is a Laravel Nova field, not a general-purpose formatting library. Run the cheap check first:
composer require rimu/formatted-number --dry-run
If it fails on the laravel/nova or php constraint, that's the best outcome of the whole evaluation — loud, cheap, and immediate. The last release was 2019-10-25, so expect a ^2.0/^3.0 Nova pin and PHP 7.x. On Nova 4/5 (Inertia + Vue 3 + Vite), resolution will fail.
Where to look, in order:
composer.json — the constraint check above.src/ — the field class. It's one class extending Laravel\Nova\Fields\Field with a handful of chainable setters. Read it end to end.resources/js/components/ and dist/ — check whether the Vue components extend Nova's legacy FormField/DetailField mixins (Nova ≤3). That single fact decides feasibility.tests/ — expect none.There is no service provider, no config file, and no facade, despite what the package blurb claims. If your scoping docs reference FormattedNumber::currency(...) or a Blade helper, delete that section — it doesn't exist.
First use case worth wiring: a read-only index/detail column on an operational resource showing large magnitudes — ARR, quota, row counts, or cents-stored-as-integers that staff read as currency. Something like:
use Rimu\FormattedNumber\FormattedNumber; // verify actual namespace
public function fields(Request $request)
{
return [
ID::make()->sortable(),
Text::make('Account', 'name'),
FormattedNumber::make('ARR', 'arr')
->decimals(2)
->thousandsSeparator(',')
->prefix('$')
->onlyOnIndex(),
];
}
The critical property: the underlying arr column stays numeric, stays sortable if the field delegates to the DB column, and formatting exists only in the render layer.
Write the in-house baseline first. Before you install anything, benchmark against thirty lines:
namespace App\Nova\Fields;
use Laravel\Nova\Fields\Number;
class FormattedNumber extends Number
{
public function currency(string $symbol = '$', int $decimals = 2): static
{
return $this->displayUsing(
fn ($value) => is_null($value) ? null : $symbol . number_format((float) $value, $decimals)
);
}
}
Zero dependencies, no upgrade exposure, covers essentially the entire stated value. If your requirement survives that comparison, you have a concrete gap worth scoping.
Display-only, enforced structurally. Scope the field to onlyOnIndex() and onlyOnDetail() so it never appears in a create/update form. That eliminates the round-trip precision-corruption risk — the failure mode where a formatted string gets re-submitted and silently writes back a rounded or locale-mangled value. If you need formatting on the detail view and editing, use two fields on one column:
Number::make('ARR', 'arr')
->onlyOnForms()
->step(0.01),
FormattedNumber::make('ARR', 'arr')
->currency('$', 2)
->onlyOnIndex()
->onlyOnDetail(),
Zero write path through the formatter.
Centralize format rules in one place. The real day-to-day payoff isn't the field class — it's not having number_format($v, 2) closures scattered across forty resources with inconsistent decimals. Define a map and reference it:
// config/nova-formats.php
return [
'currency' => ['prefix' => '$', 'decimals' => 2, 'separator' => ','],
'percent' => ['suffix' => '%', 'decimals' => 1, 'separator' => ','],
'count' => ['decimals' => 0, 'separator' => ','],
];
Then a FormatsNumbers trait on your base resource applies them. This is the layer that delivers consistency across dashboards — and it survives a Nova upgrade regardless of which field class sits underneath.
Scale stored-as-integer values at the resource boundary, not in the field. Cents and basis points are the common case:
FormattedNumber::make('Conversion', 'conversion_bps')
->displayUsing(fn ($v) => number_format($v / 100, 1) . '%');
Note that once you compose with displayUsing(), you're already outside the package's API and back in Nova's native hooks — a strong signal the package isn't earning its dependency.
Round-trip test on a real resource. Put the package field on one resource next to a native Number + displayUsing() equivalent. Render index, detail, edit. Then:
1234567.891.If the stored value changed, you found the silent failure before production did. Ten minutes, highest-value test in the evaluation.
If you adopt, vendor it. Don't composer require into production. Copy the class and component into app/Nova/Fields/ with an attribution header, add PHP tests around grouping/decimals, and record a named owner in team docs. That converts an unmaintained external dependency into first-party code you can reason about during the next Nova upgrade.
The documentation is aspirational, not descriptive. The blurb describes locale-aware formatting, currency support, a fluent API, and Blade integration. The source is a Nova field with a compiled Vue component. Treat composer.json and the field class as the only source of truth; re-verify anything you scoped from the README.
Sorting and search break, and it won't be obvious. Returning a formatted string from displayUsing() is fine for display but routinely breaks Nova's numeric sorting and search. Verify per-resource: click the header, confirm numeric rather than lexical order. $1.2M sorts before $900K as a string. Keep the field display-only and let a separate sortable Number handle the header if you need sortable columns.
The laravel/nova constraint is your early-warning system. Run --dry-run first. A resolution failure is the best outcome: loud, cheap, immediate. Never plan around a package you haven't proven resolves.
Nova 4/5 will almost certainly break the front end. Nova 4 moved to Inertia + Vue 3 and Vite. A 2019 compiled bundle built against legacy mixins may not register at all, or may register and throw in-browser — breaking create/update forms for staff, not just your one column. A blank field in modern Nova is this, not your configuration.
Locale drift between server and client is a real failure mode. If formatting happens in both the PHP class and the Vue component with different settings, index, detail, and form views can show different numbers for the same record. Users notice, and it destroys trust faster than showing raw 1234567.891 ever would. Pick one rendering layer and enforce it.
There is no upstream to escalate to. Two stars, zero dependents, no release in six years means no issue triage, no Stack Overflow corpus, no compatibility work for anything newer than the Nova it shipped against. Every bug becomes yours. Budget explicitly, or don't adopt.
Ramp-up to use is an hour; ramp-up to own is the real cost. The code is one PHP class and one Vue component. Owning it means understanding Nova's field lifecycle, asset registration pipeline, and Vue component contract well enough to fix it during an upgrade — for code you didn't write. For a thirty-line formatter, "fork and own" and "write it yourself" are the same amount of work, and the second has no attribution obligations and no legacy constraints.
The blast radius is the one genuinely favorable factor. If this breaks, internal staff see an unformatted or blank number in an admin panel. It doesn't touch APIs, exports, customer-facing output, or transactional data — provided display-only is enforced. The moment formatting leaks into a form's write path, that containment disappears. Write the boundary down: formatting is display-only and does not apply to exports or API payloads. That's the decision worth recording.
How can I help you explore Laravel packages today?