| Install | |
|---|---|
composer require chengkangzai/filament-shot |
|
| Latest Version: | v0.7.10 |
| PHP: | ^8.2 |
| License: | MIT |
| Last Updated: | Mar 20, 2026 |
| Links: | GitHub · Packagist |
Render Filament v4/v5 UI components — Forms, Tables, Infolists, and Stats Widgets — as PNG screenshots programmatically. Define your components using familiar Filament classes and get pixel-perfect images without spinning up a browser manually.
These screenshots are generated by CI using the actual rendering pipeline to verify quality.
| Light | Dark |
|---|---|
![]() |
![]() |
| Section Layout | Grid Layout |
|---|---|
![]() |
![]() |
| Additional Field Types |
|---|
![]() |
| Tabs | Wizard |
|---|---|
![]() |
![]() |
| Color Picker |
|---|
![]() |
| Basic | With Badges |
|---|---|
![]() |
![]() |
| Styled (Weight + Mono Font) | Dark Mode |
|---|---|
![]() |
![]() |
| Icon Column (Boolean) | Bulk Actions |
|---|---|
![]() |
| Reorderable | Action Group |
|---|---|
![]() |
![]() |
| Light | Dark |
|---|---|
![]() |
![]() |
| Sidebar |
|---|
![]() |
| Light | Dark |
|---|---|
![]() |
![]() |
| Success |
|---|
![]() |
Filament Shot generates standalone HTML using its own Blade templates styled with Filament's CSS classes, then captures screenshots via Browsershot. This avoids Livewire context issues entirely — no running application or panel required.
Install the package via Composer:
composer require chengkangzai/filament-shot
Install Puppeteer (required by Browsershot):
npm install puppeteer
Publish the config file (optional):
php artisan vendor:publish --tag="filament-shot-config"
Capture Filament form components as an image:
use CCK\FilamentShot\FilamentShot;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\Toggle;
FilamentShot::form([
TextInput::make('name')
->label('Full Name')
->placeholder('Enter your name'),
TextInput::make('email')
->label('Email Address')
->placeholder('you@example.com'),
Select::make('role')
->label('Role')
->options([
'admin' => 'Administrator',
'editor' => 'Editor',
'viewer' => 'Viewer',
]),
Toggle::make('active')
->label('Active'),
])
->state(['name' => 'Jane Doe', 'email' => 'jane@example.com'])
->save('form.png');
Supported field types: TextInput, Select, Textarea, Toggle, Checkbox, Radio, Placeholder, DatePicker, DateTimePicker, FileUpload, ColorPicker, TagsInput, KeyValue, RichEditor, MarkdownEditor, Repeater.
Layout components: Section, Grid, Fieldset, Tabs, Wizard.
use CCK\FilamentShot\FilamentShot;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Toggle;
use Filament\Schemas\Components\Tabs;
use Filament\Schemas\Components\Tabs\Tab;
FilamentShot::form([
Tabs::make('Settings')
->tabs([
Tab::make('General')
->icon('heroicon-o-cog-6-tooth')
->schema([
TextInput::make('site_name')->label('Site Name'),
TextInput::make('site_url')->label('Site URL'),
]),
Tab::make('Notifications')
->icon('heroicon-o-bell')
->schema([
Toggle::make('email_notifications')->label('Email Notifications'),
]),
]),
])
->state(['site_name' => 'My App', 'site_url' => 'https://myapp.com'])
->save('form-tabs.png');
Use ->activeTab(2) to render a specific tab as active.
use CCK\FilamentShot\FilamentShot;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Toggle;
use Filament\Schemas\Components\Wizard;
use Filament\Schemas\Components\Wizard\Step;
FilamentShot::form([
Wizard::make([
Step::make('Account')
->description('Your credentials')
->schema([
TextInput::make('email')->label('Email'),
TextInput::make('password')->label('Password'),
]),
Step::make('Profile')
->description('Personal info')
->schema([
TextInput::make('name')->label('Full Name'),
]),
Step::make('Review')
->description('Confirm details')
->schema([
Toggle::make('terms')->label('I accept the terms'),
]),
]),
])
->state(['email' => 'jane@example.com'])
->width(900)
->save('form-wizard.png');
Use ->startOnStep(2) to render a specific step as active (previous steps show as completed).
use CCK\FilamentShot\FilamentShot;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Toggle;
use Filament\Schemas\Components\Section;
FilamentShot::form([
Section::make('Personal Information')
->schema([
TextInput::make('name')->label('Full Name'),
TextInput::make('email')->label('Email'),
]),
Section::make('Settings')
->schema([
Toggle::make('active')->label('Active'),
]),
])
->state(['name' => 'Jane Doe', 'email' => 'jane@example.com', 'active' => true])
->save('form-with-sections.png');
use CCK\FilamentShot\FilamentShot;
use Filament\Tables\Columns\TextColumn;
FilamentShot::table()
->columns([
TextColumn::make('name'),
TextColumn::make('email'),
TextColumn::make('role'),
])
->records([
['name' => 'Alice', 'email' => 'alice@example.com', 'role' => 'Admin'],
['name' => 'Bob', 'email' => 'bob@example.com', 'role' => 'Editor'],
['name' => 'Charlie', 'email' => 'charlie@example.com', 'role' => 'Viewer'],
])
->heading('Team Members')
->striped()
->save('table.png');
use CCK\FilamentShot\FilamentShot;
use Filament\Actions\Action;
use Filament\Actions\ActionGroup;
use Filament\Tables\Columns\TextColumn;
FilamentShot::table()
->columns([
TextColumn::make('name'),
TextColumn::make('email'),
])
->records([
['name' => 'Alice', 'email' => 'alice@example.com'],
['name' => 'Bob', 'email' => 'bob@example.com'],
])
->recordActions([
Action::make('view')->label('View')->icon('heroicon-o-eye'),
ActionGroup::make([
Action::make('edit')->label('Edit')->icon('heroicon-o-pencil-square'),
Action::make('delete')->label('Delete')->icon('heroicon-o-trash')->color('danger'),
]),
])
->save('table-action-group.png');
FilamentShot::table()
->columns([
TextColumn::make('name'),
TextColumn::make('email'),
])
->records([
['name' => 'Alice', 'email' => 'alice@example.com'],
['name' => 'Bob', 'email' => 'bob@example.com'],
])
->reorderable()
->save('table-reorderable.png');
use CCK\FilamentShot\FilamentShot;
use Filament\Actions\BulkAction;
use Filament\Tables\Columns\TextColumn;
FilamentShot::table()
->columns([
TextColumn::make('name'),
TextColumn::make('email'),
TextColumn::make('status')->badge(),
])
->records([
['name' => 'Alice', 'email' => 'alice@example.com', 'status' => 'Active'],
['name' => 'Bob', 'email' => 'bob@example.com', 'status' => 'Blocked'],
['name' => 'Charlie', 'email' => 'charlie@example.com', 'status' => 'Active'],
])
->bulkActions([
BulkAction::make('change_status')
->label('Change Status')
->icon('heroicon-o-bell')
->color('success'),
BulkAction::make('delete')
->label('Delete')
->icon('heroicon-o-trash')
->color('danger'),
])
->selectedRows([0, 2]) // indices of rows to show as checked
->save('table-bulk-actions.png');
use CCK\FilamentShot\FilamentShot;
use Filament\Infolists\Components\TextEntry;
FilamentShot::infolist([
TextEntry::make('name')->label('Name'),
TextEntry::make('email')->label('Email'),
TextEntry::make('joined')->label('Member Since'),
])
->state([
'name' => 'Jane Doe',
'email' => 'jane@example.com',
'joined' => 'January 2024',
])
->save('infolist.png');
use CCK\FilamentShot\FilamentShot;
use Filament\Navigation\NavigationGroup;
use Filament\Navigation\NavigationItem;
FilamentShot::navigation()
->items([
NavigationItem::make('Dashboard')
->icon('heroicon-o-home'),
NavigationGroup::make('Content')
->items([
NavigationItem::make('Posts')
->icon('heroicon-o-document-text')
->isActiveWhen(fn () => true)
->badge('24', 'success'),
NavigationItem::make('Pages')
->icon('heroicon-o-document'),
]),
NavigationGroup::make('Settings')
->items([
NavigationItem::make('General')
->icon('heroicon-o-cog-6-tooth'),
]),
])
->heading('Admin Panel')
->width(320)
->save('navigation.png');
use CCK\FilamentShot\FilamentShot;
use Filament\Widgets\StatsOverviewWidget\Stat;
FilamentShot::stats([
Stat::make('Total Users', '1,234')
->description('12% increase')
->descriptionIcon('heroicon-m-arrow-trending-up')
->color('success'),
Stat::make('Revenue', '$56,789')
->description('8% increase')
->chart([7, 3, 4, 5, 6, 3, 5, 8]),
Stat::make('Orders', '456')
->description('3% decrease')
->descriptionIcon('heroicon-m-arrow-trending-down')
->color('danger'),
])
->save('stats.png');
use CCK\FilamentShot\FilamentShot;
FilamentShot::notification()
->title('Status Updated')
->body('The customer status has been changed to Active.')
->success()
->width(400)
->save('notification.png');
Every renderer supports multiple output methods:
$renderer = FilamentShot::form([...]);
// Save to disk
$renderer->save('/path/to/screenshot.png');
// Get base64-encoded PNG
$base64 = $renderer->toBase64();
// Get rendered HTML (useful for debugging)
$html = $renderer->toHtml();
// Get an HTTP response with the PNG
return $renderer->toResponse();
Control the screenshot dimensions and resolution:
FilamentShot::form([...])
->width(1280)
->height(720)
->deviceScale(2) // Retina / HiDPI
->save('screenshot.png');
Switch between light and dark mode, or set a custom primary color:
FilamentShot::form([...])
->darkMode()
->primaryColor('#3b82f6')
->save('dark-form.png');
FilamentShot automatically includes CSS from any Filament plugin registered via FilamentAsset::register(). If the plugin's service provider is loaded, its styles will appear in your screenshots — no extra configuration needed.
use Ysfkaya\FilamentPhoneInput\Forms\PhoneInput;
FilamentShot::form([
TextInput::make('name')->label('Name'),
PhoneInput::make('phone')->label('Phone Number'),
])
->state(['name' => 'Jane Doe', 'phone' => '+60123456789'])
->width(600)
->save('form-with-phone.png');
If a plugin's CSS isn't auto-discovered (e.g. it doesn't use Filament's asset pipeline), you can inject it manually using ->css() or ->cssFile():
FilamentShot::form([
TextInput::make('name')->label('Full Name'),
TextInput::make('phone')->label('Phone Number')->prefix('+60'),
TextInput::make('email')->label('Email'),
])
->state(['name' => 'Jane Doe', 'phone' => '12-345 6789', 'email' => 'jane@example.com'])
->css('
/* Custom plugin styles injected directly */
.my-plugin-input { border-color: #3b82f6; }
')
->cssFile('/path/to/vendor/my-plugin/dist/plugin.css')
->width(600)
->save('form-with-phone.png');

Capture screenshots from a config file:
php artisan filament-shot:capture --config=screenshots.php
The config file should return an array of screenshot definitions:
<?php
use Filament\Forms\Components\TextInput;
return [
[
'type' => 'form',
'components' => [
TextInput::make('name')->label('Name'),
TextInput::make('email')->label('Email'),
],
'output' => 'storage/screenshots/form.png',
'width' => 800,
'dark_mode' => false,
],
];
Supported types: form, table, infolist, stats.
// config/filament-shot.php
return [
// Default viewport dimensions for screenshots
'viewport' => [
'width' => 1024,
'height' => 768,
'device_scale_factor' => 2,
],
// Default theme settings
'theme' => [
'dark_mode' => false,
'primary_color' => '#6366f1',
],
// Browsershot / Puppeteer configuration
'browsershot' => [
'node_binary' => null, // Path to Node.js binary
'npm_binary' => null, // Path to npm binary
'chrome_path' => null, // Path to Chrome/Chromium binary
'no_sandbox' => false, // Set true for Docker/CI environments
'timeout' => 60, // Timeout in seconds
'additional_options' => [], // Extra Puppeteer launch options
],
// CSS customization
'css' => [
'theme_path' => null, // Override path to Filament theme CSS
'extra' => '', // Additional CSS to inject
],
];
If running in a Docker container or CI pipeline, you'll likely need to enable no_sandbox:
// config/filament-shot.php
'browsershot' => [
'no_sandbox' => true,
],
composer test
Run only unit tests (no Chrome required):
vendor/bin/pest --exclude-group=integration
Run integration tests (requires Chrome + Puppeteer):
vendor/bin/pest --group=integration
Please see CHANGELOG for more information on what has changed recently.
Please see CONTRIBUTING for details.
Please review our security policy on how to report security vulnerabilities.
The MIT License (MIT). Please see License File for more information.