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

Laravel Vote Laravel Package

jcc/laravel-vote

jcc/laravel-vote adds upvote and downvote support to Laravel Eloquent models. Use its traits and relationships to let users vote on posts, comments, or any model, then query vote counts and check whether a user has already voted.

View on GitHub
Deep Wiki
Context7

Getting Started

This package is a thin trait-based layer, not a full voting subsystem. You install it, publish its migration/config, add traits to your models, and then call methods directly. That is both its strength and its limitation: minimal overhead, but you own the endpoints, validation, caching, and UI.

composer require jcc/laravel-vote
php artisan vendor:publish --provider="Jcc\LaravelVote\LaravelVoteServiceProvider"
php artisan migrate

Look first at:

  • the published migration — confirm the votes table columns and indexes
  • vendor/jcc/laravel-vote/src/Traits/Voter.php
  • vendor/jcc/laravel-vote/src/Traits/Voteable.php
  • the published config, if present, for table/model overrides

Add the voter trait to your user model:

use Jcc\LaravelVote\Traits\Voter;

class User extends Authenticatable
{
    use Voter;
}

Add the voteable trait to any model that can receive votes:

use Jcc\LaravelVote\Traits\Voteable;

class Post extends Model
{
    use Voteable;
}

First use case — a single upvote endpoint:

Route::post('/posts/{post}/upvote', function (Post $post) {
    auth()->user()->upVote($post);

    return back();
})->middleware('auth');

Read it back with the voter helpers and voteable relationships:

$user->hasUpVoted($post);      // boolean
$user->hasDownVoted($post);    // boolean
$post->upVoters()->count();    // count
$post->voters;                 // collection of users

If those method names do not exist in your installed version, inspect the traits before building on them — this package has had little recent activity, and its API is small enough to verify in a few minutes.

Implementation Patterns

Single vote controller with explicit actions. Do not accept a raw value from the client. Map a small enum of actions to package methods:

class VoteController extends Controller
{
    public function __invoke(Request $request, Post $post)
    {
        $action = $request->validate([
            'vote' => 'required|in:up,down,cancel',
        ])['vote'];

        $user = $request->user();

        match ($action) {
            'up' => $user->upVote($post),
            'down' => $user->downVote($post),
            'cancel' => $user->cancelVote($post),
        };

        return back();
    }
}

Toggle behavior belongs in your application layer. The package gives you primitives, not toggle semantics. For a like button:

if ($user->hasUpVoted($post)) {
    $user->cancelVote($post);
} else {
    $user->upVote($post);
}

For switching between up and down, wrap cancel-then-vote in a transaction and ensure your unique constraint prevents duplicates:

DB::transaction(function () use ($user, $post) {
    $user->cancelVote($post);
    $user->downVote($post);
});

Put voting behind a service or action class. Controllers should not know whether votes are stored through the package, a custom model, or cached counters. A VoteService also gives you one place to dispatch domain events, invalidate cache, and update denormalized counters.

Use voteable relationships for feeds and counts. Eager load counts instead of loading voter collections:

$posts = Post::withCount(['upVoters', 'downVoters'])
    ->latest()
    ->paginate();

If the package exposes upVotes()/downVotes() query relationships, use those for aggregate queries. Reserve $post->voters for screens that actually render the voter list.

Add cached counters for anything list-heavy. The package stores individual vote rows; it does not give you a fast score column for sorting. Add up_votes_count, down_votes_count, and score to the parent table, then update them from your service layer:

$post->increment('up_votes_count');
$post->decrement('score');

Then order feeds with orderByDesc('score') instead of subqueries over the votes table.

Expose current-user state in API resources. A frontend usually needs upvoted, downvoted, score, and up_votes_count in one payload:

return [
    'id' => $this->id,
    'score' => $this->score,
    'upvoted' => auth()->check() && auth()->user()->hasUpVoted($this),
    'downvoted' => auth()->check() && auth()->user()->hasDownVoted($this),
];

Authorize before voting. The package does not enforce visibility or ownership rules. Use a policy:

public function vote(User $user, Post $post): bool
{
    return $user->can('view', $post);
}

Rate-limit vote endpoints. A vote endpoint is cheap to spam. Apply throttle middleware or a per-user rate limiter in addition to the package’s data integrity constraints.

Use polymorphic voteables deliberately. Because the package is polymorphic, you can attach the trait to Post, Comment, and Review. Keep the frontend contract consistent (up, down, cancel) so one controller pattern handles all types.

Gotchas and Tips

Check Laravel compatibility before committing. The last release was March 2021. On Laravel 9/10/11 you may hit trait return-type conflicts, deprecated helpers, or migration column differences. Test install and php artisan migrate in a scratch app first; be prepared to fork or alias the package.

The unique index is mandatory. Without a unique constraint on the vote identity columns (votable_type, votable_id, user_id), concurrent requests can create duplicate votes. Verify the published migration, and add the index if it is missing.

Handle race conditions gracefully. Two rapid clicks can bypass your hasVoted check. Rely on the database constraint and catch QueryException/UniqueConstraintViolationException, or use updateOrCreate if you take over the storage layer.

Relationship names can collide. The traits add methods such as votes, voters, and possibly upVoters/downVoters to your models. If you already have a votes() relationship, alias the trait method or rename yours. Conflicts here fail loudly, but only when the method is called.

N+1 queries are easy to create. $post->voters loads full user models. In a feed, use withCount and cached counters. In an API, never embed the full voter list by default.

Votes are not automatically cleaned up. Soft-deleted users and voteable models can leave orphaned rows. Add observers or scheduled cleanup if your app uses soft deletes heavily.

Do not trust client-supplied vote values. A request containing value=1 is an invitation to tamper. Derive the vote direction from the route or a validated action name.

Caching needs explicit invalidation. If you cache scores or counts, invalidate them in the same service method that writes the vote. The package likely does not dispatch events you can hook into, so do not rely on model events for cache busting unless you verify they fire.

Confirm config and publish tags. vendor:publish --provider=... may publish both config and migration, or only one depending on the version. Use --tag if the provider publishes multiple groups, and inspect config/vote.php before renaming tables.

Morph maps help if you use them. If your app registers a morph map, ensure the voteable types stored in the votes table match the map, or queries through the package’s relationships may fail to resolve models.

Extension point: replace the vote model. If you need extra columns (reason, weight, source), publish or fork the package’s Vote model, update the config/model reference if supported, and keep the package trait methods working against your subclass. This is usually cleaner than adding parallel vote tables.

Assess maintenance risk honestly. With one release in 2021, treat this as a small utility rather than a long-term dependency. The migration is simple enough to vendor into your app, and the trait methods are easy to reimplement if the package becomes incompatible. For a new project, budget a day to wrap it behind your own VoteService so replacing it later is a contained change.

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.
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib