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

Count Laravel Package

saumini/count

A lightweight Laravel package for efficient Eloquent counting. Get accurate totals for models and relationships without loading full collections, making it useful for dashboards, list cards, and search results where performance and simple code matter.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Reconcile the name before you composer require. The repo is nsaumini/nova-field-count, the package is saumini/count, and the description text advertises generic counting helpers for "controllers, views, and APIs." All three disagree. Confirm the exact Packagist vendor/name and publisher on packagist.org against the GitHub repo before installing — that mismatch is the classic shape of a typo-squat or dependency-confusion mistake. Then check the declared constraint:

composer show saumini/count        # after require, or inspect composer.json on GitHub

2. Read src/ before you read the README. There is essentially one field class and one Vue component. Two questions decide everything else:

  • Does resolve()/resolveUsing() return a {relation}_count attribute that you must load via withCount, or does it fire a query per row? (The former is the whole point; the latter is an N+1 you're installing voluntarily.)
  • Which Nova major is declared — ^4.0, ^5.0, both? The installed Nova major is the real constraint here, not Laravel or PHP. Nova is paid and closed-source; a field compiled against Nova 4's Vue 3 / asset pipeline may silently fail to register on Nova 5. Verify against your actual composer.lock.

3. Install and wire it up.

composer require saumini/count

Nova field packages typically register their JS from the package's service provider (Nova::script('count-field', __DIR__.'/../dist/js/field.js')) via auto-discovery — so no npm/Vite step in your host app if dist/ is committed. Check that it is; if it isn't, you've just inherited a front-end build chain for one integer, and you should stop and vendor instead.

4. First use case — one resource, index view. Start with something read-only and low-stakes: a Customer resource showing how many Tickets are open.

// app/Nova/Customer.php
public static function indexQuery(NovaRequest $request, $query)
{
    return $query->withCount([
        'tickets as open_tickets_count' => fn ($q) => $q->whereNull('closed_at'),
    ]);
}

public function fields(NovaRequest $request)
{
    return [
        ID::make()->sortable(),
        Text::make('Name'),
        Count::make('Open Tickets', 'open_tickets_count'), // confirm exact API in src/
    ];
}

Deploy that on staging, turn on query logging, and confirm the number of queries is constant as you page through 200 rows — not linear.


Implementation Patterns

Alias your counts; don't rely on the default attribute

withCount('tickets') produces tickets_count. As soon as you need two variants of the same relation — all tickets vs. open tickets — you need aliases. Aliasing is also what keeps the field honest and debuggable:

$query->withCount([
    'tickets as tickets_count',
    'tickets as open_tickets_count' => fn ($q) => $q->whereNull('closed_at'),
    'invoices as overdue_invoices_count' => fn ($q) => $q->where('due_at', '<', now()),
]);

Map each alias to exactly one field. Never let two fields share an attribute — that's how you get a stale or wrong number that still renders confidently.

Index and detail are separate code paths

indexQuery doesn't run on the detail page. In Nova 4+ add the same eager count to detailQuery; on Nova 3 you'll need resolveUsing with a guarded query, or a small accessor. Do it explicitly rather than assuming the field works identically on both screens — the most common report is "the count shows 0 on the detail page."

public static function detailQuery(NovaRequest $request, $query)
{
    return $query->withCount('tickets');
}

Prefer the zero-dependency equivalent when the field adds nothing

If all you need is a plain integer on the index, withCount plus a core Number field already does the job:

Number::make('Open Tickets', 'open_tickets_count')->sortable(),

Adopt the package when you want its specific rendering or when you'd otherwise sprinkle one-off accessors across resources. The build-vs-buy calculus here is genuinely symmetric: ~100 lines of vendored field versus ~20–40 lines of in-house NovaField or Text::make()->displayUsing(). Don't accept lifecycle risk on a component you could own in an afternoon unless the time saved buys something else this sprint.

Vendor it rather than depending on it

Given 28 stars, one maintainer, and zero dependents, the sane default is pin-and-copy:

cp -r vendor/saumini/count/src app/Nova/Fields/Count
cp -r vendor/saumini/count/dist app/Nova/Fields/Count/dist

Adjust the namespace, keep the asset registration in your own NovaServiceProvider. You now own the code, the Vue component, and the upgrade surface — and you can extend it (drill-down links, conditional coloring) without forking a repo nobody is watching.

Link the count through to the filtered relation

The highest-value pattern for operators is a count that's clickable:

Text::make('Open Tickets', 'open_tickets_count')
    ->displayUsing(fn ($value) => $value === null ? '—' : (string) $value)
    ->asHtml(), // or wrap in a link to the related index with a filter

Check whether the package's Vue component already emits an index link; if not, this is the single extension most worth adding when you vendor.

Test the resolution, not the browser

The field is presentation, but its input is a query shape. Write a feature test asserting the attribute exists on the index payload:

$this->actingAs($user)->get('/nova-api/customers')
    ->assertJsonPath('resources.0.open_tickets_count', 3);

That catches the "silently renders 0 because the attribute name is wrong" failure, which is the only bug this package can realistically produce and also the worst one.


Gotchas and Tips

Silent zero is worse than a crash

A missing or misnamed {relation}_count attribute renders as 0 or null, not an error. An operator reads "this account has zero open tickets" when it has twelve. Before shipping, confirm the behavior on a record that genuinely has zero and one that has several — if both render 0, you have an attribute-name bug, not empty data. Consider ->displayUsing(fn ($v) => $v === null ? '—' : $v) so "not loaded" is visually distinct from "genuinely zero." Never let the field fall back to a live query per row; you'll never notice it in dev with 10 records and you'll notice it in prod with 500.

Sorting is where this quietly breaks

->sortable() on a withCount alias requires Nova to translate the click into an ORDER BY on the count — which means either a correlated subquery or, worse, an in-PHP sort of one page. On a 200-row index against a multi-million-row tickets table, that's a visible page-load cost on a screen operators live in. Test sorting explicitly; if it's slow, drop ->sortable() and accept a fixed order, or move to a summary table / materialized counter.

Nova major is your compatibility boundary — put it on the checklist

Add this package to your Nova upgrade checklist as a blocking verification step. Budget roughly half a day per major upgrade to confirm the field still registers and renders. The failure mode is a blank cell in the browser, not a CI failure — Nova's asset registration and Vue 3 component contract churn between majors, and nothing in your test suite will tell you.

Run this before any Nova bump:

composer why-not laravel/nova

Watch the asset pipeline

If dist/ is committed and pre-built, you get no JS toolchain in your app — a real de-risking factor. Verify on a staging deploy rather than by assumption: a field whose Nova::script() points at a missing or stale bundle fails to render with no server-side error. If dist/ is not committed, treat that as a hard stop and vendor the class, writing your own trivial Vue component.

Don't stretch it past counts

Sums, averages, distinct counts, trends, time series, cross-record comparison — none of that belongs here. That's a metrics workstream with metric definition and ownership, not a field. If the request expands beyond "one integer per record," escalate and re-scope rather than accreting logic into the vendored class.

Know when to walk away

If you're not on Nova, this package is inert — use Eloquent withCount and a Blade component, or Filament, or your BI layer. If your dependency policy requires active maintenance, security response, or an SLA, 28 stars and a single maintainer won't clear it — vendor. And if the requirement is "a number the business trusts," the work is definition and reconciliation; the field is the trivial last step, not the project.

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.
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package