inani/larapoll
Larapoll is a Laravel package that adds polls to your app. Create polls with multiple options, let users vote, and display results with ease. Simple setup, configurable options, and a clean API for quick integration.
Install the package and wire up its schema before writing any domain code:
composer require inani/larapoll
php artisan vendor:publish --provider="Inani\Larapoll\LarapollServiceProvider"
php artisan migrate
Pin to ^3.1 explicitly — this is the first release since the 3.0 line (June 2021) and it fixes the deprecated Factory class and duplicate route-name collisions. Publishing drops migrations for polls, options, and votes, plus a config/larapoll.php. Read the config first — it's where you swap model classes and toggle package routes — then open vendor/inani/larapoll/src/Poll.php and Option.php to confirm the exact method surface before you build on it (the README is sparse and the API has shifted across versions).
The first use case to prototype is the full loop: create a poll, attach options, render a vote form, cast a vote, display tallies.
use Inani\Larapoll\Poll;
$poll = Poll::create([
'question' => 'Which queue driver do you use?',
'options' => ['redis', 'database', 'sqs'],
]);
$poll->vote($poll->options->first(), $user); // explicit voter
$poll->vote($poll->options->first()); // or auth()->user()
$poll->results(); // per-option tallies
If that snippet works against your Laravel version, the rest of the package is mostly plumbing around it.
Poll lifecycle. Treat isVotable(), close(), and open() as the guard rail around vote(). Wrap voting in a small service rather than calling the model from controllers:
if (! $poll->isVotable() || $poll->hasVoted($user)) {
abort(422, 'Not votable');
}
$poll->vote($option, $user);
Authoring. Build polls inside a transaction — a poll with zero options is a broken record your UI has to defend against. Prefer the array form for atomic creation; use addOptions([...]) / removeOptions([...]) for edits, and block edits once $poll->votes()->exists() so results don't drift.
Rendering results. Eager-load aggressively — Poll::with('options.votes')->find($id) — because results() and any per-option count will otherwise fire a query per option. Compute percentages in a view model or Blade partial, not in the model, so you can reuse the same data for an API response.
Authorization. The package does not ship policies. Add a PollPolicy covering close, open, update, and destroy, and check ownership before any option mutation. This is the single biggest gap for anything user-facing.
Integration points. If you use the package's routes, mount them behind your own middleware group in the config. If you don't, disable them and expose your own controllers — you'll almost certainly need custom validation, flash messages, and JSON endpoints anyway.
Model extension. Where the config exposes model bindings, point them at your own subclasses so you can add relationships (e.g., user() on Poll for an author column) without forking.
Factory class so the test scaffolding no longer triggers deprecation warnings on Laravel 8. It does not touch the illuminate/* constraints, so composer require on Laravel 10/11 will likely still fail — try --with-all-dependencies, or fork and loosen composer.json. The route-name fix is welcome but confirms the package is on the Laravel 8 track, not chasing current Laravel.isVotable vs canVote, and the options array on Poll::create, have wobbled between versions. php artisan tinker against the real model beats reading docs.(poll_id, user_id) (or (option_id, user_id), depending on whether you allow multi-select) in a follow-up migration — the package's check is a race-condition-prone exists() query.votes. Check the published migrations and add onDelete('cascade') yourself if they're missing it.vote() with no user writes a null user_id; decide early whether guest votes count, and enforce it in your guard, not in the view.results() is convenient but naive. For any list of polls, drop to withCount('votes') on options instead.votes table first — the Vote model is the source of truth, and results() silently returns zeros if the option_id foreign key didn't resolve.polls.* routes.polls/options/votes schema with your own casts and policies is often less work than maintaining this dependency.How can I help you explore Laravel packages today?