| name | whatsapp-widget-development |
| description | Development guide for the Laravel WhatsApp Widget package - floating WhatsApp contact widget with agent management, audio notifications, and signed URL redirects |
When to use this skill
- Adding new WhatsApp agents to the database
- Customizing the widget appearance or position
- Adding new translations
- Modifying the redirect behavior or WhatsApp message template
- Writing tests for the controllers and model
- Integrating the widget into a Laravel or Filament application
Setup
Requirements
- PHP 8.2 or 8.3
- Laravel 11, 12, or 13
- spatie/laravel-package-tools ^1.14.0
Installation
composer require jeffersongoncalves/laravel-whatsapp-widget
Publish and Run Migration
php artisan vendor:publish --tag=whatsapp-widget-migrations
php artisan migrate
Publish Config (Optional)
php artisan vendor:publish --tag=whatsapp-widget-config
Publish Views (Optional)
php artisan vendor:publish --tag=whatsapp-widget-views
Environment Variables
WHATSAPP_KEY=your-secret-api-key
FILESYSTEM_DISK=public
Architecture
Namespace Structure
JeffersonGoncalves\WhatsappWidget\
WhatsappWidgetServiceProvider # Registers config, views, migrations, routes, assets, translations
Models\
WhatsappAgent # Eloquent model for whatsapp_agents table
Http\
Controllers\
WhatsappsController # JSON API endpoint for listing active agents
RedirectController # Redirect page to wa.me link
Service Provider
class WhatsappWidgetServiceProvider extends PackageServiceProvider
{
public function configurePackage(Package $package): void
{
$package->name('whatsapp-widget')
->hasMigration('create_whatsapp_agents_table')
->hasConfigFile('whatsapp-widget')
->hasAssets()
->hasViews()
->hasTranslations()
->hasRoute('whatsapp-widget');
}
}
Features
WhatsappAgent Model
The model represents a support agent with a WhatsApp number:
use JeffersonGoncalves\WhatsappWidget\Models\WhatsappAgent;
WhatsappAgent::create([
'active' => true,
'name' => 'Support Team',
'phone' => '+5511999999999',
'text' => 'Hello, I need help with...',
'image' => 'agents/avatar.jpg', // Path on configured storage disk
]);
$agent->image_url;
$agent->getLinkByWhatsappAgent($agent, config('whatsapp-widget.url'));
Adding the Widget to Your Layout
Include two Blade partials in your layout:
<head>
@include('whatsapp-widget::whatsapp-widget-head')
</head>
<body>
@include('whatsapp-widget::whatsapp-widget-body')
</body>
The widget only renders if there are active WhatsappAgent records in the database.
Widget Behavior
- Displays a floating WhatsApp icon (configurable left/right position)
- Clicking opens a panel listing all active agents
- Each agent shows their name, avatar, and "Online" status
- Clicking an agent opens a signed redirect URL
- The redirect page shows agent info then forwards to
wa.me/{phone}?text={text}
- Optional audio notification plays after 3 seconds (cookie-based daily limit)
Routes
GET /api/whatsapps?token={key}
GET /whatsapp-widget/redirect/{whatsapp_agent}
JSON API Controller
class WhatsappsController
{
public function __invoke(Request $request): JsonResponse
{
abort_if($request->token !== config('whatsapp-widget.key'), 403);
$whatsappAgents = WhatsappAgent::query()->where('active', true)->get();
}
}
Redirect Controller
class RedirectController
{
public function __invoke(string $whatsapp_agent): View
{
$whatsappAgent = WhatsappAgent::query()->where('id', $whatsapp_agent)->firstOrFail();
return view('whatsapp-widget::whatsapp-redirect', compact('whatsappAgent'));
}
}
The redirect page sets a JavaScript variable and auto-redirects:
var ww_whatsapp_chat_redirect = "https://wa.me/{phone}?text={encoded_text}";
Audio Notification
Controlled by two config options:
'audio' => true,
'play_audio_daily' => true,
The audio plays after a 3-second delay. A cookie named play is set with a 24-hour
expiration to prevent repeated playback.
Translations
17+ languages supported. Translation keys use the whatsapp-widget::whatsapp-widget.* prefix:
'redirecting_to' => 'Redirecting to',
'close' => 'Close',
'close_title' => 'Close Support',
'we_are_available' => 'We are available',
'online' => 'Online',
'icon_alt' => 'WhatsApp icon',
Supported locales: en, pt_BR, pt_PT, pt, es, fr, de, it, nl, pl, ar, cs, fa, he, id, ja, sk, tr.
Configuration
return [
'audio' => true,
'play_audio_daily' => true,
'disk' => env('FILESYSTEM_DISK', 'local'),
'url' => env('APP_URL', 'http://localhost'),
'name' => env('APP_NAME', 'Laravel App'),
'key' => env('WHATSAPP_KEY'),
'position' => 'right',
];
Testing Patterns
Testing the API Endpoint
use JeffersonGoncalves\WhatsappWidget\Models\WhatsappAgent;
it('returns active agents as JSON', function () {
config(['whatsapp-widget.key' => 'test-key']);
WhatsappAgent::create([
'active' => true,
'name' => 'Agent 1',
'phone' => '+5511999999999',
]);
$this->get('/api/whatsapps?token=test-key')
->assertOk()
->assertJsonCount(1)
->assertJsonFragment(['name' => 'Agent 1']);
});
it('rejects requests without valid token', function () {
$this->get('/api/whatsapps?token=wrong-key')
->assertForbidden();
});
Testing the Redirect
it('redirects to agent page with signed URL', function () {
$agent = WhatsappAgent::create([
'active' => true,
'name' => 'Test Agent',
'phone' => '+5511999999999',
'text' => 'Hello',
]);
$url = URL::signedRoute('whatsapp-widget.redirect', [
'whatsapp_agent' => $agent->id,
'agent' => $agent->id,
'number' => $agent->phone,
'ref' => config('whatsapp-widget.url'),
]);
$this->get($url)->assertOk()->assertSee('Test Agent');
});
Testing the Model
it('generates image URL from storage disk', function () {
config(['whatsapp-widget.disk' => 'public']);
$agent = WhatsappAgent::create([
'active' => true,
'name' => 'Agent',
'phone' => '+5511999999999',
'image' => 'agents/photo.jpg',
]);
expect($agent->image_url)->toContain('agents/photo.jpg');
});
it('returns null image URL when no image', function () {
$agent = WhatsappAgent::create([
'active' => true,
'name' => 'Agent',
'phone' => '+5511999999999',
]);
expect($agent->image_url)->toBeNull();
});
Dev Commands
vendor/bin/pest
vendor/bin/phpstan analyse
vendor/bin/pint