steadfastcollective/statamic-csv-exporter
Export Statamic entries to CSV with configurable columns and filters. A small addon for content teams needing quick spreadsheet-friendly exports from the control panel.
Install and wire it up
composer require steadfastcollective/statamic-csv-exporter
Statamic addons auto-discover their service provider, so there is usually no manual registration step. Immediately after install, open vendor/steadfastcollective/statamic-csv-exporter/src/ServiceProvider.php — for a package this small, that file is the documentation. Read its boot() and register() methods and note three things:
$this->publishes([...], 'csv-exporter-config') or similar tags, then publish with php artisan vendor:publish --provider="SteadfastCollective\CsvExporter\ServiceProvider".config/csv-exporter.php (or config/statamic/csv-exporter.php) will tell you which collections/fields are exported out of the box and whether output is streamed or written to disk.First use case
Start with the lowest-risk, highest-value workflow: a one-off export of a single collection for a client handoff or a content audit. In the Control Panel, go to Collections → {your collection} → Entries, select a handful of entries, open the Actions dropdown, and look for an export entry. If the action isn't there, check config/statamic/actions.php (Statamic only surfaces registered actions) and confirm the addon's action class extends Statamic\Actions\Action.
Do the first export against a collection with plain text fields only. You want to confirm the mechanics (headers, encoding, file download) before you introduce Bard, Grid, or relationship fields, which are where CSV exports usually get messy.
CP-driven bulk export (the daily driver). The realistic workflow is: editor selects entries in a listing → runs the action → downloads a CSV. That's fine for tens of entries. For anything larger, treat the CP action as a smoke test rather than a production export path, because PHP execution time and memory will bite you before the CSV gets large.
Config-defined export schemas. If the package supports per-collection field maps, lean on it rather than editing PHP. A typical shape:
// config/csv-exporter.php
'collections' => [
'products' => [
'columns' => [
'title' => 'Product Name',
'sku' => 'SKU',
'price' => 'Price',
'categories' => 'Categories', // taxonomy -> join with |
],
],
],
Keeping labels here means non-developers can request header changes without a deploy.
Programmatic export from your own code. When you need a filtered or scheduled export, bypass the CP entirely. The pattern is the same regardless of the addon's internals:
use Illuminate\Support\Facades\DB;
$entries = \Statamic\Facades\Entry::query()
->where('collection', 'products')
->where('status', 'published')
->lazy(); // stream, don't ->get()
return response()->streamDownload(function () use ($entries) {
$out = fopen('php://output', 'w');
fputcsv($out, ['Product Name', 'SKU', 'Price', 'Categories']);
foreach ($entries as $entry) {
fputcsv($out, [
$entry->get('title'),
$entry->get('sku'),
$entry->get('price'),
collect($entry->get('categories'))->implode('|'),
]);
}
fclose($out);
}, 'products-' . now()->format('Y-m-d') . '.csv');
If the addon ships an exporter class, prefer injecting it over hand-rolling — it will already handle the header row, delimiter, and encoding consistently with the CP action.
Scheduled exports. Register an Artisan command in app/Console/Kernel.php (or routes/console.php on Laravel 11+) that writes to storage/app/exports/, then let your client pull the file from wherever you sync it. This is the sane path for full-catalogue exports — no HTTP timeout, no browser tab dying at 60 seconds.
Multi-site. Statamic stores entries per site. If the addon doesn't expose a site selector, filter explicitly with ->where('site', 'en') in your own query, or you'll silently export the default site only and wonder where the translations went.
CSV injection is a real vulnerability, not a nicety. Any field starting with =, +, -, or @ will be interpreted as a formula when the client opens the file in Excel. Prefix those cells with a single quote or a tab. If the package doesn't do this (small addons frequently don't), wrap your values before they hit fputcsv.
Field values are not all strings. Assets, entries, terms, and users come back as objects/arrays. Decide deliberately:
$asset->url() (public link) vs $asset->path() (relative) vs $asset->id(). Pick one and document it.| or ; — never a comma, since that's your delimiter.strip_tags. Don't try to make it look pretty.Augmented vs raw values. $entry->get('field') returns the stored value; $entry->augmentedValue('field')->value() returns what the template would render (resolved URLs, formatted dates, Markdown converted to HTML). For round-trip-able exports use raw; for human-readable exports use augmented. Mixing the two in one file is the most common source of "why does this column look different" tickets.
Excel and UTF-8. Without a BOM, Excel on Windows will mangle accented characters and emoji. Prepend "\xEF\xBB\xBF" to the stream if your audience is Excel-based; skip it if they're importing into a system that chokes on a BOM.
Memory blows up on ->get(). Always use ->lazy() or ->chunk(). A 20k-entry export built in memory will exhaust the default 128M limit. Streaming with php://output keeps memory flat.
Nulls vs empty strings. fputcsv writes an empty cell for null, which downstream tools read as "column missing" or "column empty" depending on the parser. If distinction matters, coerce with ?? '' and be consistent.
Permissions. Statamic actions expose visibleTo() / authorize(). If the export button appears for roles that shouldn't see unpublished drafts, that's a policy gap in the addon — verify by logging in as a low-privilege role before shipping to a client.
Evaluate maintainability honestly. This is a 4-star package with zero dependents and a single recent release. Treat it as a thin convenience wrapper, not infrastructure: pin the exact version in composer.json, read the source before relying on undocumented behaviour, and be prepared to vendor the exporter class into app/ if Statamic ships a breaking major (Statamic's action and query APIs have shifted between v3, v4, and v5). A 200-line export class is cheap to own outright, and that option should be on the table from day one.
Debugging tip. If the CP action silently does nothing, check storage/logs/laravel.log first, then confirm the action class is present in the resolved actions list with php artisan tinker → \Statamic\Facades\Action::all(). Half of all "the addon is broken" reports for Statamic addons are actually a cache issue — run php artisan statamic:stache:clear && php artisan config:clear.
How can I help you explore Laravel packages today?