Wishble gives your customers a board to post ideas on, vote for the ones they want and talk them through, and gives your team one place to triage them, size them up and tell everyone following an idea what happened to it. It runs in any Filament 5 panel, in a single-database app or a multi-tenant one, and almost everything about it can be configured.
Features
For your customers
- The board: every idea, filtered by Open, Coming, Shipped and Won't do, by area, by their own ideas or the ones they follow, and searched as typed.
- Votes: one each, taken back by pressing again. "I'd rather you didn't" is counted for your team and never shown on the board.
- Posting an idea: ideas already on the board are offered as they type, so a vote goes to the one already there. Each post is signed the way its author chooses: full name and organisation, first name only, or anonymously.
- Each idea's page: your team's updates, the discussion, and Follow, with status changes only or comments too.
- A Report button on every idea and comment. Nothing hides itself: a person on your team decides.
For your team
- Triage: votes for and against, how many customers want it, Update status (which tells every follower), hide, lock the discussion, and merge a repeated idea into another, votes and followers included.
- Sizing: impact, effort and cost, and the verdict their score falls into, seen only by staff.
- Insights: what's most wanted by how many customers, where things stand, time to the first update, and who isn't taking part.
- The board's words are yours: areas, statuses, sizing levels and verdicts are all edited in the staff panel.
- Turn an idea into a support request, when the app has
polycode/knowble.
For you, the developer
- Config over convention: every feature switches off, and every page's label, icon, group, position and placement (sidebar, user menu, both, hidden, or a cluster's tabs) can be changed.
- Single-database or multi-tenant: one host class tells Wishble who the tenant is, who may use the board, and how to reach people.
- Email and the bell, each person asked before every one.
Requirements
- PHP 8.3 or later
- Laravel 12 or 13
- Filament 5
polycode/knowble, to turn an idea into a support request (optional)- A staff panel that only your team signs in to, separate from your customers' panel, to triage ideas in the app (optional: without one, staff are emailed)
Installation
composer require polycode/wishble
php artisan wishble:install
The installer publishes the config and the migration, writes your choices into them, migrates if you ask, and adds the board's starting vocabulary: areas, statuses, sizing levels and verdicts, all yours to rename. It can run unattended:
php artisan wishble:install --no-interaction --without-against-votes --in-app \
--staff-email=ideas@example.com --team="Acme team" --migrate
Run php artisan wishble:install --help for every option. The installer writes
WISHBLE_STAFF_EMAILS and WISHBLE_TEAM to .env, and to .env.example when you have one.
Installing by hand
php artisan vendor:publish --tag=wishble-config
php artisan vendor:publish --tag=wishble-migrations
php artisan migrate
php artisan wishble:install --vocabulary-only
The last line adds the starting vocabulary to the migrated tables, and never changes a row you've
edited. Then set WISHBLE_STAFF_EMAILS and WISHBLE_TEAM in .env.
Wishble serves its own compiled stylesheet from /wishble-assets/wishble.css on each panel page's
own domain, so there's nothing to publish or deploy. It takes its colours from your panel, dark
mode included.
Setting up the panels
Register WishblePlugin on the panel your customers use, and WishbleAdminPlugin on your staff
panel:
use Polycode\Wishble\WishblePlugin;
use Polycode\Wishble\WishbleAdminPlugin;
$panel->plugin(WishblePlugin::make()); // the customer panel
$panel->plugin(WishbleAdminPlugin::make()); // the staff panel
The board joins the customer panel's Support group as Wishlist; Ideas, Insights and the settings join a Wishlist group on the staff panel.
The two plugins need separate panels, because Wishble tells staff from customers by the panel
they're in. With both on one panel, the app stops with an error as the panel is built, so a deploy
or php artisan optimize shows it. If your app has one panel, add a second for staff. The board
works without a staff panel: staff are emailed new ideas and reports, and triage in a panel when
you add one.
Telling Wishble about your app
config/wishble.php names a host class. The default, Hosts\SingleApp, suits a single-database
app.
Filament's own tenancy
If your customer panel uses ->tenant(Team::class), SingleApp works with no changes: each
team is a tenant, emailed links include the team's slug, and staff can filter by team. Outside a
request (a queued job, a command), build a link with Wishble::ideaUrlFor($tenantId, $reference),
as Wishble::ideaUrl() needs the team of the current request.
Other multi-tenant apps
An app with its own tenancy, such as a database per tenant, extends SingleApp:
use App\Models\Tenant;
use Illuminate\Contracts\Auth\Authenticatable;
use Polycode\Wishble\Hosts\SingleApp;
class WishbleHost extends SingleApp
{
public function tenantId(): string|int|null { return tenant('id'); }
public function tenantName(string|int $tenantId): string { return Tenant::find($tenantId)?->name ?? (string) $tenantId; }
public function tenants(): array { return Tenant::query()->pluck('name', 'id')->all(); }
public function customerUrl(string|int|null $tenantId, string $path): string { /* that tenant's address */ }
public function mayUseWishble(Authenticatable $user): bool { return $user->can('use-the-board'); }
}
mayUseWishble() is all or nothing: someone who may use the board may post, vote, comment and
report, and someone who may not sees none of it. Implement any of these as well:
Contracts\SwitchesPerTenant::isOnFor(): each tenant can turn the board off. Off, its people see no board and hear nothing from it.Contracts\NotifiesInApp: put notifications in the bell, however your app reaches a tenant's users. Wishble askswantsInApp()before each one.Contracts\NotificationPreferences::wantsMail(): each person's choice about email.
Wishble's tables live on one connection (WISHBLE_CONNECTION, a multi-tenant app's central), and
keep their times in one zone (WISHBLE_STORAGE_TIMEZONE, or the app's zone as it starts), however
the app switches zones for each tenant.
Words
brand.team(WISHBLE_TEAM) is how your team signs its posts: "Acme team".brand.customersis what the board calls the customers behind the posts: "from 3 businesses".- Every other string goes through
__(), so alang/*.jsonfile rewords anything.
Plain text or the rich editor
text_editor is rich by default: ideas, comments and the team's notes are written in Filament's
rich editor, with bold, lists and links. Set it to plain in config/wishble.php for plain text
boxes instead (wishble:install --plain-text does this, and the installer asks). Plain text is
shown as typed, line breaks kept, on the board, in the emails and in the bell, and earlier rich
posts still show as they were written.
Order of lists
Areas, statuses, the impact, effort and cost levels, and verdicts are each in the order your team
drags them into on their staff screens. Set any of them to alphabetical under order in
config/wishble.php to sort it by name instead: categories, statuses, scales and
verdicts, all manual by default. The board, its filters and the staff selects follow the same
order.
Feature switches
Each is in config/wishble.php under features: against_votes, sizing, reports,
merging, comments, comment_emails, insights, in_app, new_idea_notices, per_tenant
and convert_to_support. All but in_app are on.
Navigation
Every page's label, icon, group, sort and placement come from config or the plugin, the plugin winning:
use App\Filament\Clusters\SettingsCluster;
use Polycode\Wishble\Navigation\Placement;
WishblePlugin::make()->navigation('board', label: 'Ideas', placement: Placement::UserMenu);
WishbleAdminPlugin::make()
->navigationGroup('Product')
->navigation('statuses', cluster: SettingsCluster::class);
The items are board on the customer panel, and ideas, insights, categories, statuses,
impact, effort, cost and verdicts on the staff panel. ideaActions() adds your own
actions to an idea's triage page.
How the board keeps trust
- The author's name and email, and the business behind a post, never reach the browser: every model hides them when serialised.
- Against-votes are counted for staff and never shown to a customer.
- A status moves only through Update status, which tells every follower.
- Counts are recalculated from the votes, never added to.
- Nothing a customer sends is trusted: which side wrote a comment is decided on the server, and everything a customer can send is throttled.
Leaving
When a tenant leaves, call Wishble::forgetTenant($tenantId). Its follows are deleted and the
email on its ideas, comments, votes and reports is blanked; its words and names stay, as other
people's votes and replies hang on them.
Uninstalling
- Take
WishblePluginandWishbleAdminPluginoff your panels. - To drop the tables, roll back the published
create_wishble_tablesmigration withphp artisan migrate:rollback --path=database/migrations/<its file name>, or drop thewishble_*tables yourself. The migration reads onlyconfig/wishble.php, so it runs with or without the package installed. - Run
composer remove polycode/wishble. - Delete
config/wishble.php, the migration file, and theWISHBLE_*lines in.envand.env.example.
For your own code
Wishble::boardUrl()andWishble::ideaUrl($reference)link to the board.Support\Votes,Support\UpdatesandSupport\Mergerare the same calls the screens make.Support\Vocabulary::seed()adds the starting vocabulary; factories cover every model.
Licence
Commercial: see LICENSE.md.