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

Nova Rating Field Laravel Package

nikaia/nova-rating-field

A Laravel Nova field that adds interactive star ratings to your resources, letting users rate records with a configurable star scale directly in the Nova UI. Supports custom icons, half stars, and read-only display for indexes and detail views.

View on GitHub
Deep Wiki
Context7

Getting Started

Verify you're actually a candidate before installing anything. This field only renders inside a licensed Laravel Nova panel. It has zero value in Filament, Orchid, or a plain Blade admin. The sole release is 2019-12-24, so check your Nova major first:

composer show laravel/nova | grep versions
php -v

On Nova 1.x/2.x (PHP 7.x) this is a legitimate drop-in and you can be productive in ten minutes. On Nova 3/4/5, treat the repository as reference source, not a dependency — the Vue side uses the pre-Vue-3 field.value / $emit('input') contract and will not bind to Nova 4's update:value model. Budget half a day for a spike (load form → click star → save → reload) before you promise anyone a delivery date.

Install (legacy path only). Service provider is auto-discovered and registers compiled assets via Nova::script() / Nova::style(), so there's normally no publish step of your own:

composer require nikaia/nova-rating-field
php artisan nova:publish
php artisan view:clear

Where to look first — the source is only two files. The docs are thin, so read:

  • vendor/nikaia/nova-rating-field/src/ — the Field subclass. grep 'public function' to enumerate the fluent setters (max(), color, size, read-only/display flags) and confirm the $component name matches what the Vue side registers.
  • vendor/nikaia/nova-rating-field/dist/ — confirm the compiled JS/CSS is actually committed. If it isn't, you're inheriting a 2019 webpack toolchain and should re-scaffold with current nova.mix.js rather than resurrect the old package.json.

First use case: a nullable whole-star score on one resource. Schema and cast first, then the field:

// migration
Schema::table('reviews', function (Blueprint $table) {
    $table->unsignedTinyInteger('rating')->nullable()->after('body');
});
// app/Models/Review.php — the cast is load-bearing, see Gotchas
protected $casts = ['rating' => 'integer'];
// app/Nova/Review.php
use Nikaia\Rating\Rating; // verify FQCN in vendor/src

public function fields(Request $request)
{
    return [
        ID::make()->sortable(),

        Rating::make('Rating')
            ->rules('nullable', 'integer', 'between:1,5')
            ->sortable(),

        Textarea::make('Body'),
    ];
}

Seed three records — null, 1, 5 — and open index, detail, and create views. Those three contexts are precisely where third-party Nova fields break, and it's cheaper to find out on day one than in a demo.

Implementation Patterns

Define the data contract once in a shared trait. Star config drifting between resources is the #1 source of "why does this one go to 10?" bugs:

// app/Nova/Concerns/HasRatingField.php
trait HasRatingField
{
    protected function ratingField(string $column = 'rating', int $max = 5): Rating
    {
        return Rating::make(ucfirst($column))
            ->max($max)                        // confirm setter name in vendor/src
            ->rules('nullable', 'integer', "between:1,{$max}")
            ->sortable();
    }
}

Treat null as the canonical "not scored yet" and say so in the migration comment. For review queues, RICE inputs, and vendor scores, null means unscored and 0 means something else entirely. Enforce the distinction at the model boundary (mutator or check constraint), never in the widget — the widget emits whatever the operator clicks.

Validation lives server-side, always. The stars give you a visual range, not a validated one. Keep integer|between:1,5 in the field's rules() and in the FormRequest if you have one, because Nova field rules only run on the Nova form path — not on imports, seeders, console commands, or API writes.

Read-only display in index/detail. If a display/read-only setter exists, use it. If it doesn't, scope explicitly instead of fighting the component:

Rating::make('Rating')->exceptOnForms(),   // detail + index only
Rating::make('Rating')->hideFromIndex(),   // if index rendering is noisy or heavy

Roll out behind a feature flag on a single resource. Put the field on Review only, and keep a Number::make('Rating')->min(1)->max(5) behind a config toggle so you can flip back in one deploy if it misbehaves in an edge context (inline belongsTo create, 100-row index pages, mobile Safari).

Vendor the source when you need Nova 4/5 or ongoing ownership. The whole package is a Field subclass plus a registered asset — roughly 150–200 LOC. Lifting it into app/Nova/Fields is straightforward:

