exolnet/laravel-bento
Exolnet's Laravel Bento is a lightweight package for building modular dashboards from reusable widgets and cards. Compose responsive admin layouts, configure panels, and add metrics or custom views with a simple API, ideal for internal tools and admin panels.
Requirements (as of v2.9.0): PHP 8.4+ and Laravel 12 only — Laravel 11 support was removed. Pin "exolnet/laravel-bento": "^2.9" and confirm your composer.json platform block before upgrading.
composer require exolnet/laravel-bento
php artisan vendor:publish --tag=bento-config # check `vendor:publish` output for the exact tag
.env with the credentials from Bento → Settings → API Keys:
BENTO_SITE_UUID=
BENTO_PUBLISHABLE_KEY=
BENTO_SECRET_KEY=
The publishable key is for client-side/inert use; the secret key is what server-side writes require. Never expose the secret key through a VITE_/MIX_ variable.Bento facade are auto-discovered — no manual registration in config/app.php.config/bento.php (this is where timeouts/keys live), the Bento facade class in vendor/exolnet/laravel-bento/src, then the underlying bentonow/bento-php-sdk it proxies to. The facade method names track the SDK's resource groups (subscribers(), events()/track(), tags(), fields(), broadcasts()), so the SDK's README is your real API reference.First use case: capture a signup. In a listener for Registered, call the subscriber-create endpoint with the user's email plus any custom fields you've already defined in Bento. Verify it landed in the Bento UI before building anything else on top — most integration pain here is field/tag mismatch, not PHP.
Never call Bento from a controller. Wrap every outbound call in a queued job or a queued event listener so the HTTP round-trip never sits in the request cycle:
class SyncSubscriberToBento implements ShouldQueue
{
public function handle(User $user): void
{
Bento::subscribers()->create([
'email' => $user->email,
'first_name' => $user->first_name,
]);
}
}
Prefer constructor injection over the facade in domain services. Type-hint the client (or the package's manager) so tests can swap it; keep the Bento facade for tinker, artisan commands, and one-off scripts.
Define fields and tags before you send them. Bento silently ignores unknown custom fields, so a typo in plan_tier produces a clean 200 and zero data. Bootstrap them once in a migration-like artisan command (bento:sync-schema) that calls the fields/tags endpoints, and run it against each environment.
Model lifecycle events, not controllers. OrderPaid, SubscriptionCancelled, TrialStarted → one queued listener each. This keeps tracking declarative and means backfills can re-fire the same listeners via Event::dispatch or a replay command.
Bulk operations use the import endpoints. Looping create() over 10k users will hit rate limits and take hours. Batch through the import path (typically a CSV/file payload or a chunked array), 500–1000 rows per call, with Bus::batch() so you get progress and failure tracking.
Testing: the SDK talks HTTP underneath, so Http::fake() with URL pattern matching is the lowest-friction approach — assert on the request payload rather than mocking the wrapper. For unit tests, bind a mock of the client into the container in your TestCase::setUp().
Multi-tenant / multi-brand: the credentials come from config at resolution time, so config(['bento.secret_key' => $tenant->bento_key]) inside a scoped job works. Do it in a middleware or job constructor, never mid-request after other Bento calls have already fired.
^2.8 and pin it in composer.json; a loose ^2.0 constraint will pull 2.9 and fail resolution (or worse, resolve against a platform mismatch in CI). Audit composer.lock and any Docker images still on 8.3 before bumping.null will cost you a day of debugging missing events.php artisan config:cache snapshots bento.*; if you write credentials at runtime from a database or vault, the cached config wins. Either exclude Bento from the cached file or read credentials inside the job rather than at boot.$tries = 3 and backoff() on the job.release(). On 429, $this->release(60) from the job rather than retrying immediately — the SDK generally will not back off for you.How can I help you explore Laravel packages today?