tomatophp/filament-withdrawals
Filament Withdrawals lets you manage withdrawal methods, custom fields, and withdrawal requests directly from your Filament admin panel. Register the plugin, run the installer, and extend forms with custom field types.
composer require tomatophp/filament-withdrawals
php artisan filament-withdrawals:install
app/Providers/Filament/AdminPanelProvider.php:
->plugin(\TomatoPHP\FilamentWithdrawals\FilamentWithdrawalsPlugin::make())
php artisan vendor:publish --tag="filament-withdrawals-config"
php artisan vendor:publish --tag="filament-withdrawals-lang"
php artisan vendor:publish --tag="filament-withdrawals-migrations"
The config file is now effectively required if you use relation fields — see the allowlist note below.Create a withdrawal method and process a request:
// Define a withdrawal method (e.g., bank transfer)
FilamentWithdrawalFormBuilder::make(1)->build();
// Submit a withdrawal request
FilamentWithdrawalFormBuilder::make(1)->send([
'amount' => 100.50,
'account' => '123456789',
'currency' => 'USD',
]);
Open config/filament-withdrawals.php and inspect the new relations key. It ships empty, so every relation field on every withdrawal method will render with no options until you populate it:
'relations' => [
\App\Models\Bank::class => ['name'],
],
Withdrawal Method Management
FilamentWithdrawalFormFields::register().Request Processing
$withdrawal = FilamentWithdrawalFormBuilder::make($methodId)->send($data);
Form Field Extensions
CodeEditor for verification codes):
FilamentWithdrawalFormFields::register([
WithdrawalFormFieldType::make('verification_code')
->className(TextInput::class)
->label('Verification Code')
->rules('required|digits:6'),
]);
Relation Fields (v5.0.1 allowlist)
config/filament-withdrawals.php. Map each model to the exact columns that may be read:
'relations' => [
\App\Models\Bank::class => ['name', 'iban'],
\App\Models\Country::class => ['name', 'iso_code'],
],
'banks' on a user model) can be allowlisted the same way instead of a foreign class.['id', 'name'] is usually enough.Event-Driven Logic
WithdrawalRequested) to trigger actions like:
WithdrawalRequest::dispatch($data)->onQueue('withdrawals');
Route::post('/withdraw', [WithdrawalController::class, 'store']);
relations allowlist as part of your deploy artifact (env-specific config or a committed config/filament-withdrawals.php), so a fresh environment doesn't silently ship with blank relation dropdowns.Empty Relation Allowlist After Upgrade (v5.0.1)
Hidden Attributes Are Never Shown
$hidden array (password, remember_token, etc.) is filtered out before rendering. Don't fight it — the pre-5.0.1 behaviour of leaking a password column is exactly what was patched.$hidden and $visible on the model before assuming the config is wrong.Floating-Point Precision
bcmath or round() for currency amounts to avoid precision errors:
$amount = bcdiv($request->amount, 1, 2); // Ensures 2 decimal places
Migration Conflicts
wallet was changed from integer to float in v1.0.2).Form Field Registration
WithdrawalFormFieldType contract. Missing methods will throw errors.Plugin Registration Order
PluginNotFoundException.event(new WithdrawalRequested($data));
FilamentWithdrawalFormBuilder::make(1)->setRules([
'amount' => 'required|numeric|min:0.01',
]);
config('filament-withdrawals.relations')?$hidden/$visible exclusion set?php artisan config:clear after editing.Custom Withdrawal Logic Override the default processing in a service provider:
$this->app->bind(\TomatoPHP\FilamentWithdrawals\Services\Contracts\WithdrawalProcessor::class,
\App\Services\CustomWithdrawalProcessor::class);
Localization Publish and extend language files:
php artisan vendor:publish --tag="filament-withdrawals-lang"
Then add translations to resources/lang/{locale}/filament-withdrawals.php.
Relation Allowlist as Runtime Config Because the allowlist is plain config, you can drive it from a settings table or a service provider for multi-tenant apps — but keep it server-controlled only, never user-editable, or you reintroduce the vulnerability the release fixed.
Testing Use the package's test utilities to mock withdrawals:
$this->actingAs($user)
->post('/withdraw', ['method_id' => 1, 'amount' => 100.00]);
$this->assertDatabaseHas('withdrawal_requests', ['amount' => 100.00]);
Add a regression test asserting a non-allowlisted relation field renders no options and a hidden column (password) is absent from the rendered form.
How can I help you explore Laravel packages today?