// app/Nova/Fields/StarRating.php
namespace App\Nova\Fields;

use Laravel\Nova\Fields\Field;

class StarRating extends Field
{
    public $component = 'star-rating';    // Nova 1/2 style; ->component() on 4+
    public $max = 5;

    public function max(int $max): static
    {
        $this->max = $max;
        return $this;
    }

    public function jsonSerialize(): array
    {
        return array_merge(parent::jsonSerialize(), ['max' => $this->max]);
    }
}
// resources/js/fields/star-rating.js (Nova 1/2 API)
Nova.booting((Vue) => {
  Vue.component('star-rating', {
    props: ['resourceName', 'field'],
    template: `
      <div class="star-rating">
        <button v-for="n in field.max" :key="n" type="button"
                :aria-label="n + ' stars'"
                :class="{ active: n <= field.value }"
                @click="$emit('input', n)">★</button>
        <button type="button" @click="$emit('input', null)">Clear</button>
      </div>`
  })
})

On Nova 4+, switch to modelValue / @update:modelValue="$emit('update:value', $event)", register with Nova.booting((app) => { app.component(...) }), and compile through nova.mix.js. Register the asset wherever you register your other custom assets:

// NovaServiceProvider::boot()
Nova::script('star-rating', __DIR__.'/../dist/js/field.js');
Nova::style('star-rating', __DIR__.'/../dist/css/field.css');

Pair the input field with an aggregate you own. If anything shows "4.2/5", compute it in your own layer — accessor, scoped query, cached column — and format it yourself. Do not try to coax a float out of the star field; it is an operator-input widget, not an aggregation.

Use it as a template for other cheap Nova fields. Once vendored, the pattern (Field subclass + one SFC + one Nova::script() line) generalizes to colour pickers, severity badges, and status pills. That's the real day-to-day leverage here: not the stars, the recipe.

Gotchas and Tips

  • The laravel/nova constraint is the trap. It's likely permissive or pinned to an old major, so composer require can succeed against Nova 5 and then fail in the browser with an unregistered component or a blank cell. Composer success is not evidence of compatibility — the spike is the load-form-click-save-reload loop.
  • Vue API drift is a rewrite, not a patch. Nova 1/2 uses this.field.value + $emit('input'); Nova 4+ uses the update:value model plus different field mixins. If stars render but never persist, this is why. Check the console for the component name resolving, and confirm the emit name matches your Nova major.
  • Check whether dist/ is committed. If assets ship pre-built, runtime risk is nil and build debt is academic. If not, you inherit webpack 3/4 + Babel 6 — re-scaffold with the current nova.mix.js instead of resurrecting 2019 dependencies.
  • Asset not loading? Nova::script() requires an absolute path and Nova versions asset URLs. Symptom: 404 on field.js in the network tab. Fix the path, then php artisan nova:publish and hard-refresh. Stale compiled JS is the single most common cause of "the fix didn't work."
  • Cast the column to integer. Without it Eloquent hands a string to the JSON payload, the fill comparison (n <= field.value) silently fails, and you get a blank widget on data you know exists.
  • Same-star click usually doesn't clear. Most implementations of this widget can't return to null by re-clicking. Ship a clear affordance, and verify what the component emits on clear — null, 0, or '' — then normalise at the model boundary.
  • Index and detail are different render paths. A field that looks great in a form often renders an editable input in the index table, or instantiates a Vue component per row on 100-row pages. Default to a plain text/Number display in index; keep the star UI for forms and detail unless you've explicitly tested the index with real data volumes.
  • No filtering, no search, no aggregation. "Show me all unrated items" needs a hand-written Nova filter or a whereNull('rating') scope. Don't expect the field to wire anything up — it's display + input only.
  • Accessibility is on you. These widgets are typically unlabeled buttons with no keyboard nav. If you vendor or rewrite, add aria-label, visible focus styles, and Space/Enter handling on day one — retrofitting a11y after operators have built muscle memory is materially worse.
  • Adoption equals ownership. Zero dependents, no release since 2019, no upstream to fix anything. Vendor the source into app/Nova/Fields, drop the Composer dependency, name an owner, and add a mandatory re-test line item to every Nova and Laravel major upgrade — the recurring cost is re-validation, not code churn.
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
shitoudev/permissionmanager