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 Feature Voting Laravel Package

a2zwebltd/laravel-feature-voting

Add feature request voting to any Laravel app. Users submit ideas, upvote the ones they want, and track status as you build. Ships with models, migrations, routes, and a service layer so you can drop it into an existing project quickly.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and publish everything before touching the feature flag — moderation is opt-in, so the migration is a separate tag and won't arrive on its own.

composer require <vendor>/feature-voting
php artisan vendor:publish --tag=feature-voting-config
php artisan vendor:publish --tag=feature-voting-migrations
php artisan vendor:publish --tag=feature-voting-moderation-migration   # new in v1.2.0
php artisan migrate

Where to look first, in order:

  1. config/feature-voting.php — enable moderation here (FEATURE_VOTING_MODERATION=true in .env), and check the model/table/route keys it exposes.
  2. lang/en/*.json (or your host lang/{locale}/*.json) — v1.2.0 makes every package string translatable from the host app, so this is now the canonical place to rename "Feature Request" to "Idea", "Roadmap Suggestion", etc.
  3. resources/views/vendor/feature-voting/ after publishing views — the namespaced component layouts live here.
  4. app/Listeners/ — FeatureRequestPublished is the integration seam for everything downstream (changelog, Slack, analytics).

First use case: submit a request as a non-admin user with moderation on, confirm it lands in a pending state, approve it from the admin panel, and watch the submitter mail go out in the submitter's locale. That single path exercises the flag, the migration, the publish() transition, the event, and the mailable — if it works end-to-end, the package is wired correctly.

Implementation Patterns

Moderation as a two-phase lifecycle. Treat publish() as the domain transition, not a UI toggle. Everything that should only happen for publicly-visible requests (notifications, sitemap entries, search indexing, changelog feeds) hangs off the event rather than off the controller:

// app/Listeners/AnnounceFeatureRequest.php
public function handle(FeatureRequestPublished $event): void
{
    Cache::forget('feature-requests.public');

   if (config('feature-voting.integrations.slack_webhook')) {
        Notification::route('slack', config('feature-voting.integrations.slack_webhook'))
            ->notify(new FeatureRequestAnnounced($event->featureRequest));
    }
}

Because the event carries the model, listeners stay decoupled from how publishing was triggered (admin UI, artisan command, import job).

Gate the moderation surface behind your own policy. The flag enables the feature globally; it doesn't grant permission. Keep FeatureRequestPolicy::moderate() in sync with the flag so that disabling moderation doesn't leave orphaned "pending" links in your navigation:

public function moderate(User $user): bool
{
    return config('feature-voting.moderation.enabled')
        && $user->hasRole('admin');
}

Localize from the host, never from the package. Copy the package's JSON keys into your app's lang/{locale}.json and override only what you need. The trans_choice counts mean vote totals and request counts are pluralized through the same mechanism — verify your overrides keep the {0} …|{1} …|[2,*] … pipe segments intact, otherwise counts render with a literal | in the UI.

Mail locale. v1.2.0 sends mail in the recipient's locale. That means the mailable resolves translations at send time using the submitter's stored locale — so make sure your users table actually has a reliable locale column and that it's populated at registration, or every mail falls back to config('app.locale'). If you queue mail, set the locale on the mailable, not just on the current request:

Mail::to($request->submitter)
    ->locale($request->submitter->locale ?? config('app.locale'))
    ->queue(new FeatureRequestPublishedMail($request));

Re-publish views deliberately. Published views are copies; v1.2.0's namespaced component layout changes won't reach them. Diff before overwriting:

php artisan vendor:publish --tag=feature-voting-views --force
git diff resources/views/vendor/feature-voting

If you've customized layouts, prefer overriding a single component in resources/views/components/ rather than forking the whole published tree — that keeps future re-syncs cheap.

Gotchas and Tips

  • The moderation migration is not part of the main migrations tag. Existing installs that run only php artisan migrate will not get the new columns/table. You must publish feature-voting-moderation-migration explicitly, then migrate. This is the most likely upgrade failure.
  • Flipping the flag on later leaves existing requests in an ambiguous state. Decide a backfill policy before you enable it in production: either treat all existing requests as already-published, or set them pending and accept a moderation queue spike. Do it in one data migration, not by hand.
  • config:cache will bite you. Adding FEATURE_VOTING_MODERATION to .env does nothing until php artisan config:cache is re-run on cached environments. Symptom: the flag reads false in production but true locally.
  • trans_choice with JSON translation files is fragile for languages with more than two plural forms (Polish, Russian, Arabic). JSON keys hold a single pipe-delimited string, which works for English-like zero/one/many but not for full CLDR plural sets. If you ship to those locales, move those specific strings into PHP array files under lang/{locale}/feature-voting.php and use trans_choice against the array key instead.
  • Queued mail can lose its locale if the locale is set on the request rather than the mailable. If translated strings appear in the wrong language only in production (where the queue is async), that's the cause.
  • Event timing. If publish() runs inside a DB transaction and a listener dispatches a queued job immediately, the job may read the pre-commit state. Use DB::afterCommit() or make listeners implement ShouldQueueAfterCommit.
  • Namespaced component layouts mean any hardcoded @extends('layouts.app') in your own overrides won't match the package's expected layout name. Check the published components for the exact namespace before customizing.
  • Debugging translation gaps: the fastest check is php artisan lang:publish plus a grep for feature-voting:: in the published views — any key not present in your host JSON falls through to the package's default, which is fine, but a key you thought you overrode and didn't is the usual culprit.
  • Extension points worth knowing: the publish() transition, the FeatureRequestPublished event, the mailable class (swappable via config if the package exposes a mail key — verify in config/feature-voting.php), and the published views. Those four cover nearly every customization request; prefer them over forking package classes.
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.
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core