Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Whatsapp Widget Laravel Package

jeffersongoncalves/laravel-whatsapp-widget

Add a customizable WhatsApp chat widget to any Laravel app. This package offers an easy-to-configure floating button with a pre-filled message, flexible positioning, and simple Blade integration so visitors can start a conversation instantly.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a small, single-purpose package: it drops a floating WhatsApp launcher into your Blade layout, backed by a whatsapp_agents table and two routes (a token-protected JSON list and a signed redirect page). There is no admin UI — you own the CRUD.

1. Install and create the table

composer require jeffersongoncalves/laravel-whatsapp-widget
php artisan vendor:publish --tag=whatsapp-widget-migrations
php artisan migrate
php artisan route:list --path=whatsapp   # confirm /api/whatsapps + redirect route exist

Publish config (--tag=whatsapp-widget-config), views (--tag=whatsapp-widget-views) and assets only when you need to change something; the defaults are functional.

2. Set the environment

WHATSAPP_KEY=some-long-random-string
FILESYSTEM_DISK=public
APP_URL=https://your-site.test

WHATSAPP_KEY is not optional in practice — see the gotcha below. If agents have avatars, FILESYSTEM_DISK must point at a web-served disk and you must run php artisan storage:link.

3. Seed one agent and wire the Blade partials

WhatsappAgent::create([
    'active' => true,
    'name'   => 'Support',
    'phone'  => '5511999999999',   // digits only, country code first, no + or spaces
    'text'   => 'Hi, I saw your site and need help with…',
    'image'  => 'agents/support.jpg', // relative path on the configured disk
]);
<head>
    @include('whatsapp-widget::whatsapp-widget-head')
</head>
<body>
    {{-- page content --}}
    @include('whatsapp-widget::whatsapp-widget-body')
</body>

