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

Voyager Export Laravel Package

joy/voyager-export

Export Voyager admin data to Excel or CSV in one click. joy/voyager-export adds BREAD-aware export actions to Laravel Voyager, letting you download any table, filtered list, or dataset straight from the admin panel.

View on GitHub
Deep Wiki
Context7

Getting Started

Prerequisites and install

composer require joy/voyager-export

This is a Voyager module, so you need a working Voyager install first (tcg/voyager or the joy-voyager ecosystem it belongs to). Confirm the constraint in composer.json before running the command — the 2024-02 release targets modern Laravel, but the Voyager major version it pins against is the thing that will bite you.

Then publish the module assets/config and clear caches:

php artisan vendor:publish --provider="Joy\VoyagerExport\VoyagerExportServiceProvider"
php artisan optimize:clear

If the provider name differs in your version, just run the interactive publisher and look for the joy/voyager-export tag. If a migration ships with it (some Joy modules store export jobs/logs), run php artisan migrate as well.

Where to look first

  1. config/voyager-export.php (or whatever the published file is named) — exporter driver, chunk size, disk, and which BREADs get an export button.
  2. vendor/joy/voyager-export/src/ — the service provider and the exporter class it registers. This package is small and thinly documented; reading the ~5 files is faster than searching for docs.
  3. The published Blade views (if any) — this is where the export button is injected into voyager::bread.browse.

First use case

Log into /admin, open any BREAD (e.g. Users or Posts), and look for an Export action on the browse screen. Clicking it should stream the table out as CSV (or XLSX, depending on the driver configured). That end-to-end click is your smoke test — it exercises provider boot, view injection, permission check, and the exporter in one shot.


Implementation Patterns

Admin-facing export on a BREAD

The primary pattern is declarative: enable it per-BREAD via config, and the browse view renders the button without you touching controllers.

// config/voyager-export.php
return [
    'enabled'    => true,
    'driver'     => 'csv',              // or 'xlsx' if a Laravel Excel adapter ships
    'chunk_size' => 1000,
    'breads'     => [
        'users' => ['columns' => ['id', 'name', 'email', 'created_at']],
        'posts' => ['columns' => ['id', 'title', 'status', 'created_at']],
    ],
];

Anything not listed either exports everything or is hidden — check the source, the default matters more than the docs suggest.

Reusing the exporter outside Voyager

The real day-to-day leverage is calling the exporter from your own controller or Artisan command rather than from the admin UI:

use Joy\VoyagerExport\Facades\VoyagerExport;

Route::get('/reports/posts.csv', function () {
    return VoyagerExport::fromModel(Post::class)
        ->columns(['id', 'title', 'status', 'created_at'])
        ->download('posts-' . now()->format('Y-m-d') . '.csv');
});

You get consistent column handling and streaming for free, and your scheduled reports look the same as the admin's ad-hoc downloads.

Scoping to the current filter

If the browse view supports search/filter, the button should carry the query string through — otherwise users get "export" for the whole table and assume it exported their filtered view. Either pass the request query into the exporter (->applyFilters($request->query())) or generate the button URL with request()->getQueryString() appended. This is the single most common "bug report" for export modules.

Large exports

For anything over ~10–50k rows, don't do it in the request:

ExportPostsJob::dispatch($filters)->onQueue('exports');

Have the job write to Storage::disk('exports') and email/notify the user with a signed URL. The package's chunking helps memory, but it won't help a load balancer's 30-second timeout.

Permissions

Voyager's role system is the natural gate. Wrap the button render in a permission check (Auth::user()->hasPermission('browse_posts') or a dedicated export_posts if the package registers one), and keep the route behind the same admin.user middleware group. Never expose the export route publicly just because it's read-only — a full table dump is a data exfiltration vector.


Gotchas and Tips

Excel and UTF-8. Raw UTF-8 CSV opened in Excel mangles accented characters. Prepend a BOM (\xEF\xBB\xBF) on the first row, or your first support ticket will be about Müller showing as Müller.

BREAD columns ≠ database columns. The exporter typically reads the BREAD definition, so relation columns (belongsTo) come out as raw IDs, and image/file columns as paths. If you need labels, add an accessor on the model and export that instead — don't try to make the exporter join.

Dates and timezones. Timestamps export in whatever the app timezone is at query time, which may not be the admin's. Either document it or format explicitly per-column. Mixed date formats across exports are a silent source of reporting errors.

Empty exports. If a BREAD has zero rows, most exporters happily emit a header-only file or an empty body — verify which, because "download succeeded but the file is blank" is ambiguous to users. Prefer a header-only file.

Version drift is your biggest risk. Seven stars and a single 2024 release means no active maintainer to catch a Voyager or Laravel breaking change. Pin the version in composer.json, and if the module is load-bearing, fork it into your packages/ directory with a path repository so you can patch it during a framework upgrade without waiting on upstream.

Extension points, in order of preference:

  1. Config (drivers, chunk size, per-BREAD column lists) — cheapest, survives upgrades.
  2. Published Blade views — override the browse view to restyle or reposition the button.
  3. A custom exporter class bound via the container if the package exposes a contract — subclass rather than editing vendor code.
  4. Fork. Last resort, but for a package this small it's a realistic option.

Debugging a missing button. Work outward: (a) is the BREAD browse permission granted to the role? (b) is the BREAD listed in the package config? (c) did the view publish actually overwrite the cached compiled views (php artisan view:clear)? (d) is the service provider auto-discovered — check bootstrap/cache/packages.php.

Test it in the pipeline. A one-line feature test that hits the export endpoint and asserts Content-Type and a non-zero row count will catch the "an upgrade silently broke the exporter" case that nobody notices until month-end reporting.

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