jeffersongoncalves/laravel-buffer
A Laravel integration for the Buffer API that lets you schedule and manage social media posts directly from your application. It provides a simple, configurable wrapper for connecting your Buffer account and publishing content across supported networks.
This package wraps the Buffer API so you can schedule and inspect social posts from Laravel instead of hand-rolling Guzzle calls. Since 1.0.0 is the first stable tag, treat the changelog and the package's src/ + config/ as the source of truth — the release notes themselves are just a tag.
Minimal setup:
composer require jeffersongoncalves/laravel-bufferphp artisan vendor:publish --tag=buffer-config (check php artisan vendor:publish for the exact tag — providers registered by the package will list it)..env — typically a Buffer access token (and possibly a client id/secret if the package ships an OAuth flow). Read the published config/buffer.php first; it names the exact env keys.app(Buffer::class) / the facade shown in the package README).First use case: read your channels, then post something. Resolve the client, call the "list profiles/channels" endpoint, grab a channel id, and push a single update with text + optional media to the queue or to a specific scheduled_at. Do this in php artisan tinker before writing any app code — it confirms your token scopes and shows you the response shape you'll be mapping in jobs.
Wrap every call in a queued job. Buffer is an external HTTP dependency; calling it inline from a controller or an Eloquent observer will eventually time out a web request. A PublishToBuffer job with retry(3) and backoff([30, 120, 600]) is the standard shape:
final class PublishToBuffer implements ShouldQueue
{
public function __construct(public Post $post) {}
public function handle(Buffer $buffer): void
{
$update = $buffer->updates()->create([
'profile_ids' => [$this->post->channel_id],
'text' => $this->post->body,
'media' => ['photo' => $this->post->image_url],
'scheduled_at' => $this->post->publish_at->toIso8601String(),
]);
$this->post->update(['buffer_update_id' => $update->id]);
}
}
(The exact method names come from the package's client class — read it once and mirror it.)
Persist the remote id. Store buffer_update_id on your model. You will need it for edits, deletes, and for reconciling status later. Without it you're reduced to fuzzy text matching.
Use "add to queue" vs "schedule at" deliberately. Buffer has two distinct modes: now/queue (drops into the next free slot for that channel's posting schedule) and scheduled_at (explicit timestamp). Model these as two enum cases in your own app, because they behave completely differently when you're bulk-importing content.
Multi-tenant / multi-account. If each of your users has their own Buffer token, don't rely on the singleton config. Resolve a client instance per tenant with their stored token and pass it into the job — the config file is the default, not the ceiling. Cache the channel list per tenant with a short TTL; it changes rarely and costs a request every time.
Testing. The package almost certainly goes through Laravel's HTTP client, so Http::fake() with a canned Buffer payload lets you assert your job without network. Fake the package's endpoints (check src/ for the base URL) rather than wrapping it in your own HTTP layer.
Integration tip: keep a thin BufferPublisher service of your own that speaks your domain (publish(Post $post)), and let it delegate to the package. That gives you one place to swap packages, add idempotency keys, and add logging — and it keeps package types out of your controllers.
Bus::batch()->chunk() and add release() on 429 rather than letting the job fail. A failed schedule is silent — nothing gets posted and nobody notices until a client asks.scheduled_at is absolute; the queue is relative to the channel's configured timezone, which you don't control from Laravel. If exact publish times matter, use scheduled_at with an explicit UTC/Z offset, not the queue.storage/app paths and localhost URLs will fail at Buffer's end, not yours — the create call succeeds and the post later shows a broken image. Use signed/public URLs with a generous expiry.$response->body()), because the package may only throw a status-coded exception. A ?debug=true middleware logging all outgoing HTTP via Http::globalRequestMiddleware() is worth adding during integration.^1.0 and watch for the first few patch releases — pre-1.0 packages often carry naming churn into their first stable tag. Read the config file after every minor bump; config keys are the most common silent breaking change.$this->app->bind(...) in your own provider — useful for injecting a mocked client in feature tests without touching Http::fake().How can I help you explore Laravel packages today?