Where to look first: config/whatsapp-widget.php (position, name, audio, disk, key), then the published resources/views/vendor/whatsapp-widget/* blades — almost every visual tweak lives there, not in PHP.

First use case: one active agent, default position => 'right', audio off. Ship that, verify the click path reaches wa.me with the prefilled text, then add more agents.

Implementation Patterns

Keep agents in config-seeded data, not hand-edited rows. A seeder makes local/staging/production reproducible and gives you a rollback path:

// database/seeders/WhatsappAgentSeeder.php
WhatsappAgent::updateOrCreate(
    ['phone' => '5511999999999'],
    ['active' => true, 'name' => 'Sales', 'text' => 'I want a quote', 'image' => 'agents/sales.jpg']
);

Admin CRUD is your job. A simple Filament resource or a Nova/Backpack form over WhatsappAgent is the expected integration — the model is plain Eloquent with active, name, phone, text, image. Use a file upload to the configured disk and store the relative path; the model's image_url accessor resolves it via Storage::disk(config('whatsapp-widget.disk'))->url(...). Never store absolute URLs — swapping disk between local and S3 should not require touching rows.

Generate links through the model, not by hand. getLinkByWhatsappAgent($agent, config('whatsapp-widget.url')) produces the signed redirect URL. The signature covers the extra parameters (agent, number, ref), so hand-built strings with a different parameter set will 403. If you need links in emails or a mobile app, reuse the same helper or URL::signedRoute() with the identical parameter list.

Headless / SPA consumption. The JSON endpoint returns id, name, image_url, phone, link for active agents; you can build your own React/Vue widget and ignore the Blade partial entirely. Cache that response (Cache::remember('whatsapp-agents', 300, …)) — the agent list changes rarely.

Blade partials in a component world. Both partials are plain @includes, so they work fine inside Livewire layouts, but they must be inside <body> (the body partial emits markup and inline JS). If your layout is an Inertia root view, either skip the package widget or render the partial in the single Blade shell.

Cache the query the partial runs. The body partial hits WhatsappAgent::where('active', true)->get() on every page render. Wrap the widget data in a small view composer or view model with a 5–10 minute cache, and add an index on active in your own migration if the table grows:

Schema::table('whatsapp_agents', fn (Blueprint $t) => $t->index('active'));

Translations. The package ships 18 locales covering redirecting_to, close, close_title, we_are_available, online, icon_alt. To tweak copy without forking, override in lang/vendor/whatsapp-widget/{locale}/whatsapp-widget.php (or publish the translations tag) rather than editing vendor/.

Testing patterns that match the shipped routes. Pest feature tests are the fastest way to lock behaviour:

it('lists only active agents', function () {
    config(['whatsapp-widget.key' => 'test-key']);
    WhatsappAgent::create(['active' => true,  'name' => 'A', 'phone' => '5511999999999']);
    WhatsappAgent::create(['active' => false, 'name' => 'B', 'phone' => '5511888888888']);

    $this->get('/api/whatsapps?token=test-key')
        ->assertOk()->assertJsonCount(1)->assertJsonFragment(['name' => 'A']);
});

it('renders the redirect page for a signed link', function () {
    $agent = WhatsappAgent::create(['active' => true, 'name' => 'Test',
        'phone' => '5511999999999', 'text' => 'Hello']);

    $this->get($agent->getLinkByWhatsappAgent($agent, config('whatsapp-widget.url')))
        ->assertOk()->assertSee('Test');
});

Because both controllers are invokable single-purpose classes, they're easy to assert against without booting browsers.

Gotchas and Tips

An unset WHATSAPP_KEY opens the API. The controller does abort_if($request->token !== config('whatsapp-widget.key'), 403). With key => null and no token query param, null !== null is false — the request succeeds. Always set the key, and add a boot-time guard (if (app()->isProduction() && blank(config('whatsapp-widget.key'))) throw …) or a test asserting 403 when the key is missing.

The token travels in the query string. It lands in access logs, proxies, and any Referer the endpoint leaks to. It also can't be rotated per client. Treat the token as a rate-limiter, not authentication for sensitive data — the payload (names, phones) is public-facing by design. If you need real auth, put your own middleware in front of /api/whatsapps.

Signed URLs do not expire and do not re-check active. RedirectController does firstOrFail() by id with no where('active', true). Deactivating an agent hides it from the widget but an old signed link still resolves. If deactivation must be hard, add a check in a published redirect view or a route middleware; if links must expire, switch to temporarySignedRoute() and adjust the helper.

Wrong FILESYSTEM_DISK = missing avatars. Default is local, whose URLs point at storage/app/private and are not web-reachable. Set public (plus storage:link) or an S3 disk with a url set. Symptom: widget renders, avatars 404 silently, image_url is non-null but unusable.

Nothing renders if no agent is active. This is intended but confusing during setup: the partial outputs nothing and you'll blame the include order or a missing asset tag. Check WhatsappAgent::where('active', true)->count() before debugging Blade.

Assets 404 after install. The head partial references the package's CSS/JS via hasAssets(). If those requests fail, publish the assets tag (--tag=whatsapp-widget-assets) or verify the public vendor path exists — a cached config:cache from before install is a common culprit (php artisan optimize:clear).

Audio rarely behaves as expected. Two switches — audio and play_audio_daily — control a 3-second-delayed playback using a client-side cookie literally named play. Three failure modes: (a) browsers block autoplay before any user gesture, so first-time visitors often hear nothing; (b) the generic cookie name can collide with another app's cookie on the same domain; (c) the cookie is set in JS, so it's readable/forgeable and won't respect HttpOnly/Secure conventions. Disable audio if you can't accept any of that.

Phone formatting is your responsibility. The redirect builds https://wa.me/{phone}?text={encoded_text} verbatim — spaces, +, parentheses or dashes yield a broken wa.me link. Normalise to digits in a mutator on save.

Publish views, don't fork them. --tag=whatsapp-widget-views puts the blades in resources/views/vendor/whatsapp-widget/, which Laravel prefers over the package copies. That's the sanctioned extension point for position tweaks beyond position => 'left'|'right', custom markup, or adding an agent subtitle. Re-publishing after a package upgrade overwrites your edits — keep changes in git and diff on upgrade.

Extension points are thin by design. There are no events, no contracts, no repository interface. If you need per-page agent selection, per-department routing, or analytics on clicks, wrap the package: your own Blade partial calling WhatsappAgent directly, and keep the package for the redirect route and translations. Don't try to bend a 9-star single-purpose widget into a support platform.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle