tomatophp/tomato-artisan
Tomato Artisan brings Laravel's Artisan commands into your TomatoPHP admin panel, letting you browse, search, and run them from the browser with output shown inline—handy for quick maintenance tasks without opening a terminal.
tomatophp/tomato-artisan is a TomatoPHP admin-panel plugin that puts an Artisan command runner inside your Laravel dashboard, so you can fire commands like optimize:clear, migrate, queue:restart, or storage:link from the browser instead of opening SSH.
Minimal steps:
composer require tomatophp/tomato-artisan
php artisan vendor:publish --tag=tomato-artisan-config
php artisan vendor:publish --tag=tomato-artisan-migrations
php artisan migrate
It expects the Tomato admin stack (tomatophp/tomato-admin, Filament-based) to already be installed and your AdminPanelProvider to exist. Register the plugin there:
->plugin(\TomatoPHP\TomatoArtisan\TomatoArtisanPlugin::make())
Where to look first:
config/tomato-artisan.php — command allow-list, middleware, and guard settings.TomatoArtisanPlugin registration in app/Providers/Filament/AdminPanelProvider.php.src/Pages/ directory if you need to see how the command form and output view are composed.First use case: give a support/ops user a "Clear cache" and "Restart queue" button in the panel so they stop pinging you for php artisan optimize:clear after a content change.
Gate the whole surface behind a permission. Day-to-day, this is the pattern that matters most:
// AdminPanelProvider
->plugin(
TomatoArtisanPlugin::make()
->canRun(fn () => auth()->user()?->hasRole('super-admin'))
)
Pair it with a Gate::define('run-artisan', ...) in a service provider so the check is reusable and testable outside the panel.
Whitelist rather than blacklist. Keep the config's allowed-command list narrow and grow it deliberately. A useful production set:
'allowed' => [
'optimize:clear',
'cache:clear',
'config:cache',
'route:cache',
'queue:restart',
'storage:link',
'migrate --force',
],
Long-running commands go to the queue. Anything that takes more than a couple of seconds (imports, migrate on a large DB) should be dispatched as a job the panel command simply kicks off, then poll a status row. Running synchronously in a web request will hit PHP's max_execution_time and leave the browser hanging.
Environment scoping. Wrap the plugin registration in if (app()->environment(['local', 'staging'])) for teams that don't want a command runner in production, or keep it in production but restrict to a single super-admin role plus an audit log entry per invocation.
Custom commands ride along for free. Any php artisan make:command you write is immediately runnable from the panel — handy for one-off maintenance scripts (app:reindex-products) that you don't want to expose via Tinker.
Combine with tomato-settings to add a feature flag that flips the runner on/off without a deploy. Useful during incidents where you want to lock the panel down quickly.
v1.1.1 specifics: the release only touches the command UI's dark mode and RTL styling. Both are inherited automatically — there is nothing to configure and nothing to migrate. If you've published and customised the plugin views, diff them against upstream to pick up the new class names, otherwise the update is a no-op for you.
This is a remote-code-execution surface by design. Treat the allow-list as a security boundary, not a convenience. Never let a free-text command field reach Artisan::call() with unvalidated input. If the package version you're on exposes an arbitrary-input mode, disable it and rely solely on the allow-list.
Output truncation. Command output is captured and returned in the response; commands producing megabytes of output (verbose migrations, dump-autoload -v) can blow the response size or the memory limit. Use --quiet-style flags or redirect to a log file and link it.
Permissions follow the web user, not your shell user. storage:link or queue:restart run as www-data. If your deploy writes files as deploy, storage:link will fail with a confusing permission error — the fix lives in your deploy script's chown/chmod, not in the plugin.
Config caching bites during setup. After editing the allow-list, run php artisan config:clear locally; a stale cached config will silently keep the old command set and you'll spend ten minutes wondering why your new command doesn't appear.
Cache-busting commands need ordering. config:cache followed by route:cache can fail if a route closure exists. Surface failures clearly — check that the panel displays the exit code and stderr, not just stdout, or you'll chase phantom "successes."
RTL and dark mode (new in v1.1.1). Direction is driven by the panel's locale/direction setting, so Arabic/Hebrew locales now render the command table and form correctly. If you have published views with hardcoded text-left, ml-*, or bg-white utilities, they will not follow the new theme — swap them for logical properties (ms-*, text-start) and Filament's dark: variants.
Extension point for UI tweaks: publish the views (--tag=tomato-artisan-views) rather than subclassing the page classes; the page classes are thin and change between minor releases, while the Blade layer is where your branding belongs.
Debugging a command that works in the terminal but not the panel: log Artisan::output() and the exit code around the call inside a local wrapper command. Nine times out of ten it's a different working directory, a missing env var not present in the FPM pool, or a --no-interaction requirement.
How can I help you explore Laravel packages today?