← Back to blog
Modular blocks for media, permissions, translations, queues, search, debugging and multi-tenancy seated in one base plate
·10 min read

The Laravel Packages I Use in Production (Kept Updated)

The Laravel packages I actually ship: Spatie Media Library, Permission and Activity Log, Laravel Excel, Scramble, Horizon, Telescope, Octane, Laravel MCP and more. What each one is for, the production gotchas, and the packages I tried and dropped.

laravelphppackagesbackend

Last updated: October 2026. I revisit this list when I adopt or drop a package — see the changelog at the bottom.

This isn't a list of every popular Laravel package. It's what I actually install on the apps I build and maintain — SaaS products, e-commerce, and Odigix, a digital-goods store with local payments and supplier APIs. Each package below is one I've shipped to production, with the reason I use it and the thing that bit me.

Quick list

PackageWhat it's for
spatie/laravel-medialibraryFile uploads and image conversions attached to models
spatie/laravel-permissionRoles and permissions
spatie/laravel-activitylogAudit trail: who changed what, and when
spatie/laravel-translatableMultilingual model fields (Arabic, French, English)
maatwebsite/excelExcel/CSV imports and exports
dedoc/scrambleAPI documentation generated from your code
laravel/horizonRedis queues with a dashboard
laravel/telescopeExceptions, slow queries and outgoing HTTP calls
laravel/scoutFull-text search on Eloquent models
laravel/octaneKeeping the app in memory between requests
spatie/browsershotPDFs that render Arabic correctly
laravel/mcpLetting ChatGPT or Claude work with your app
laravel/boostGiving AI coding agents real context about your app

The second tier covers the packages I use without much to say about them, and the last section covers the ones I dropped.

Data and permissions

spatie/laravel-medialibrary

Attaches files to any Eloquent model and generates image conversions (thumbnails, WebP) for you. I use it for anything with images: product galleries, avatars, documents.

composer require spatie/laravel-medialibrary
use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\InteractsWithMedia;
use Spatie\MediaLibrary\MediaCollections\Models\Media;

class Product extends Model implements HasMedia
{
    use InteractsWithMedia;

    public function registerMediaConversions(?Media $media = null): void
    {
        $this->addMediaConversion('thumb')->width(400)->format('webp');
    }
}

$product->addMediaFromRequest('image')->toMediaCollection('gallery');

My note: conversions are queued by default. Make sure a queue worker is running in production, or your thumbnails never appear.

spatie/laravel-permission

Roles and permissions stored in the database, with middleware, Blade directives and Laravel's own can() all working out of the box.

composer require spatie/laravel-permission
$user->assignRole('manager');
$role->givePermissionTo('orders.refund');

// Anywhere Laravel's authorization works:
$user->can('orders.refund');
Route::middleware('permission:orders.refund')->post('/orders/{order}/refund', ...);

My note: check permissions in your code, not roles. Roles change ("support can refund now"); a can('orders.refund') check doesn't have to.

spatie/laravel-activitylog

An audit trail. When an admin asks "who changed this price?", you have the answer — with the old and new values.

composer require spatie/laravel-activitylog
use Spatie\Activitylog\LogOptions;
use Spatie\Activitylog\Traits\LogsActivity;

class Product extends Model
{
    use LogsActivity;

    public function getActivitylogOptions(): LogOptions
    {
        return LogOptions::defaults()
            ->logOnly(['name', 'price', 'is_active'])
            ->logOnlyDirty();
    }
}

My note: logOnly + logOnlyDirty keeps the table small and the log readable. And like Telescope, the activity table grows forever — schedule activitylog:clean.

spatie/laravel-translatable

Stores translations of a field as JSON in one column, so $product->name returns the current locale's value. It's the simplest way I know to handle Arabic, French and English content without translation tables. (For the interface strings side, see i18n in Laravel and Vue with Inertia.)

use Spatie\Translatable\HasTranslations;

class Product extends Model
{
    use HasTranslations;

    public array $translatable = ['name', 'description'];
}

$product->setTranslation('name', 'ar', 'بطاقة هدايا');

Imports, exports and API docs

Laravel Excel

maatwebsite/excel handles the "can I export this to Excel?" request that every admin panel gets, and the reverse: bulk imports from a spreadsheet.

use Maatwebsite\Excel\Concerns\FromQuery;
use Maatwebsite\Excel\Concerns\Exportable;

class OrdersExport implements FromQuery
{
    use Exportable;

    public function query()
    {
        return Order::query()->whereMonth('created_at', now()->month);
    }
}

