vluzrmos/slack-api
A Laravel wrapper for the Slack API that provides a simple facade and configuration for sending messages, managing channels and users, and working with incoming webhooks, making Slack integrations clean and idiomatic in PHP applications.
Install
composer require vluzrmos/slack-api
On Laravel 5.5+ the service provider and Slack facade are auto-discovered. On Lumen, register manually in bootstrap/app.php:
$app->register(Vluzrmos\SlackApi\SlackApiServiceProvider::class);
class_alias(Vluzrmos\SlackApi\Facades\Slack::class, 'Slack');
$app->withFacades();
Configure Publish the config and set a token:
php artisan vendor:publish --provider="Vluzrmos\SlackApi\SlackApiServiceProvider"
SLACK_TOKEN=xoxb-your-bot-token
SLACK_DEFAULT_CHANNEL=#general
In Lumen, copy config/slack.php manually into your config/ directory and load it via $app->configure('slack').
Look here first
config/slack.php — this is the whole surface area: token, endpoint, default_channel.Vluzrmos\SlackApi\Facades\Slack — the facade you will call.First use case — notify a channel
use Vluzrmos\SlackApi\Facades\Slack;
$response = Slack::post('chat.postMessage', [
'channel' => '#general',
'text' => 'Deploy to production finished ✅',
]);
if (($response['ok'] ?? false) !== true) {
logger()->error('Slack failed', $response);
}
Create a Slack app in your workspace, add a bot user, install it, and grant at minimum chat:write (plus channels:read, users:read if you want to resolve names).
Treat it as a transport, not an SDK. Every call is Slack::get($method, $params) or Slack::post($method, $params); the token is injected for you. That means you get full coverage of the Web API with one package, but zero type safety and zero compile-time checks.
Wrap it in your own service. Do not sprinkle Slack::post(...) throughout controllers. Create App\Services\SlackNotifier with typed methods (notifyDeploy(), notifyUserMention()), inject the Slack contract or facade into it, then bind it in a service provider. This gives you a seam for testing and a swap point if you ever move to laravel/slack-notification-channel or the official SDK.
Queued notifications. Always send from a queued job, never inline in a request:
class NotifySlackChannel implements ShouldQueue
{
public function __construct(public array $payload) {}
public function handle(): void
{
Slack::post('chat.postMessage', $this->payload);
}
}
Slack is rate-limited; a synchronous call in a controller on a slow network will bite you.
Block Kit payloads. Build a payload array and pass it through; the package just serializes JSON:
Slack::post('chat.postMessage', [
'channel' => $channelId,
'text' => 'New signup', // required fallback even with blocks
'blocks' => json_encode($blocks),
]);
Note that blocks frequently needs to be a JSON string on the raw API, not an array — check Slack's docs for the exact shape, since the package does not transform it.
Multiple workspaces. The package binds a single configured client. For multi-tenant Slack, instantiate the underlying class directly with a per-tenant token and cache the instance (per-tenant, per-request) rather than mutating the singleton's token via config(['slack.token' => ...]), which leaks across the container.
Testing. Mock the facade — this is the biggest reason to go through the facade rather than a raw Guzzle client:
Slack::shouldReceive('post')
->once()
->with('chat.postMessage', Mockery::on(fn ($p) => $p['channel'] === '#alerts'))
->andReturn(['ok' => true]);
Because the package uses Guzzle directly under the hood, Laravel's Http::fake() will not intercept it. Facade mocking is the reliable path.
Integration points worth wiring up
php artisan slack:notify command called from your CI pipeline.#errors channel via a queued listener, not in the handler itself.Slack::get('api.test') as a connectivity probe in a scheduled command.HTTP 200 does not mean success. Slack returns 200 OK with {"ok": false, "error": "channel_not_found"}. The package will not throw. You must inspect $response['ok'] every single call — or wrap the client in a decorator that throws on ok === false. This is the #1 source of silent failures.
Rate limits are real. Slack enforces per-method tiers and returns 429 with a Retry-After header. Implement exponential backoff in your job ($this->release(...)) or throttle per channel. A notification loop over 500 users will trip this immediately.
Resolve channel names to IDs. Passing '#general' works but is fragile (renames, private channels, name collisions). Look up the channel ID once via channels.list / conversations.list and cache it (Cache::remember('slack.channel.alerts', 3600, ...)). Use IDs in payloads.
text is required even with Block Kit. Slack renders the fallback in notifications; omit it and the message can render blank or be rejected.
Config caching. The package's config uses env(). Once you run php artisan config:cache, the values are frozen — do not touch .env after caching in production and expect a change. In Lumen, env() inside config files won't work unless you explicitly enable Dotenv.
File uploads need multipart. files.upload and the newer files.getUploadURLExternal flow require multipart/form-data. The package's JSON-oriented proxy is not the right tool — fall back to plain Guzzle or Laravel's Http client for those calls.
Pagination via cursors. For conversations.list, users.list, etc., read response_metadata.next_cursor and loop with cursor in the params. The package does not paginate for you.
Timeouts. The default Guzzle timeout may be shorter than Slack's response under load. If you see sporadic cURL error 28, bump the timeout on the client the package registers (bind your own instance in the container if needed).
Debugging. There's no request log. When something fails opaquely, dump the raw response array before any transformation, or bind a Guzzle middleware that logs request/response bodies to a dedicated Slack log channel. Log the method name too — a typo returns unknown_method and you'll waste an afternoon staring at the wrong layer.
Extension point. Because the endpoint is configurable, you can point slack.endpoint at a proxy, an Enterprise Grid internal gateway, or a local stub server for integration tests.
Keep the blast radius small. This is a 99-star, zero-dependent package — a thin wrapper, not a battle-tested client. Confine all usage to one service class so that swapping to the official slack-php/slack-php-sdk later is a one-file change rather than a grep-and-replace.
How can I help you explore Laravel packages today?