# Add a Bulk Export Action to a Filament Table from a Linear Issue

Yes. An agent reads the Linear issue, adds an admin-only CSV bulk export to your Filament orders table, covers it with Pest tests and opens a pull request.

[Run this on Rush](https://cloud.getrush.ai/run/add-bulk-export/filament/), in a Project that has your repository.

## What the agent does

The agent clones the repository into its own cloud sandbox and reads how the panel is put together before it writes anything. In a Filament app like `filamentphp/demo`, `composer.json` requires `filament/filament` ^5.0 on Laravel 12 with Pest 3 for tests, and the `test` script runs `pest --parallel` followed by `phpstan analyse`. The orders table is not defined in the resource itself: `app/Filament/Resources/Shop/Orders/Tables/OrdersTable.php` builds it in a static `configure()` method, and its only bulk action today is a `DeleteBulkAction` inside `groupedBulkActions()`.

Filament already has the export plumbing, and the demo already uses it. `app/Filament/Exports/Shop/BrandExporter.php` extends `Exporter` with a list of `ExportColumn`s and a completion notification, `ListBrands.php` wires it up through `ExportAction::make()->exporter(BrandExporter::class)`, and the migrations include the `exports`, `job_batches` and `notifications` tables the exporter needs. So the agent follows the same pattern: a new `app/Filament/Exports/Shop/OrderExporter.php` with columns for `number`, `customer.name`, `status`, `total_price`, `shipping_price`, `shipping_method` and `created_at`, matching the fields on the `Order` model and the columns the table already shows.

Then it adds `ExportBulkAction::make()->exporter(OrderExporter::class)` beside the delete action in `OrdersTable.php`, limited to CSV with `->formats([ExportFormat::Csv])`. The admins-only part needs a decision, because the repository has no notion of an admin: `User::canAccessPanel()` returns `true` for everyone and the `users` table has no role column. The agent reads that, picks the smallest change that makes the rule enforceable, such as an `is_admin` boolean with a migration and a factory state, puts `->visible()` on the action, and writes the choice and its alternative into the pull request description so the reviewer can swap it for the team's real authorization.

Tests go where the existing ones are. `tests/Filament/Resources/Shop/Orders/Pages/ListOrdersTest.php` already drives `ListOrders` through Livewire with `TestAction::make(...)->table($record)`, and `tests/Pest.php` signs in a fresh user before every test in `Filament`. The agent adds cases that an admin can call the bulk export on selected orders and that a non-admin cannot see it, runs `composer setup` steps the tests need (an `.env`, an app key, the SQLite database), runs `vendor/bin/pest`, and fixes what fails before it pushes a branch.

## Start it with one API call

`POST https://api.prix.dev/o/{org}/sessions`

```json
{
  "agent": "claude",
  "input": "A Linear issue labeled agent says: \"Add a Bulk Export Action to a Filament Table. Add a bulk action on the orders table that exports the selected orders to CSV with Filament's exporter, available to admins only.\" Do the work in this repository, add tests for it, run the Pest suite, and open a pull request that links back to the issue.",
  "resources": [
    {
      "url": "https://github.com/your-org/your-app"
    },
    {
      "url": "https://linear.app/your-workspace/issue/ENG-42"
    }
  ]
}
```

1. **Get an API key.** In the console, open Settings, then API keys, and create a key for your org. Send it in the Authorization header as a Bearer token.
2. **Send the request.** POST the body in request.json to https://api.prix.dev/o/{org}/sessions, with your repository as the resources URL. The run starts in its own cloud sandbox.
3. **Read what came back.** Follow the run on its live stream or wait for the run.completed webhook, then read the result and its trajectory.

## What comes back

What comes back is a pull request on its own branch. In a panel shaped like the demo, the diff adds `app/Filament/Exports/Shop/OrderExporter.php`, changes `app/Filament/Resources/Shop/Orders/Tables/OrdersTable.php` to register the export next to the existing delete action, and extends `ListOrdersTest.php`. If the app had no admin flag, the migration, the `User` change and the factory state that introduce one are in the same diff, called out in the description. The description quotes the issue and links back to it.

A reviewer checks four things before merging: the exported columns are the ones the business wants in the CSV (and nothing sensitive from the customer record slipped in), the visibility rule matches how the team already decides who is an admin, the export runs on the queue the app uses in production rather than only the `sync` queue `phpunit.xml` sets for tests, and the new Pest cases fail if the action is removed or made visible to everyone.

The session keeps the record behind the pull request: every command the agent ran with its output, from `composer install` to the Pest run, and its reasoning about the admin rule. The session read returns the repository and the pull request, so the run and its result stay linked.

## Run it on every Linear issue labelled agent

In the console, add an automation on the Project with a Linear trigger on `linear.issue.labeled`, filtered to the `agent` label. Each issue that gains the label starts one run with the issue as its context, in its own sandbox, and opens its own pull request. Changes made by an app integration never fire the trigger, so only a person adding the label starts work.

Without an automation, a server that receives Linear's webhook can send the same request with the issue's URL in `resources`. Set a webhook URL on the API key and Rush POSTs `run.completed` when each turn finishes, with the `session_url` to read the pull request from.

## Why run it on Rush?

A Filament change like this needs PHP 8, Composer, a migrated database and a seeded app key before a single Pest test can run, and the demo's `test` script runs PHPStan straight after Pest. Each run on Rush sets that up in its own sandbox, runs the real commands and keeps their output, so the reviewer reads the actual test run instead of a claim that tests passed.

Labelled issues do not wait for each other. Ten small admin-panel requests labelled in one triage session become ten runs, each in its own sandbox on its own branch, each ending in a pull request with checks, and nobody has to switch their checkout between them.

## Frequently asked questions

### My app has no admin role. What does the agent do with "admins only"?

It reads how panel access is decided, in `canAccessPanel()` and any policies, and uses the existing rule if there is one. If there is none, as in the Filament demo, it adds the smallest enforceable rule and says so in the pull request, so the reviewer can replace it with the team's own authorization before merging.

### Will it use Filament's exporter or write its own CSV code?

The issue asks for Filament's exporter, and the agent follows the pattern already in the codebase: an `Exporter` subclass with `ExportColumn`s, attached through `ExportBulkAction`. That keeps the export on Filament's queue jobs and its completion notification instead of a hand-written stream.

### Exports run on a queue. Does the test actually produce a file?

The demo's `phpunit.xml` sets `QUEUE_CONNECTION` to `sync`, so the export job runs inside the test. The agent's tests assert that the action can be called on selected records and is hidden from the wrong users; the reviewer should still try one export on a real queue worker before release.

### Does it run PHPStan too?

The prompt asks for the Pest suite, and the agent runs it. When the repository's `composer test` script also runs `phpstan analyse`, as the demo's does, the agent can run that script instead, and the record shows which command ran and what it printed.