(new OrdersExport)->queue('exports/orders.xlsx');

My note: for anything over a few thousand rows, use FromQuery and queue it (and WithChunkReading for imports). Loading everything into a collection inside a web request is how exports time out.

Scramble

dedoc/scramble generates OpenAPI documentation by reading your code — routes, Form Requests, API Resources and return types. No annotations to write and keep in sync.

composer require dedoc/scramble

That's it: the docs are at /docs/api. Because they come from the code, they can't drift from what the API actually does — which is the problem with every hand-written API doc I've maintained.

My note: the more typed your code is (Form Requests, Resources, return types), the better the docs. It's a nice side effect: Scramble rewards writing Laravel the clean way.

Queues, debugging and performance

Laravel Horizon

A dashboard and supervisor for Redis queues: throughput, failed jobs, retries, and worker balancing. On Odigix, every paid order is fulfilled by a Horizon worker that calls the supplier's API, so Horizon is where I look first when an order is stuck.

composer require laravel/horizon
php artisan horizon:install

My note: put queues with different urgency in separate supervisors. A slow bulk job shouldn't delay the job that delivers a customer's order.

Laravel Telescope

Most people treat Telescope as a local debugging tool. I run it in production too, because it answers questions logs don't:

  • Exceptions with the full request that caused them.
  • Slow queries — set the threshold and see which queries cross it.
  • Outgoing HTTP calls. If your app talks to third-party APIs (payments, suppliers, couriers), turn on the HTTP client watcher. You see the exact request you sent and the response you got back, which settles most "is it us or them?" debates in minutes.
// config/telescope.php
'watchers' => [
    Watchers\QueryWatcher::class => [
        'enabled' => env('TELESCOPE_QUERY_WATCHER', true),
        'slow' => 100, // ms
    ],
    Watchers\ClientRequestWatcher::class => env('TELESCOPE_HTTP_CLIENT_WATCHER', true),
    // ...
],

Lock the dashboard down with the viewTelescope gate in TelescopeServiceProvider — it shows request data.

The warning: prune it, or it will kill your server. Telescope writes a row for almost everything, and the telescope_entries table grows fast. The docs tell you to schedule telescope:prune, but on a busy app there's a catch: once the table is big, pruning takes a long time. Scheduled commands run in the foreground of schedule:run by default, so a long prune blocks your other scheduled tasks, overlaps with the next run, and can hit time or memory limits and die halfway — leaving the table as big as before.

Give the prune its own process. Either its own cron entry:

# crontab — runs separately from schedule:run
0 3 * * * cd /var/www/app && php artisan telescope:prune --hours=48 >> /dev/null 2>&1

Or push it to the queue, so it runs on a worker instead of inside the scheduler:

// routes/console.php
Schedule::call(fn () => Artisan::queue('telescope:prune', ['--hours' => 48]))->dailyAt('03:00');

If you go the queue route, make sure the worker's timeout is long enough for the prune to finish.

Laravel Scout

Adds a search() method to Eloquent models, backed by the driver you choose. Start with the database engine and move to a dedicated engine like Meilisearch or Typesense when you need typo tolerance and relevance ranking.

use Laravel\Scout\Searchable;

class Product extends Model
{
    use Searchable;
}

Product::search('playstation 50')->paginate(20);

Laravel Octane

Octane boots your app once and keeps it in memory, instead of bootstrapping Laravel on every request. I'm running it with FrankenPHP, and performance went up noticeably.

composer require laravel/octane
php artisan octane:install --server=frankenphp
php artisan octane:start --server=frankenphp

Status: on trial. I'm still measuring it, so no numbers yet — I'll publish before/after benchmarks here when the trial is done. If you try it, the known gotcha is shared state: singletons, static properties and anything cached on a service now live across requests, so audit those before you switch.

spatie/browsershot

Renders HTML to PDF (or images) with headless Chrome. I reach for it whenever a PDF contains Arabic. PHP PDF libraries like dompdf render Arabic as empty squares and break right-to-left layouts; a real browser gets both right, along with all of your CSS.

use Spatie\Browsershot\Browsershot;

Browsershot::html(view('invoices.show', compact('invoice'))->render())
    ->format('A4')
    ->save(storage_path("invoices/{$invoice->number}.pdf"));

The trade-off: you need Node and Chrome on the server. For the full story of hitting this wall, see AutoFacture, where I drove Puppeteer directly.

AI

Laravel MCP

laravel/mcp lets you build an MCP server inside your Laravel app — a set of tools that an AI assistant like ChatGPT or Claude can call. Your app becomes something you can operate from a chat.

On Odigix, I exposed product management as MCP tools: update a product's information and upload its images. The admin then used ChatGPT to redo product images — about 600 products in one day, a job that used to mean Photoshop and uploading each image by hand. It ran on the client's existing $20/month ChatGPT plan and used roughly 10% of its weekly usage limit.

The shape of a tool:

use Illuminate\Contracts\JsonSchema\JsonSchema;
use Illuminate\Support\Arr;
use Laravel\Mcp\Request;
use Laravel\Mcp\Response;
use Laravel\Mcp\Server\Tool;

class UpdateProductTool extends Tool
{
    protected string $description = 'Update a product\'s name or description.';

    public function schema(JsonSchema $schema): array
    {
        return [
            'product_id' => $schema->integer()->required(),
            'name' => $schema->string(),
            'description' => $schema->string(),
        ];
    }

    public function handle(Request $request): Response
    {
        $data = $request->validate([
            'product_id' => ['required', 'exists:products,id'],
            'name' => ['sometimes', 'string', 'max:255'],
            'description' => ['sometimes', 'string'],
        ]);

        $product = Product::findOrFail($data['product_id']);
        $product->update(Arr::except($data, 'product_id'));

        return Response::text("Updated product #{$product->id}.");
    }
}
// routes/ai.php
Mcp::web('/mcp/admin', AdminServer::class); // protect it: this is your admin

My note: treat an MCP server like an admin API. Same authorization, same validation, and an audit log (activitylog is perfect here) so you can see what the AI changed.

Laravel Boost

laravel/boost gives AI coding agents (Claude Code, Cursor and others) the context they're missing: your Laravel and package versions, version-specific docs, your database schema, and tools to run queries and read logs. The result is less code written for the wrong Laravel version.

composer require laravel/boost --dev
php artisan boost:install

More on how I use AI tools day to day on How I work with AI.

Also in my composer.json

Packages I use regularly, with less to say about them:

  • laravel/sanctum: API tokens and SPA authentication.
  • laravel/socialite: "Sign in with Google" and friends.
  • spatie/laravel-query-builder: filtering, sorting and includes from the query string, safely.
  • spatie/laravel-data: typed data objects for requests and responses. Scramble documents them too.
  • inertiajs/inertia-laravel + tightenco/ziggy: my default way to build a Laravel front end with Vue or React, and Laravel route names in JavaScript.
  • laravel/cashier: Stripe subscriptions, for products sold outside Algeria.
  • laravel/reverb: WebSockets without a third-party service.
  • spatie/laravel-backup: database and file backups to S3-compatible storage.
  • sentry/sentry-laravel: error tracking and alerts.
  • prism-php/prism: one API for calling different LLM providers.
  • Dev tools: laravel/pint (code style), pestphp/pest (tests), larastan/larastan (static analysis), barryvdh/laravel-debugbar and barryvdh/laravel-ide-helper.

What I tried or stopped using

Laravel Nova → a custom admin. I used Nova for admin panels. When the license expired, I didn't renew it. With Inertia and AI-assisted development, building a custom admin is now fast, and it's more flexible: no fighting a framework's idea of what a resource page should look like. Odigix's admin is moving to a custom Laravel + Inertia + React build.

stancl/tenancy → single-database multi-tenancy. I've worked with stancl/tenancy and it's a solid package, but I never shipped it to production. GoStore, my multi-tenant e-commerce platform, ended up on Next.js with a single Postgres database, where every table is scoped by tenant. If you need a database per tenant in Laravel, stancl is where I'd start.

dompdf → Browsershot. Covered above: Arabic text and modern CSS.

Payment SDKs → small classes of my own. SlickPay and OneClick, the two Algerian payment providers I've integrated, both have packages. I ended up writing one small class per provider behind a shared interface instead. A payment integration is a few HTTP calls; a package for it is another dependency to keep updated, and when it lags behind the provider's API, you're stuck. Small things don't always need a package. The pattern is in How to accept online payments in Algeria.

Changelog

  • October 2026: rewrote the list. Added Activity Log, Laravel Excel, Scramble, Octane (on trial), Browsershot, Laravel MCP and Boost. Moved stancl/tenancy to "tried", added the Nova and payment SDK notes.
  • August 2024: first version (7 packages).

Building a Laravel product and want someone who's already made these choices in production? I work as a freelance Laravel developer for founders and teams, remotely in UTC+1.

Building something similar?

Tell me where you are and what's blocking you. I'll give you an honest read on how I'd approach it.